pkg/proc: support swiss table map implementation (#3838)
Adds support for the swiss table implementation for Go's maps.
This commit is contained in:
parent
698f838616
commit
477e46ebbd
@ -34,6 +34,11 @@ type astruct struct {
|
|||||||
B int
|
B int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type largestruct struct {
|
||||||
|
name string
|
||||||
|
v [256]byte
|
||||||
|
}
|
||||||
|
|
||||||
type astructName1 astruct
|
type astructName1 astruct
|
||||||
type astructName2 astruct
|
type astructName2 astruct
|
||||||
|
|
||||||
@ -267,6 +272,7 @@ func main() {
|
|||||||
m2 := map[int]*astruct{1: &astruct{10, 11}}
|
m2 := map[int]*astruct{1: &astruct{10, 11}}
|
||||||
m3 := map[astruct]int{{1, 1}: 42, {2, 2}: 43}
|
m3 := map[astruct]int{{1, 1}: 42, {2, 2}: 43}
|
||||||
m4 := map[astruct]astruct{{1, 1}: {11, 11}, {2, 2}: {22, 22}}
|
m4 := map[astruct]astruct{{1, 1}: {11, 11}, {2, 2}: {22, 22}}
|
||||||
|
mlarge := map[largestruct]largestruct{largestruct{name: "one"}: largestruct{name: "oneval"}}
|
||||||
upnil := unsafe.Pointer(nil)
|
upnil := unsafe.Pointer(nil)
|
||||||
up1 := unsafe.Pointer(&i1)
|
up1 := unsafe.Pointer(&i1)
|
||||||
i4 := 800
|
i4 := 800
|
||||||
@ -427,5 +433,5 @@ func main() {
|
|||||||
longslice := make([]int, 100, 100)
|
longslice := make([]int, 100, 100)
|
||||||
|
|
||||||
runtime.Breakpoint()
|
runtime.Breakpoint()
|
||||||
fmt.Println(i1, i2, i3, p1, pp1, amb1, s1, s3, a0, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, m4, m5, upnil, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, ni64, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, rettm, errtypednil, emptyslice, emptymap, byteslice, bytestypeslice, runeslice, bytearray, bytetypearray, runearray, longstr, nilstruct, as2, as2.NonPointerReceiverMethod, s4, iface2map, issue1578, ll, unread, w2, w3, w4, w5, longarr, longslice, val, m6, m7, cl, tim1, tim2, typedstringvar, namedA1, namedA2, astructName1(namedA2), badslice, tim3, int3chan, longbyteslice, enum1, enum2, enum3, enum4, enum5, enum6, zeropoint4)
|
fmt.Println(i1, i2, i3, p1, pp1, amb1, s1, s3, a0, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, m4, m5, upnil, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, ni64, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, rettm, errtypednil, emptyslice, emptymap, byteslice, bytestypeslice, runeslice, bytearray, bytetypearray, runearray, longstr, nilstruct, as2, as2.NonPointerReceiverMethod, s4, iface2map, issue1578, ll, unread, w2, w3, w4, w5, longarr, longslice, val, m6, m7, cl, tim1, tim2, typedstringvar, namedA1, namedA2, astructName1(namedA2), badslice, tim3, int3chan, longbyteslice, enum1, enum2, enum3, enum4, enum5, enum6, zeropoint4, mlarge)
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,8 @@ const emptyOne = 1
|
|||||||
|
|
||||||
const emptyRest = 0
|
const emptyRest = 0
|
||||||
|
|
||||||
|
const internal/runtime/maps.ctrlEmpty = 128
|
||||||
|
|
||||||
const kindDirectIface|internal/abi.KindDirectIface = 32
|
const kindDirectIface|internal/abi.KindDirectIface = 32
|
||||||
|
|
||||||
const kindGCProg|internal/abi.KindGCProg = 64
|
const kindGCProg|internal/abi.KindGCProg = 64
|
||||||
|
@ -56,6 +56,14 @@
|
|||||||
// - a type defined in the runtime package, without the 'runtime.' prefix
|
// - a type defined in the runtime package, without the 'runtime.' prefix
|
||||||
// - anytype to match all possible types
|
// - anytype to match all possible types
|
||||||
// - an expression of the form T1|T2 where both T1 and T2 can be arbitrary type expressions
|
// - an expression of the form T1|T2 where both T1 and T2 can be arbitrary type expressions
|
||||||
|
//
|
||||||
|
// GO VERSION RESTRICTIONS
|
||||||
|
//
|
||||||
|
// A rtype comment of this form:
|
||||||
|
//
|
||||||
|
// // +rtype go1.24 ...
|
||||||
|
//
|
||||||
|
// Will only be applied to versions of Go following version 1.24.0
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
@ -70,6 +78,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -78,6 +87,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const magicCommentPrefix = "+rtype"
|
const magicCommentPrefix = "+rtype"
|
||||||
|
const gover = "go1."
|
||||||
|
|
||||||
var fset = &token.FileSet{}
|
var fset = &token.FileSet{}
|
||||||
var checkVarTypeRules = []*checkVarType{}
|
var checkVarTypeRules = []*checkVarType{}
|
||||||
@ -97,6 +107,7 @@ type rtypeCmnt struct {
|
|||||||
type checkVarType struct {
|
type checkVarType struct {
|
||||||
V, T string // V must have type T
|
V, T string // V must have type T
|
||||||
pos token.Pos
|
pos token.Pos
|
||||||
|
firstMinor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *checkVarType) String() string {
|
func (c *checkVarType) String() string {
|
||||||
@ -111,6 +122,7 @@ type checkFieldType struct {
|
|||||||
S, F, T string // S.F must have type T
|
S, F, T string // S.F must have type T
|
||||||
opt bool
|
opt bool
|
||||||
pos token.Pos
|
pos token.Pos
|
||||||
|
firstMinor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *checkFieldType) String() string {
|
func (c *checkFieldType) String() string {
|
||||||
@ -122,6 +134,13 @@ type checkConstVal struct {
|
|||||||
C string // const C = V
|
C string // const C = V
|
||||||
V constant.Value
|
V constant.Value
|
||||||
pos token.Pos
|
pos token.Pos
|
||||||
|
firstMinor
|
||||||
|
}
|
||||||
|
|
||||||
|
type firstMinor int
|
||||||
|
|
||||||
|
func (firstMinor firstMinor) versionOk(curminor int) bool {
|
||||||
|
return curminor >= int(firstMinor)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *checkConstVal) String() string {
|
func (c *checkConstVal) String() string {
|
||||||
@ -255,31 +274,38 @@ func process(pkg *packages.Package, rtcmnt *rtypeCmnt, cmntmap ast.CommentMap, r
|
|||||||
tinfo := pkg.TypesInfo
|
tinfo := pkg.TypesInfo
|
||||||
fields := strings.Split(rtcmnt.txt, " ")
|
fields := strings.Split(rtcmnt.txt, " ")
|
||||||
|
|
||||||
|
firstMinor := 0
|
||||||
|
|
||||||
|
if strings.HasPrefix(fields[1], gover) {
|
||||||
|
firstMinor, _ = strconv.Atoi(fields[1][len(gover):])
|
||||||
|
fields = fields[1:]
|
||||||
|
}
|
||||||
|
|
||||||
switch fields[1] {
|
switch fields[1] {
|
||||||
case "-var":
|
case "-var":
|
||||||
// -var V T
|
// -var V T
|
||||||
// requests that variable V is of type T
|
// requests that variable V is of type T
|
||||||
addCheckVarType(fields[2], fields[3], rtcmnt.slash)
|
addCheckVarType(fields[2], fields[3], rtcmnt.slash, firstMinor)
|
||||||
case "-field":
|
case "-field":
|
||||||
// -field S.F T
|
// -field S.F T
|
||||||
// requests that field F of type S is of type T
|
// requests that field F of type S is of type T
|
||||||
v := strings.Split(fields[2], ".")
|
v := strings.Split(fields[2], ".")
|
||||||
addCheckFieldType(v[0], v[1], fields[3], false, rtcmnt.slash)
|
addCheckFieldType(v[0], v[1], fields[3], false, rtcmnt.slash, firstMinor)
|
||||||
default:
|
default:
|
||||||
ok := false
|
ok := false
|
||||||
if ident := isProcVariableDecl(rtcmnt.stmt, tinfo); ident != nil {
|
if ident := isProcVariableDecl(rtcmnt.stmt, tinfo); ident != nil {
|
||||||
if len(fields) == 2 {
|
if len(fields) == 2 {
|
||||||
processProcVariableUses(rtcmnt.toplevel, tinfo, ident, cmntmap, rtcmnts, fields[1])
|
processProcVariableUses(rtcmnt.toplevel, tinfo, ident, cmntmap, rtcmnts, fields[1], firstMinor)
|
||||||
ok = true
|
ok = true
|
||||||
} else if len(fields) == 3 && fields[1] == "-opt" {
|
} else if len(fields) == 3 && fields[1] == "-opt" {
|
||||||
processProcVariableUses(rtcmnt.toplevel, tinfo, ident, cmntmap, rtcmnts, fields[2])
|
processProcVariableUses(rtcmnt.toplevel, tinfo, ident, cmntmap, rtcmnts, fields[2], firstMinor)
|
||||||
ok = true
|
ok = true
|
||||||
}
|
}
|
||||||
} else if ident := isConstDecl(rtcmnt.toplevel, rtcmnt.node); len(fields) == 2 && ident != nil {
|
} else if ident := isConstDecl(rtcmnt.toplevel, rtcmnt.node); len(fields) == 2 && ident != nil {
|
||||||
addCheckConstVal(fields[1], constValue(tinfo.Defs[ident]), rtcmnt.slash)
|
addCheckConstVal(fields[1], constValue(tinfo.Defs[ident]), rtcmnt.slash, firstMinor)
|
||||||
ok = true
|
ok = true
|
||||||
} else if F := isStringCaseClause(rtcmnt.stmt); F != "" && len(fields) == 4 && fields[1] == "-fieldof" {
|
} else if F := isStringCaseClause(rtcmnt.stmt); F != "" && len(fields) == 4 && fields[1] == "-fieldof" {
|
||||||
addCheckFieldType(fields[2], F, fields[3], false, rtcmnt.slash)
|
addCheckFieldType(fields[2], F, fields[3], false, rtcmnt.slash, firstMinor)
|
||||||
ok = true
|
ok = true
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -359,7 +385,7 @@ func isStringCaseClause(stmt ast.Stmt) string {
|
|||||||
// processProcVariableUses scans the body of the function declaration 'decl'
|
// processProcVariableUses scans the body of the function declaration 'decl'
|
||||||
// looking for uses of 'procVarIdent' which is assumed to be an identifier
|
// looking for uses of 'procVarIdent' which is assumed to be an identifier
|
||||||
// for a *proc.Variable variable.
|
// for a *proc.Variable variable.
|
||||||
func processProcVariableUses(decl ast.Node, tinfo *types.Info, procVarIdent *ast.Ident, cmntmap ast.CommentMap, rtcmnts []*rtypeCmnt, S string) {
|
func processProcVariableUses(decl ast.Node, tinfo *types.Info, procVarIdent *ast.Ident, cmntmap ast.CommentMap, rtcmnts []*rtypeCmnt, S string, firstMinor int) {
|
||||||
if len(S) > 0 && S[0] == '*' {
|
if len(S) > 0 && S[0] == '*' {
|
||||||
S = S[1:]
|
S = S[1:]
|
||||||
}
|
}
|
||||||
@ -435,7 +461,7 @@ func processProcVariableUses(decl ast.Node, tinfo *types.Info, procVarIdent *ast
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
F, _ := strconv.Unquote(arg0.Value)
|
F, _ := strconv.Unquote(arg0.Value)
|
||||||
addCheckFieldType(S, F, typ, opt, fncall.Pos())
|
addCheckFieldType(S, F, typ, opt, fncall.Pos(), firstMinor)
|
||||||
//printNode(fset, fncall)
|
//printNode(fset, fncall)
|
||||||
default:
|
default:
|
||||||
pos := fset.Position(n.Pos())
|
pos := fset.Position(n.Pos())
|
||||||
@ -454,18 +480,18 @@ func findComment(slash token.Pos, rtcmnts []*rtypeCmnt) int {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func addCheckVarType(V, T string, pos token.Pos) {
|
func addCheckVarType(V, T string, pos token.Pos, firstMinor_ int) {
|
||||||
checkVarTypeRules = append(checkVarTypeRules, &checkVarType{V, T, pos})
|
checkVarTypeRules = append(checkVarTypeRules, &checkVarType{V, T, pos, firstMinor(firstMinor_)})
|
||||||
}
|
}
|
||||||
|
|
||||||
func addCheckFieldType(S, F, T string, opt bool, pos token.Pos) {
|
func addCheckFieldType(S, F, T string, opt bool, pos token.Pos, firstMinor_ int) {
|
||||||
if !strings.Contains(S, "|") {
|
if !strings.Contains(S, "|") {
|
||||||
checkFieldTypeRules[S] = append(checkFieldTypeRules[S], &checkFieldType{S, F, T, opt, pos})
|
checkFieldTypeRules[S] = append(checkFieldTypeRules[S], &checkFieldType{S, F, T, opt, pos, firstMinor(firstMinor_)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addCheckConstVal(C string, V constant.Value, pos token.Pos) {
|
func addCheckConstVal(C string, V constant.Value, pos token.Pos, firstMinor_ int) {
|
||||||
checkConstValRules[C] = append(checkConstValRules[C], &checkConstVal{C, V, pos})
|
checkConstValRules[C] = append(checkConstValRules[C], &checkConstVal{C, V, pos, firstMinor(firstMinor_)})
|
||||||
}
|
}
|
||||||
|
|
||||||
// report writes a report of all rules derived from the proc package to stdout.
|
// report writes a report of all rules derived from the proc package to stdout.
|
||||||
@ -548,7 +574,7 @@ func check() {
|
|||||||
pkgmap := map[string]*packages.Package{}
|
pkgmap := map[string]*packages.Package{}
|
||||||
allok := true
|
allok := true
|
||||||
|
|
||||||
for _, rule := range checkVarTypeRules {
|
for _, rule := range versionOkFilter(checkVarTypeRules) {
|
||||||
pos := fset.Position(rule.pos)
|
pos := fset.Position(rule.pos)
|
||||||
def := lookupPackage(pkgmap, "runtime").Types.Scope().Lookup(rule.V)
|
def := lookupPackage(pkgmap, "runtime").Types.Scope().Lookup(rule.V)
|
||||||
if def == nil {
|
if def == nil {
|
||||||
@ -569,7 +595,10 @@ func check() {
|
|||||||
}
|
}
|
||||||
sort.Strings(Ss)
|
sort.Strings(Ss)
|
||||||
for _, S := range Ss {
|
for _, S := range Ss {
|
||||||
rules := checkFieldTypeRules[S]
|
rules := versionOkFilter(checkFieldTypeRules[S])
|
||||||
|
if len(rules) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
pos := fset.Position(rules[0].pos)
|
pos := fset.Position(rules[0].pos)
|
||||||
|
|
||||||
def := lookupTypeDef(pkgmap, S)
|
def := lookupTypeDef(pkgmap, S)
|
||||||
@ -617,7 +646,10 @@ func check() {
|
|||||||
}
|
}
|
||||||
sort.Strings(Cs)
|
sort.Strings(Cs)
|
||||||
for _, C := range Cs {
|
for _, C := range Cs {
|
||||||
rules := checkConstValRules[C]
|
rules := versionOkFilter(checkConstValRules[C])
|
||||||
|
if len(rules) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
pos := fset.Position(rules[0].pos)
|
pos := fset.Position(rules[0].pos)
|
||||||
def := findConst(pkgmap, C)
|
def := findConst(pkgmap, C)
|
||||||
if def == nil {
|
if def == nil {
|
||||||
@ -716,3 +748,14 @@ func relative(s string) string {
|
|||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func versionOkFilter[T interface{ versionOk(int) bool }](rules []T) []T {
|
||||||
|
curminor, _ := strconv.Atoi(strings.Split(runtime.Version()[len(gover):], ".")[0])
|
||||||
|
r := []T{}
|
||||||
|
for _, rule := range rules {
|
||||||
|
if rule.versionOk(curminor) {
|
||||||
|
rules = append(rules, rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
@ -467,6 +467,9 @@ func qf(*types.Package) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTypecheckRPC(t *testing.T) {
|
func TestTypecheckRPC(t *testing.T) {
|
||||||
|
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 24) {
|
||||||
|
t.Skip("disabled due to export format changes")
|
||||||
|
}
|
||||||
fset := &token.FileSet{}
|
fset := &token.FileSet{}
|
||||||
cfg := &packages.Config{
|
cfg := &packages.Config{
|
||||||
Mode: packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedName | packages.NeedCompiledGoFiles | packages.NeedTypes,
|
Mode: packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedName | packages.NeedCompiledGoFiles | packages.NeedTypes,
|
||||||
@ -1349,6 +1352,9 @@ func TestVersion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStaticcheck(t *testing.T) {
|
func TestStaticcheck(t *testing.T) {
|
||||||
|
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 24) {
|
||||||
|
t.Skip("disabled due to export format changes")
|
||||||
|
}
|
||||||
_, err := exec.LookPath("staticcheck")
|
_, err := exec.LookPath("staticcheck")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Skip("staticcheck not installed")
|
t.Skip("staticcheck not installed")
|
||||||
|
@ -1840,7 +1840,7 @@ func lenBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
|
|||||||
}
|
}
|
||||||
return newConstant(arg.Children[0].Value, arg.mem), nil
|
return newConstant(arg.Children[0].Value, arg.mem), nil
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
it := arg.mapIterator()
|
it := arg.mapIterator(0)
|
||||||
if arg.Unreadable != nil {
|
if arg.Unreadable != nil {
|
||||||
return nil, arg.Unreadable
|
return nil, arg.Unreadable
|
||||||
}
|
}
|
||||||
@ -2152,7 +2152,7 @@ func (scope *EvalScope) evalReslice(op *evalop.Reslice, stack *evalStack) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
xev.mapSkip += int(low)
|
xev.mapSkip += int(low)
|
||||||
xev.mapIterator() // reads map length
|
xev.mapIterator(0) // reads map length
|
||||||
if int64(xev.mapSkip) >= xev.Len {
|
if int64(xev.mapSkip) >= xev.Len {
|
||||||
stack.err = errors.New("map index out of bounds")
|
stack.err = errors.New("map index out of bounds")
|
||||||
return
|
return
|
||||||
@ -2755,7 +2755,7 @@ func (v *Variable) sliceAccess(idx int) (*Variable, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *Variable) mapAccess(idx *Variable) (*Variable, error) {
|
func (v *Variable) mapAccess(idx *Variable) (*Variable, error) {
|
||||||
it := v.mapIterator()
|
it := v.mapIterator(0)
|
||||||
if it == nil {
|
if it == nil {
|
||||||
return nil, fmt.Errorf("can not access unreadable map: %v", v.Unreadable)
|
return nil, fmt.Errorf("can not access unreadable map: %v", v.Unreadable)
|
||||||
}
|
}
|
||||||
|
602
pkg/proc/mapiter.go
Normal file
602
pkg/proc/mapiter.go
Normal file
@ -0,0 +1,602 @@
|
|||||||
|
package proc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/go-delve/delve/pkg/dwarf/godwarf"
|
||||||
|
"github.com/go-delve/delve/pkg/goversion"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mapIterator interface {
|
||||||
|
next() bool
|
||||||
|
key() *Variable
|
||||||
|
value() *Variable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Variable) mapIterator(maxNumBuckets uint64) mapIterator {
|
||||||
|
mt := v.RealType.(*godwarf.MapType)
|
||||||
|
sv := v.clone()
|
||||||
|
sv.RealType = resolveTypedef(&(sv.RealType.(*godwarf.MapType).TypedefType))
|
||||||
|
sv = sv.maybeDereference()
|
||||||
|
v.Base = sv.Addr
|
||||||
|
|
||||||
|
maptype, ok := sv.RealType.(*godwarf.StructType)
|
||||||
|
if !ok {
|
||||||
|
v.Unreadable = errors.New("wrong real type for map")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
isptr := func(typ godwarf.Type) bool {
|
||||||
|
_, isptr := typ.(*godwarf.PtrType)
|
||||||
|
return isptr
|
||||||
|
}
|
||||||
|
|
||||||
|
it := &mapIteratorClassic{v: v, bidx: 0, b: nil, idx: 0, maxNumBuckets: maxNumBuckets, keyTypeIsPtr: isptr(mt.KeyType), elemTypeIsPtr: isptr(mt.ElemType)}
|
||||||
|
itswiss := &mapIteratorSwiss{v: v, maxNumGroups: maxNumBuckets, keyTypeIsPtr: isptr(mt.KeyType), elemTypeIsPtr: isptr(mt.ElemType)}
|
||||||
|
|
||||||
|
if sv.Addr == 0 {
|
||||||
|
it.numbuckets = 0
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
v.mem = cacheMemory(v.mem, v.Base, int(v.RealType.Size()))
|
||||||
|
|
||||||
|
for _, f := range maptype.Field {
|
||||||
|
var err error
|
||||||
|
field, _ := sv.toField(f)
|
||||||
|
switch f.Name {
|
||||||
|
// Classic map fields
|
||||||
|
case "count": // +rtype -fieldof hmap int
|
||||||
|
v.Len, err = field.asInt()
|
||||||
|
case "B": // +rtype -fieldof hmap uint8
|
||||||
|
var b uint64
|
||||||
|
b, err = field.asUint()
|
||||||
|
it.numbuckets = 1 << b
|
||||||
|
it.oldmask = (1 << (b - 1)) - 1
|
||||||
|
case "buckets": // +rtype -fieldof hmap unsafe.Pointer
|
||||||
|
it.buckets = field.maybeDereference()
|
||||||
|
case "oldbuckets": // +rtype -fieldof hmap unsafe.Pointer
|
||||||
|
it.oldbuckets = field.maybeDereference()
|
||||||
|
|
||||||
|
// Swisstable map fields
|
||||||
|
case "used":
|
||||||
|
var n uint64
|
||||||
|
n, err = field.asUint()
|
||||||
|
v.Len = int64(n)
|
||||||
|
case "dirPtr":
|
||||||
|
itswiss.dirPtr = field
|
||||||
|
case "dirLen":
|
||||||
|
itswiss.dirLen, err = field.asInt()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
v.Unreadable = err
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if it.buckets == nil && itswiss.dirPtr != nil {
|
||||||
|
itswiss.loadTypes()
|
||||||
|
return itswiss
|
||||||
|
}
|
||||||
|
|
||||||
|
if it.buckets == nil || it.oldbuckets == nil || it.buckets.Kind != reflect.Struct || it.oldbuckets.Kind != reflect.Struct {
|
||||||
|
v.Unreadable = errMapBucketsNotStruct
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
it.hashTophashEmptyOne = hashTophashEmptyZero
|
||||||
|
it.hashMinTopHash = hashMinTopHashGo111
|
||||||
|
if producer := v.bi.Producer(); producer != "" && goversion.ProducerAfterOrEqual(producer, 1, 12) {
|
||||||
|
it.hashTophashEmptyOne = hashTophashEmptyOne
|
||||||
|
it.hashMinTopHash = hashMinTopHashGo112
|
||||||
|
}
|
||||||
|
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
// Classic Maps ///////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
type mapIteratorClassic struct {
|
||||||
|
v *Variable
|
||||||
|
numbuckets uint64
|
||||||
|
oldmask uint64
|
||||||
|
buckets *Variable
|
||||||
|
oldbuckets *Variable
|
||||||
|
b *Variable
|
||||||
|
bidx uint64
|
||||||
|
|
||||||
|
keyTypeIsPtr, elemTypeIsPtr bool
|
||||||
|
|
||||||
|
tophashes *Variable
|
||||||
|
keys *Variable
|
||||||
|
values *Variable
|
||||||
|
overflow *Variable
|
||||||
|
|
||||||
|
maxNumBuckets uint64 // maximum number of buckets to scan
|
||||||
|
|
||||||
|
idx int64
|
||||||
|
|
||||||
|
hashTophashEmptyOne uint64 // Go 1.12 and later has two sentinel tophash values for an empty cell, this is the second one (the first one hashTophashEmptyZero, the same as Go 1.11 and earlier)
|
||||||
|
hashMinTopHash uint64 // minimum value of tophash for a cell that isn't either evacuated or empty
|
||||||
|
}
|
||||||
|
|
||||||
|
var errMapBucketContentsNotArray = errors.New("malformed map type: keys, values or tophash of a bucket is not an array")
|
||||||
|
var errMapBucketContentsInconsistentLen = errors.New("malformed map type: inconsistent array length in bucket")
|
||||||
|
var errMapBucketsNotStruct = errors.New("malformed map type: buckets, oldbuckets or overflow field not a struct")
|
||||||
|
|
||||||
|
func (it *mapIteratorClassic) nextBucket() bool {
|
||||||
|
if it.overflow != nil && it.overflow.Addr > 0 {
|
||||||
|
it.b = it.overflow
|
||||||
|
} else {
|
||||||
|
it.b = nil
|
||||||
|
|
||||||
|
if it.maxNumBuckets > 0 && it.bidx >= it.maxNumBuckets {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for it.bidx < it.numbuckets {
|
||||||
|
it.b = it.buckets.clone()
|
||||||
|
it.b.Addr += uint64(it.buckets.DwarfType.Size()) * it.bidx
|
||||||
|
|
||||||
|
if it.oldbuckets.Addr <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// if oldbuckets is not nil we are iterating through a map that is in
|
||||||
|
// the middle of a grow.
|
||||||
|
// if the bucket we are looking at hasn't been filled in we iterate
|
||||||
|
// instead through its corresponding "oldbucket" (i.e. the bucket the
|
||||||
|
// elements of this bucket are coming from) but only if this is the first
|
||||||
|
// of the two buckets being created from the same oldbucket (otherwise we
|
||||||
|
// would print some keys twice)
|
||||||
|
|
||||||
|
oldbidx := it.bidx & it.oldmask
|
||||||
|
oldb := it.oldbuckets.clone()
|
||||||
|
oldb.Addr += uint64(it.oldbuckets.DwarfType.Size()) * oldbidx
|
||||||
|
|
||||||
|
if it.mapEvacuated(oldb) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldbidx == it.bidx {
|
||||||
|
it.b = oldb
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// oldbucket origin for current bucket has not been evacuated but we have already
|
||||||
|
// iterated over it so we should just skip it
|
||||||
|
it.b = nil
|
||||||
|
it.bidx++
|
||||||
|
}
|
||||||
|
|
||||||
|
if it.b == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
it.bidx++
|
||||||
|
}
|
||||||
|
|
||||||
|
if it.b.Addr <= 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
it.b.mem = cacheMemory(it.b.mem, it.b.Addr, int(it.b.RealType.Size()))
|
||||||
|
|
||||||
|
it.tophashes = nil
|
||||||
|
it.keys = nil
|
||||||
|
it.values = nil
|
||||||
|
it.overflow = nil
|
||||||
|
|
||||||
|
for _, f := range it.b.DwarfType.(*godwarf.StructType).Field {
|
||||||
|
field, err := it.b.toField(f)
|
||||||
|
if err != nil {
|
||||||
|
it.v.Unreadable = err
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if field.Unreadable != nil {
|
||||||
|
it.v.Unreadable = field.Unreadable
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch f.Name {
|
||||||
|
case "tophash": // +rtype -fieldof bmap [8]uint8
|
||||||
|
it.tophashes = field
|
||||||
|
case "keys":
|
||||||
|
it.keys = field
|
||||||
|
case "values":
|
||||||
|
it.values = field
|
||||||
|
case "overflow":
|
||||||
|
it.overflow = field.maybeDereference()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanity checks
|
||||||
|
if it.tophashes == nil || it.keys == nil || it.values == nil {
|
||||||
|
it.v.Unreadable = errors.New("malformed map type")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if it.tophashes.Kind != reflect.Array || it.keys.Kind != reflect.Array || it.values.Kind != reflect.Array {
|
||||||
|
it.v.Unreadable = errMapBucketContentsNotArray
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if it.tophashes.Len != it.keys.Len {
|
||||||
|
it.v.Unreadable = errMapBucketContentsInconsistentLen
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if it.values.fieldType.Size() > 0 && it.tophashes.Len != it.values.Len {
|
||||||
|
// if the type of the value is zero-sized (i.e. struct{}) then the values
|
||||||
|
// array's length is zero.
|
||||||
|
it.v.Unreadable = errMapBucketContentsInconsistentLen
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if it.overflow.Kind != reflect.Struct {
|
||||||
|
it.v.Unreadable = errMapBucketsNotStruct
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *mapIteratorClassic) next() bool {
|
||||||
|
for {
|
||||||
|
if it.b == nil || it.idx >= it.tophashes.Len {
|
||||||
|
r := it.nextBucket()
|
||||||
|
if !r {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
it.idx = 0
|
||||||
|
}
|
||||||
|
tophash, _ := it.tophashes.sliceAccess(int(it.idx))
|
||||||
|
h, err := tophash.asUint()
|
||||||
|
if err != nil {
|
||||||
|
it.v.Unreadable = fmt.Errorf("unreadable tophash: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
it.idx++
|
||||||
|
if h != hashTophashEmptyZero && h != it.hashTophashEmptyOne {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *mapIteratorClassic) key() *Variable {
|
||||||
|
k, _ := it.keys.sliceAccess(int(it.idx - 1))
|
||||||
|
if k.Kind == reflect.Ptr && !it.keyTypeIsPtr {
|
||||||
|
k = k.maybeDereference()
|
||||||
|
}
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *mapIteratorClassic) value() *Variable {
|
||||||
|
if it.values.fieldType.Size() <= 0 {
|
||||||
|
return it.v.newVariable("", it.values.Addr, it.values.fieldType, DereferenceMemory(it.v.mem))
|
||||||
|
}
|
||||||
|
|
||||||
|
v, _ := it.values.sliceAccess(int(it.idx - 1))
|
||||||
|
if v.Kind == reflect.Ptr && !it.elemTypeIsPtr {
|
||||||
|
v = v.maybeDereference()
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *mapIteratorClassic) mapEvacuated(b *Variable) bool {
|
||||||
|
if b.Addr == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, f := range b.DwarfType.(*godwarf.StructType).Field {
|
||||||
|
if f.Name != "tophash" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tophashes, _ := b.toField(f)
|
||||||
|
tophash0var, _ := tophashes.sliceAccess(0)
|
||||||
|
tophash0, err := tophash0var.asUint()
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
//TODO: this needs to be > hashTophashEmptyOne for go >= 1.12
|
||||||
|
return tophash0 > it.hashTophashEmptyOne && tophash0 < it.hashMinTopHash
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swisstable Maps ///////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
const (
|
||||||
|
swissTableCtrlEmpty = 0b10000000 // +rtype go1.24 internal/runtime/maps.ctrlEmpty
|
||||||
|
)
|
||||||
|
|
||||||
|
type mapIteratorSwiss struct {
|
||||||
|
v *Variable
|
||||||
|
dirPtr *Variable
|
||||||
|
dirLen int64
|
||||||
|
maxNumGroups uint64 // Maximum number of groups we will visit
|
||||||
|
|
||||||
|
keyTypeIsPtr, elemTypeIsPtr bool
|
||||||
|
tableType, groupType *godwarf.StructType
|
||||||
|
|
||||||
|
tableFieldIndex, tableFieldGroups, groupsFieldLengthMask, groupsFieldData, groupFieldCtrl, groupFieldSlots, slotFieldKey, slotFieldElem *godwarf.StructField
|
||||||
|
|
||||||
|
dirIdx int64
|
||||||
|
tab *swissTable
|
||||||
|
|
||||||
|
groupIdx uint64
|
||||||
|
group *swissGroup
|
||||||
|
|
||||||
|
slotIdx uint32
|
||||||
|
|
||||||
|
groupCount uint64 // Total count of visited groups except for current table
|
||||||
|
|
||||||
|
curKey, curValue *Variable
|
||||||
|
}
|
||||||
|
|
||||||
|
type swissTable struct {
|
||||||
|
index int64
|
||||||
|
groups *Variable
|
||||||
|
}
|
||||||
|
|
||||||
|
type swissGroup struct {
|
||||||
|
slots *Variable
|
||||||
|
ctrls []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var errSwissTableCouldNotLoad = errors.New("could not load one of the tables")
|
||||||
|
var errSwissMapBadType = errors.New("swiss table type does not have some required fields")
|
||||||
|
var errSwissMapBadTableField = errors.New("swiss table bad table field")
|
||||||
|
var errSwissMapBadGroupTypeErr = errors.New("bad swiss map type, group type lacks some required fields")
|
||||||
|
|
||||||
|
// loadTypes determines the correct type for it.dirPtr: the linker records
|
||||||
|
// this type as **table but in reality it is either *[dirLen]*table for
|
||||||
|
// large maps or *group for small maps, when it.dirLen == 0.
|
||||||
|
func (it *mapIteratorSwiss) loadTypes() {
|
||||||
|
tableptrptrtyp, ok := it.dirPtr.DwarfType.(*godwarf.PtrType)
|
||||||
|
if !ok {
|
||||||
|
it.v.Unreadable = errSwissMapBadTableField
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tableptrtyp, ok := tableptrptrtyp.Type.(*godwarf.PtrType)
|
||||||
|
if !ok {
|
||||||
|
it.v.Unreadable = errSwissMapBadTableField
|
||||||
|
return
|
||||||
|
}
|
||||||
|
it.tableType, ok = tableptrtyp.Type.(*godwarf.StructType)
|
||||||
|
if !ok {
|
||||||
|
it.v.Unreadable = errSwissMapBadTableField
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, field := range it.tableType.Field {
|
||||||
|
switch field.Name {
|
||||||
|
case "index":
|
||||||
|
it.tableFieldIndex = field
|
||||||
|
case "groups":
|
||||||
|
it.tableFieldGroups = field
|
||||||
|
groupstyp, ok := field.Type.(*godwarf.StructType)
|
||||||
|
if ok {
|
||||||
|
for _, field := range groupstyp.Field {
|
||||||
|
switch field.Name {
|
||||||
|
case "data":
|
||||||
|
it.groupsFieldData = field
|
||||||
|
typ, ok := field.Type.(*godwarf.PtrType)
|
||||||
|
if ok {
|
||||||
|
it.groupType, _ = resolveTypedef(typ.Type).(*godwarf.StructType)
|
||||||
|
}
|
||||||
|
case "lengthMask":
|
||||||
|
it.groupsFieldLengthMask = field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if it.groupType == nil || it.tableFieldIndex == nil || it.tableFieldGroups == nil || it.groupsFieldLengthMask == nil {
|
||||||
|
it.v.Unreadable = errSwissMapBadType
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, field := range it.groupType.Field {
|
||||||
|
switch field.Name {
|
||||||
|
case "ctrl":
|
||||||
|
it.groupFieldCtrl = field
|
||||||
|
case "slots":
|
||||||
|
it.groupFieldSlots = field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if it.groupFieldCtrl == nil || it.groupFieldSlots == nil {
|
||||||
|
it.v.Unreadable = errSwissMapBadGroupTypeErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
slotsType, ok := resolveTypedef(it.groupFieldSlots.Type).(*godwarf.ArrayType)
|
||||||
|
if !ok {
|
||||||
|
it.v.Unreadable = errSwissMapBadGroupTypeErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slotType, ok := slotsType.Type.(*godwarf.StructType)
|
||||||
|
if !ok {
|
||||||
|
it.v.Unreadable = errSwissMapBadGroupTypeErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, field := range slotType.Field {
|
||||||
|
switch field.Name {
|
||||||
|
case "key":
|
||||||
|
it.slotFieldKey = field
|
||||||
|
case "elem":
|
||||||
|
it.slotFieldElem = field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if it.slotFieldKey == nil || it.slotFieldElem == nil {
|
||||||
|
it.v.Unreadable = errSwissMapBadGroupTypeErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if it.dirLen <= 0 {
|
||||||
|
// small maps, convert it.dirPtr to be of type *group, then dereference it
|
||||||
|
it.dirPtr.DwarfType = pointerTo(fakeArrayType(1, it.groupType), it.v.bi.Arch)
|
||||||
|
it.dirPtr.RealType = it.dirPtr.DwarfType
|
||||||
|
it.dirPtr = it.dirPtr.maybeDereference()
|
||||||
|
it.dirLen = 1
|
||||||
|
it.tab = &swissTable{groups: it.dirPtr} // so that we don't try to load this later on
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// normal map, convert it.dirPtr to be of type *[dirLen]*table, then dereference it
|
||||||
|
|
||||||
|
it.dirPtr.DwarfType = pointerTo(fakeArrayType(uint64(it.dirLen), tableptrtyp), it.v.bi.Arch)
|
||||||
|
it.dirPtr.RealType = it.dirPtr.DwarfType
|
||||||
|
it.dirPtr = it.dirPtr.maybeDereference()
|
||||||
|
}
|
||||||
|
|
||||||
|
// derived from $GOROOT/src/internal/runtime/maps/table.go and $GOROOT/src/runtime/runtime-gdb.py
|
||||||
|
func (it *mapIteratorSwiss) next() bool {
|
||||||
|
if it.v.Unreadable != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for it.dirIdx < it.dirLen {
|
||||||
|
if it.tab == nil {
|
||||||
|
it.loadCurrentTable()
|
||||||
|
if it.tab == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if it.tab.index != it.dirIdx {
|
||||||
|
it.nextTable()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ; it.groupIdx < uint64(it.tab.groups.Len); it.nextGroup() {
|
||||||
|
if it.maxNumGroups > 0 && it.groupIdx+it.groupCount >= it.maxNumGroups {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if it.group == nil {
|
||||||
|
it.loadCurrentGroup()
|
||||||
|
if it.group == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ; it.slotIdx < uint32(it.group.slots.Len); it.slotIdx++ {
|
||||||
|
if it.slotIsEmptyOrDeleted(it.slotIdx) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cur, err := it.group.slots.sliceAccess(int(it.slotIdx))
|
||||||
|
if err != nil {
|
||||||
|
it.v.Unreadable = fmt.Errorf("error accessing swiss map in table %d, group %d, slot %d", it.dirIdx, it.groupIdx, it.slotIdx)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var err1, err2 error
|
||||||
|
it.curKey, err1 = cur.toField(it.slotFieldKey)
|
||||||
|
it.curValue, err2 = cur.toField(it.slotFieldElem)
|
||||||
|
if err1 != nil || err2 != nil {
|
||||||
|
it.v.Unreadable = fmt.Errorf("error accessing swiss map slot: %v %v", err1, err2)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the type we expect is non-pointer but we read a pointer type it
|
||||||
|
// means that the key (or the value) is stored indirectly into the map
|
||||||
|
// because it is too big. We dereference it here so that the type of the
|
||||||
|
// key (or value) matches the type on the map definition.
|
||||||
|
if it.curKey.Kind == reflect.Ptr && !it.keyTypeIsPtr {
|
||||||
|
it.curKey = it.curKey.maybeDereference()
|
||||||
|
}
|
||||||
|
if it.curValue.Kind == reflect.Ptr && !it.elemTypeIsPtr {
|
||||||
|
it.curValue = it.curValue.maybeDereference()
|
||||||
|
}
|
||||||
|
|
||||||
|
it.slotIdx++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
it.slotIdx = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
it.groupCount += it.groupIdx
|
||||||
|
it.groupIdx = 0
|
||||||
|
it.group = nil
|
||||||
|
it.nextTable()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *mapIteratorSwiss) nextTable() {
|
||||||
|
it.dirIdx++
|
||||||
|
it.tab = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *mapIteratorSwiss) nextGroup() {
|
||||||
|
it.groupIdx++
|
||||||
|
it.group = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadCurrentTable loads the table at index it.dirIdx into it.tab
|
||||||
|
func (it *mapIteratorSwiss) loadCurrentTable() {
|
||||||
|
tab, err := it.dirPtr.sliceAccess(int(it.dirIdx))
|
||||||
|
if err != nil || tab == nil || tab.Unreadable != nil {
|
||||||
|
it.v.Unreadable = errSwissTableCouldNotLoad
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tab = tab.maybeDereference()
|
||||||
|
|
||||||
|
r := &swissTable{}
|
||||||
|
|
||||||
|
field, _ := tab.toField(it.tableFieldIndex)
|
||||||
|
r.index, err = field.asInt()
|
||||||
|
if err != nil {
|
||||||
|
it.v.Unreadable = fmt.Errorf("could not load swiss table index: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
groups, _ := tab.toField(it.tableFieldGroups)
|
||||||
|
r.groups, _ = groups.toField(it.groupsFieldData)
|
||||||
|
|
||||||
|
field, _ = groups.toField(it.groupsFieldLengthMask)
|
||||||
|
groupsLengthMask, err := field.asUint()
|
||||||
|
if err != nil {
|
||||||
|
it.v.Unreadable = fmt.Errorf("could not load swiss table group lengthMask: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert the type of groups from *group to *[len]group so that it's easier to use
|
||||||
|
r.groups.DwarfType = pointerTo(fakeArrayType(groupsLengthMask+1, it.groupType), it.v.bi.Arch)
|
||||||
|
r.groups.RealType = r.groups.DwarfType
|
||||||
|
r.groups = r.groups.maybeDereference()
|
||||||
|
|
||||||
|
it.tab = r
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadCurrentGroup loads the group at index it.groupIdx of it.tab into it.group
|
||||||
|
func (it *mapIteratorSwiss) loadCurrentGroup() {
|
||||||
|
group, err := it.tab.groups.sliceAccess(int(it.groupIdx))
|
||||||
|
if err != nil {
|
||||||
|
it.v.Unreadable = fmt.Errorf("could not load swiss map group: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g := &swissGroup{}
|
||||||
|
g.slots, _ = group.toField(it.groupFieldSlots)
|
||||||
|
ctrl, _ := group.toField(it.groupFieldCtrl)
|
||||||
|
g.ctrls = make([]byte, ctrl.DwarfType.Size())
|
||||||
|
_, err = ctrl.mem.ReadMemory(g.ctrls, ctrl.Addr)
|
||||||
|
if err != nil {
|
||||||
|
it.v.Unreadable = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
it.group = g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *mapIteratorSwiss) key() *Variable {
|
||||||
|
return it.curKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *mapIteratorSwiss) value() *Variable {
|
||||||
|
return it.curValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *mapIteratorSwiss) slotIsEmptyOrDeleted(k uint32) bool {
|
||||||
|
//TODO: check that this hasn't changed after it's merged and the TODO is deleted
|
||||||
|
return it.group.ctrls[k]&swissTableCtrlEmpty == swissTableCtrlEmpty
|
||||||
|
}
|
@ -1365,7 +1365,7 @@ func (v *Variable) loadValueInternal(recurseLevel int, cfg LoadConfig) {
|
|||||||
v.loadMap(recurseLevel, cfg)
|
v.loadMap(recurseLevel, cfg)
|
||||||
} else {
|
} else {
|
||||||
// loads length so that the client knows that the map isn't empty
|
// loads length so that the client knows that the map isn't empty
|
||||||
v.mapIterator()
|
v.mapIterator(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
@ -1992,11 +1992,10 @@ func (v *Variable) funcvalAddr() uint64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *Variable) loadMap(recurseLevel int, cfg LoadConfig) {
|
func (v *Variable) loadMap(recurseLevel int, cfg LoadConfig) {
|
||||||
it := v.mapIterator()
|
it := v.mapIterator(uint64(cfg.MaxMapBuckets))
|
||||||
if it == nil {
|
if it == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
it.maxNumBuckets = uint64(cfg.MaxMapBuckets)
|
|
||||||
|
|
||||||
if v.Len == 0 || int64(v.mapSkip) >= v.Len || cfg.MaxArrayValues == 0 {
|
if v.Len == 0 || int64(v.mapSkip) >= v.Len || cfg.MaxArrayValues == 0 {
|
||||||
return
|
return
|
||||||
@ -2013,12 +2012,7 @@ func (v *Variable) loadMap(recurseLevel int, cfg LoadConfig) {
|
|||||||
errcount := 0
|
errcount := 0
|
||||||
for it.next() {
|
for it.next() {
|
||||||
key := it.key()
|
key := it.key()
|
||||||
var val *Variable
|
val := it.value()
|
||||||
if it.values.fieldType.Size() > 0 {
|
|
||||||
val = it.value()
|
|
||||||
} else {
|
|
||||||
val = v.newVariable("", it.values.Addr, it.values.fieldType, DereferenceMemory(v.mem))
|
|
||||||
}
|
|
||||||
key.loadValueInternal(recurseLevel+1, cfg)
|
key.loadValueInternal(recurseLevel+1, cfg)
|
||||||
val.loadValueInternal(recurseLevel+1, cfg)
|
val.loadValueInternal(recurseLevel+1, cfg)
|
||||||
if key.Unreadable != nil || val.Unreadable != nil {
|
if key.Unreadable != nil || val.Unreadable != nil {
|
||||||
@ -2035,259 +2029,6 @@ func (v *Variable) loadMap(recurseLevel int, cfg LoadConfig) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type mapIterator struct {
|
|
||||||
v *Variable
|
|
||||||
numbuckets uint64
|
|
||||||
oldmask uint64
|
|
||||||
buckets *Variable
|
|
||||||
oldbuckets *Variable
|
|
||||||
b *Variable
|
|
||||||
bidx uint64
|
|
||||||
|
|
||||||
tophashes *Variable
|
|
||||||
keys *Variable
|
|
||||||
values *Variable
|
|
||||||
overflow *Variable
|
|
||||||
|
|
||||||
maxNumBuckets uint64 // maximum number of buckets to scan
|
|
||||||
|
|
||||||
idx int64
|
|
||||||
|
|
||||||
hashTophashEmptyOne uint64 // Go 1.12 and later has two sentinel tophash values for an empty cell, this is the second one (the first one hashTophashEmptyZero, the same as Go 1.11 and earlier)
|
|
||||||
hashMinTopHash uint64 // minimum value of tophash for a cell that isn't either evacuated or empty
|
|
||||||
}
|
|
||||||
|
|
||||||
// Code derived from go/src/runtime/hashmap.go
|
|
||||||
func (v *Variable) mapIterator() *mapIterator {
|
|
||||||
sv := v.clone()
|
|
||||||
sv.RealType = resolveTypedef(&(sv.RealType.(*godwarf.MapType).TypedefType))
|
|
||||||
sv = sv.maybeDereference()
|
|
||||||
v.Base = sv.Addr
|
|
||||||
|
|
||||||
maptype, ok := sv.RealType.(*godwarf.StructType)
|
|
||||||
if !ok {
|
|
||||||
v.Unreadable = errors.New("wrong real type for map")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
it := &mapIterator{v: v, bidx: 0, b: nil, idx: 0}
|
|
||||||
|
|
||||||
if sv.Addr == 0 {
|
|
||||||
it.numbuckets = 0
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
|
|
||||||
v.mem = cacheMemory(v.mem, v.Base, int(v.RealType.Size()))
|
|
||||||
|
|
||||||
for _, f := range maptype.Field {
|
|
||||||
var err error
|
|
||||||
field, _ := sv.toField(f)
|
|
||||||
switch f.Name {
|
|
||||||
case "count": // +rtype -fieldof hmap int
|
|
||||||
v.Len, err = field.asInt()
|
|
||||||
case "B": // +rtype -fieldof hmap uint8
|
|
||||||
var b uint64
|
|
||||||
b, err = field.asUint()
|
|
||||||
it.numbuckets = 1 << b
|
|
||||||
it.oldmask = (1 << (b - 1)) - 1
|
|
||||||
case "buckets": // +rtype -fieldof hmap unsafe.Pointer
|
|
||||||
it.buckets = field.maybeDereference()
|
|
||||||
case "oldbuckets": // +rtype -fieldof hmap unsafe.Pointer
|
|
||||||
it.oldbuckets = field.maybeDereference()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
v.Unreadable = err
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if it.buckets.Kind != reflect.Struct || it.oldbuckets.Kind != reflect.Struct {
|
|
||||||
v.Unreadable = errMapBucketsNotStruct
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
it.hashTophashEmptyOne = hashTophashEmptyZero
|
|
||||||
it.hashMinTopHash = hashMinTopHashGo111
|
|
||||||
if producer := v.bi.Producer(); producer != "" && goversion.ProducerAfterOrEqual(producer, 1, 12) {
|
|
||||||
it.hashTophashEmptyOne = hashTophashEmptyOne
|
|
||||||
it.hashMinTopHash = hashMinTopHashGo112
|
|
||||||
}
|
|
||||||
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
|
|
||||||
var errMapBucketContentsNotArray = errors.New("malformed map type: keys, values or tophash of a bucket is not an array")
|
|
||||||
var errMapBucketContentsInconsistentLen = errors.New("malformed map type: inconsistent array length in bucket")
|
|
||||||
var errMapBucketsNotStruct = errors.New("malformed map type: buckets, oldbuckets or overflow field not a struct")
|
|
||||||
|
|
||||||
func (it *mapIterator) nextBucket() bool {
|
|
||||||
if it.overflow != nil && it.overflow.Addr > 0 {
|
|
||||||
it.b = it.overflow
|
|
||||||
} else {
|
|
||||||
it.b = nil
|
|
||||||
|
|
||||||
if it.maxNumBuckets > 0 && it.bidx >= it.maxNumBuckets {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for it.bidx < it.numbuckets {
|
|
||||||
it.b = it.buckets.clone()
|
|
||||||
it.b.Addr += uint64(it.buckets.DwarfType.Size()) * it.bidx
|
|
||||||
|
|
||||||
if it.oldbuckets.Addr <= 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// if oldbuckets is not nil we are iterating through a map that is in
|
|
||||||
// the middle of a grow.
|
|
||||||
// if the bucket we are looking at hasn't been filled in we iterate
|
|
||||||
// instead through its corresponding "oldbucket" (i.e. the bucket the
|
|
||||||
// elements of this bucket are coming from) but only if this is the first
|
|
||||||
// of the two buckets being created from the same oldbucket (otherwise we
|
|
||||||
// would print some keys twice)
|
|
||||||
|
|
||||||
oldbidx := it.bidx & it.oldmask
|
|
||||||
oldb := it.oldbuckets.clone()
|
|
||||||
oldb.Addr += uint64(it.oldbuckets.DwarfType.Size()) * oldbidx
|
|
||||||
|
|
||||||
if it.mapEvacuated(oldb) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if oldbidx == it.bidx {
|
|
||||||
it.b = oldb
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// oldbucket origin for current bucket has not been evacuated but we have already
|
|
||||||
// iterated over it so we should just skip it
|
|
||||||
it.b = nil
|
|
||||||
it.bidx++
|
|
||||||
}
|
|
||||||
|
|
||||||
if it.b == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
it.bidx++
|
|
||||||
}
|
|
||||||
|
|
||||||
if it.b.Addr <= 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
it.b.mem = cacheMemory(it.b.mem, it.b.Addr, int(it.b.RealType.Size()))
|
|
||||||
|
|
||||||
it.tophashes = nil
|
|
||||||
it.keys = nil
|
|
||||||
it.values = nil
|
|
||||||
it.overflow = nil
|
|
||||||
|
|
||||||
for _, f := range it.b.DwarfType.(*godwarf.StructType).Field {
|
|
||||||
field, err := it.b.toField(f)
|
|
||||||
if err != nil {
|
|
||||||
it.v.Unreadable = err
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if field.Unreadable != nil {
|
|
||||||
it.v.Unreadable = field.Unreadable
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
switch f.Name {
|
|
||||||
case "tophash": // +rtype -fieldof bmap [8]uint8
|
|
||||||
it.tophashes = field
|
|
||||||
case "keys":
|
|
||||||
it.keys = field
|
|
||||||
case "values":
|
|
||||||
it.values = field
|
|
||||||
case "overflow":
|
|
||||||
it.overflow = field.maybeDereference()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sanity checks
|
|
||||||
if it.tophashes == nil || it.keys == nil || it.values == nil {
|
|
||||||
it.v.Unreadable = errors.New("malformed map type")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if it.tophashes.Kind != reflect.Array || it.keys.Kind != reflect.Array || it.values.Kind != reflect.Array {
|
|
||||||
it.v.Unreadable = errMapBucketContentsNotArray
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if it.tophashes.Len != it.keys.Len {
|
|
||||||
it.v.Unreadable = errMapBucketContentsInconsistentLen
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if it.values.fieldType.Size() > 0 && it.tophashes.Len != it.values.Len {
|
|
||||||
// if the type of the value is zero-sized (i.e. struct{}) then the values
|
|
||||||
// array's length is zero.
|
|
||||||
it.v.Unreadable = errMapBucketContentsInconsistentLen
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if it.overflow.Kind != reflect.Struct {
|
|
||||||
it.v.Unreadable = errMapBucketsNotStruct
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *mapIterator) next() bool {
|
|
||||||
for {
|
|
||||||
if it.b == nil || it.idx >= it.tophashes.Len {
|
|
||||||
r := it.nextBucket()
|
|
||||||
if !r {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
it.idx = 0
|
|
||||||
}
|
|
||||||
tophash, _ := it.tophashes.sliceAccess(int(it.idx))
|
|
||||||
h, err := tophash.asUint()
|
|
||||||
if err != nil {
|
|
||||||
it.v.Unreadable = fmt.Errorf("unreadable tophash: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
it.idx++
|
|
||||||
if h != hashTophashEmptyZero && h != it.hashTophashEmptyOne {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *mapIterator) key() *Variable {
|
|
||||||
k, _ := it.keys.sliceAccess(int(it.idx - 1))
|
|
||||||
return k
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *mapIterator) value() *Variable {
|
|
||||||
v, _ := it.values.sliceAccess(int(it.idx - 1))
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *mapIterator) mapEvacuated(b *Variable) bool {
|
|
||||||
if b.Addr == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, f := range b.DwarfType.(*godwarf.StructType).Field {
|
|
||||||
if f.Name != "tophash" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tophashes, _ := b.toField(f)
|
|
||||||
tophash0var, _ := tophashes.sliceAccess(0)
|
|
||||||
tophash0, err := tophash0var.asUint()
|
|
||||||
if err != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
//TODO: this needs to be > hashTophashEmptyOne for go >= 1.12
|
|
||||||
return tophash0 > it.hashTophashEmptyOne && tophash0 < it.hashMinTopHash
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Variable) readInterface() (_type, data *Variable, isnil bool) {
|
func (v *Variable) readInterface() (_type, data *Variable, isnil bool) {
|
||||||
// An interface variable is implemented either by a runtime.iface
|
// An interface variable is implemented either by a runtime.iface
|
||||||
// struct or a runtime.eface struct. The difference being that empty
|
// struct or a runtime.eface struct. The difference being that empty
|
||||||
|
@ -683,6 +683,7 @@ func getEvalExpressionTestCases() []varTest {
|
|||||||
{"m3[as1]", false, "42", "42", "int", nil},
|
{"m3[as1]", false, "42", "42", "int", nil},
|
||||||
{"mnil[\"Malone\"]", false, "", "", "", errors.New("key not found")},
|
{"mnil[\"Malone\"]", false, "", "", "", errors.New("key not found")},
|
||||||
{"m1[80:]", false, "", "", "", errors.New("map index out of bounds")},
|
{"m1[80:]", false, "", "", "", errors.New("map index out of bounds")},
|
||||||
|
{"mlarge", false, "map[main.largestruct]main.largestruct [{name: \"one\", v: [256]uint8 [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+192 more]}: {name: \"oneval\", v: [256]uint8 [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+192 more]}, ]", "map[main.largestruct]main.largestruct [...]", "map[main.largestruct]main.largestruct", nil},
|
||||||
|
|
||||||
// interfaces
|
// interfaces
|
||||||
{"err1", true, "error(*main.astruct) *{A: 1, B: 2}", "error(*main.astruct) 0x…", "error", nil},
|
{"err1", true, "error(*main.astruct) *{A: 1, B: 2}", "error(*main.astruct) 0x…", "error", nil},
|
||||||
@ -957,21 +958,15 @@ func getEvalExpressionTestCases() []varTest {
|
|||||||
{`*(*uint)(unsafe.Pointer(p1))`, false, `1`, `1`, "uint", nil},
|
{`*(*uint)(unsafe.Pointer(p1))`, false, `1`, `1`, "uint", nil},
|
||||||
{`*(*uint)(unsafe.Pointer(&i1))`, false, `1`, `1`, "uint", nil},
|
{`*(*uint)(unsafe.Pointer(&i1))`, false, `1`, `1`, "uint", nil},
|
||||||
|
|
||||||
// Conversions to ptr-to-ptr types
|
|
||||||
{`**(**runtime.hmap)(uintptr(&m1))`, false, `…`, `…`, "runtime.hmap", nil},
|
|
||||||
|
|
||||||
// Malformed values
|
// Malformed values
|
||||||
{`badslice`, false, `(unreadable non-zero length array with nil base)`, `(unreadable non-zero length array with nil base)`, "[]int", nil},
|
{`badslice`, false, `(unreadable non-zero length array with nil base)`, `(unreadable non-zero length array with nil base)`, "[]int", nil},
|
||||||
}
|
}
|
||||||
|
|
||||||
ver, _ := goversion.Parse(runtime.Version())
|
// Conversions to ptr-to-ptr types
|
||||||
if ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 7, Rev: -1}) {
|
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 24) {
|
||||||
for i := range testcases {
|
testcases = append(testcases, varTest{`**(**maps.Map)(uintptr(&m1))`, false, `…`, `…`, "internal/runtime/maps.Map", nil})
|
||||||
if testcases[i].name == "iface3" {
|
} else {
|
||||||
testcases[i].value = "interface {}(*map[string]go/constant.Value) *[]"
|
testcases = append(testcases, varTest{`**(**runtime.hmap)(uintptr(&m1))`, false, `…`, `…`, "runtime.hmap", nil})
|
||||||
testcases[i].alternate = "interface {}(*map[string]go/constant.Value) 0x…"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return testcases
|
return testcases
|
||||||
@ -1204,18 +1199,11 @@ func TestPackageRenames(t *testing.T) {
|
|||||||
|
|
||||||
{`"dir0/pkg".A`, false, "0", "", "int", nil},
|
{`"dir0/pkg".A`, false, "0", "", "int", nil},
|
||||||
{`"dir1/pkg".A`, false, "1", "", "int", nil},
|
{`"dir1/pkg".A`, false, "1", "", "int", nil},
|
||||||
}
|
|
||||||
|
|
||||||
testcases_i386 := []varTest{
|
|
||||||
{"amap", true, "interface {}(map[go/ast.BadExpr]net/http.Request) [{From: 2, To: 3}: {Method: \"othermethod\", …", "", "interface {}", nil},
|
{"amap", true, "interface {}(map[go/ast.BadExpr]net/http.Request) [{From: 2, To: 3}: {Method: \"othermethod\", …", "", "interface {}", nil},
|
||||||
{"amap2", true, "interface {}(*map[go/ast.BadExpr]net/http.Request) *[{From: 2, To: 3}: {Method: \"othermethod\", …", "", "interface {}", nil},
|
{"amap2", true, "interface {}(*map[go/ast.BadExpr]net/http.Request) *[{From: 2, To: 3}: {Method: \"othermethod\", …", "", "interface {}", nil},
|
||||||
}
|
}
|
||||||
|
|
||||||
testcases_64bit := []varTest{
|
|
||||||
{"amap", true, "interface {}(map[go/ast.BadExpr]net/http.Request) [{From: 2, To: 3}: *{Method: \"othermethod\", …", "", "interface {}", nil},
|
|
||||||
{"amap2", true, "interface {}(*map[go/ast.BadExpr]net/http.Request) *[{From: 2, To: 3}: *{Method: \"othermethod\", …", "", "interface {}", nil},
|
|
||||||
}
|
|
||||||
|
|
||||||
testcases1_9 := []varTest{
|
testcases1_9 := []varTest{
|
||||||
{"astruct2", true, `interface {}(*struct { github.com/go-delve/delve/_fixtures/internal/dir1/pkg.SomeType; X int }) *{SomeType: github.com/go-delve/delve/_fixtures/internal/dir1/pkg.SomeType {X: 1, Y: 2}, X: 10}`, "", "interface {}", nil},
|
{"astruct2", true, `interface {}(*struct { github.com/go-delve/delve/_fixtures/internal/dir1/pkg.SomeType; X int }) *{SomeType: github.com/go-delve/delve/_fixtures/internal/dir1/pkg.SomeType {X: 1, Y: 2}, X: 10}`, "", "interface {}", nil},
|
||||||
}
|
}
|
||||||
@ -1239,12 +1227,6 @@ func TestPackageRenames(t *testing.T) {
|
|||||||
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 13) {
|
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 13) {
|
||||||
testPackageRenamesHelper(t, p, testcases1_13)
|
testPackageRenamesHelper(t, p, testcases1_13)
|
||||||
}
|
}
|
||||||
|
|
||||||
if runtime.GOARCH == "386" && !goversion.VersionAfterOrEqual(runtime.Version(), 1, 22) {
|
|
||||||
testPackageRenamesHelper(t, p, testcases_i386)
|
|
||||||
} else {
|
|
||||||
testPackageRenamesHelper(t, p, testcases_64bit)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1908,3 +1890,74 @@ func TestSetupRangeFramesCrash(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClassicMap(t *testing.T) {
|
||||||
|
// This test replicates some of the tests in TestEvalExpression to check
|
||||||
|
// that we still support non-swiss maps on versions of Go where the default
|
||||||
|
// map backend is swisstables.
|
||||||
|
protest.AllowRecording(t)
|
||||||
|
|
||||||
|
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 24) {
|
||||||
|
t.Skip("N/A")
|
||||||
|
}
|
||||||
|
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 27) {
|
||||||
|
panic("test expired, please remove")
|
||||||
|
}
|
||||||
|
t.Setenv("GOEXPERIMENT", "noswissmap")
|
||||||
|
|
||||||
|
testcases := []varTest{
|
||||||
|
{"m1[\"Malone\"]", false, "main.astruct {A: 2, B: 3}", "main.astruct {A: 2, B: 3}", "main.astruct", nil},
|
||||||
|
{"m2[1].B", false, "11", "11", "int", nil},
|
||||||
|
{"m2[c1.sa[2].B-4].A", false, "10", "10", "int", nil},
|
||||||
|
{"m2[*p1].B", false, "11", "11", "int", nil},
|
||||||
|
{"m3[as1]", false, "42", "42", "int", nil},
|
||||||
|
{"mlarge", false, "map[main.largestruct]main.largestruct [{name: \"one\", v: [256]uint8 [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+192 more]}: {name: \"oneval\", v: [256]uint8 [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+192 more]}, ]", "map[main.largestruct]main.largestruct [...]", "map[main.largestruct]main.largestruct", nil},
|
||||||
|
{"mnil[\"Malone\"]", false, "", "", "", errors.New("key not found")},
|
||||||
|
{"m1[80:]", false, "", "", "", errors.New("map index out of bounds")},
|
||||||
|
{"mnil", true, "map[string]main.astruct nil", "map[string]main.astruct nil", "map[string]main.astruct", nil},
|
||||||
|
{"m1 == nil", false, "false", "false", "", nil},
|
||||||
|
{"mnil == m1", false, "", "", "", errors.New("can not compare map variables")},
|
||||||
|
{"mnil == nil", false, "true", "true", "", nil},
|
||||||
|
{"m2", true, "map[int]*main.astruct [1: *{A: 10, B: 11}, ]", "map[int]*main.astruct [...]", "map[int]*main.astruct", nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
|
assertNoError(grp.Continue(), t, "Continue() returned an error")
|
||||||
|
for _, tc := range testcases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Logf("%q", tc.name)
|
||||||
|
variable, err := evalVariableWithCfg(p, tc.name, pnormalLoadConfig)
|
||||||
|
if tc.err == nil {
|
||||||
|
assertNoError(err, t, fmt.Sprintf("EvalExpression(%s) returned an error", tc.name))
|
||||||
|
assertVariable(t, variable, tc)
|
||||||
|
variable, err := evalVariableWithCfg(p, tc.name, pshortLoadConfig)
|
||||||
|
assertNoError(err, t, fmt.Sprintf("EvalExpression(%s, pshortLoadConfig) returned an error", tc.name))
|
||||||
|
assertVariable(t, variable, tc.alternateVarTest())
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected error %s, got no error (%s)", tc.err.Error(), tc.name)
|
||||||
|
}
|
||||||
|
switch e := tc.err.(type) {
|
||||||
|
case *altError:
|
||||||
|
ok := false
|
||||||
|
for _, tgtErr := range e.errs {
|
||||||
|
if tgtErr == err.Error() {
|
||||||
|
ok = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if tc.err.Error() != "*" && tc.err.Error() != err.Error() {
|
||||||
|
t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user