mirror of
https://github.com/go-delve/delve.git
synced 2025-10-29 09:46:56 +08:00
proc/variables: map types support
Use m[n:] to skip the first n keys of a map Map indexing is implemented with a linear scan Implements #61, #122
This commit is contained in:
@ -45,11 +45,57 @@ func main() {
|
|||||||
var nilptr *int = nil
|
var nilptr *int = nil
|
||||||
ch1 := make(chan int, 2)
|
ch1 := make(chan int, 2)
|
||||||
var chnil chan int = nil
|
var chnil chan int = nil
|
||||||
|
m1 := map[string]astruct{
|
||||||
|
"Malone": astruct{2, 3},
|
||||||
|
"Adenauer": astruct{},
|
||||||
|
"squadrons": astruct{},
|
||||||
|
"quintuplets": astruct{},
|
||||||
|
"parasite": astruct{},
|
||||||
|
"wristwatches": astruct{},
|
||||||
|
"flashgun": astruct{},
|
||||||
|
"equivocally": astruct{},
|
||||||
|
"sweetbrier": astruct{},
|
||||||
|
"idealism": astruct{},
|
||||||
|
"tangos": astruct{},
|
||||||
|
"alterable": astruct{},
|
||||||
|
"quaffing": astruct{},
|
||||||
|
"arsenic": astruct{},
|
||||||
|
"coincidentally": astruct{},
|
||||||
|
"hindrances": astruct{},
|
||||||
|
"zoning": astruct{},
|
||||||
|
"egging": astruct{},
|
||||||
|
"inserts": astruct{},
|
||||||
|
"adaptive": astruct{},
|
||||||
|
"orientations": astruct{},
|
||||||
|
"periling": astruct{},
|
||||||
|
"lip": astruct{},
|
||||||
|
"chant": astruct{},
|
||||||
|
"availing": astruct{},
|
||||||
|
"fern": astruct{},
|
||||||
|
"flummoxes": astruct{},
|
||||||
|
"meanders": astruct{},
|
||||||
|
"ravenously": astruct{},
|
||||||
|
"reminisce": astruct{},
|
||||||
|
"snorkel": astruct{},
|
||||||
|
"gutters": astruct{},
|
||||||
|
"jibbed": astruct{},
|
||||||
|
"tiara": astruct{},
|
||||||
|
"takers": astruct{},
|
||||||
|
"animates": astruct{},
|
||||||
|
"Zubenelgenubi": astruct{},
|
||||||
|
"bantering": astruct{},
|
||||||
|
"tumblers": astruct{},
|
||||||
|
"horticulturists": astruct{},
|
||||||
|
"thallium": astruct{},
|
||||||
|
}
|
||||||
|
var mnil map[string]astruct = nil
|
||||||
|
m2 := map[int]*astruct{1: &astruct{10, 11}}
|
||||||
|
m3 := map[astruct]int{{1, 1}: 42, {2, 2}: 43}
|
||||||
|
|
||||||
var amb1 = 1
|
var amb1 = 1
|
||||||
runtime.Breakpoint()
|
runtime.Breakpoint()
|
||||||
for amb1 := 0; amb1 < 10; amb1++ {
|
for amb1 := 0; amb1 < 10; amb1++ {
|
||||||
fmt.Println(amb1)
|
fmt.Println(amb1)
|
||||||
}
|
}
|
||||||
fmt.Println(i1, i2, i3, p1, amb1, s1, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil)
|
fmt.Println(i1, i2, i3, p1, amb1, s1, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3)
|
||||||
}
|
}
|
||||||
|
|||||||
91
proc/eval.go
91
proc/eval.go
@ -250,9 +250,6 @@ func (scope *EvalScope) evalIndex(node *ast.IndexExpr) (*Variable, error) {
|
|||||||
if xev.Unreadable != nil {
|
if xev.Unreadable != nil {
|
||||||
return nil, xev.Unreadable
|
return nil, xev.Unreadable
|
||||||
}
|
}
|
||||||
if xev.base == 0 {
|
|
||||||
return nil, fmt.Errorf("can not index \"%s\"", exprToString(node.X))
|
|
||||||
}
|
|
||||||
|
|
||||||
idxev, err := scope.evalAST(node.Index)
|
idxev, err := scope.evalAST(node.Index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -261,6 +258,9 @@ func (scope *EvalScope) evalIndex(node *ast.IndexExpr) (*Variable, error) {
|
|||||||
|
|
||||||
switch xev.Kind {
|
switch xev.Kind {
|
||||||
case reflect.Slice, reflect.Array, reflect.String:
|
case reflect.Slice, reflect.Array, reflect.String:
|
||||||
|
if xev.base == 0 {
|
||||||
|
return nil, fmt.Errorf("can not index \"%s\"", exprToString(node.X))
|
||||||
|
}
|
||||||
n, err := idxev.asInt()
|
n, err := idxev.asInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -268,7 +268,11 @@ func (scope *EvalScope) evalIndex(node *ast.IndexExpr) (*Variable, error) {
|
|||||||
return xev.sliceAccess(int(n))
|
return xev.sliceAccess(int(n))
|
||||||
|
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
return nil, fmt.Errorf("map access not implemented")
|
idxev.loadValue()
|
||||||
|
if idxev.Unreadable != nil {
|
||||||
|
return nil, idxev.Unreadable
|
||||||
|
}
|
||||||
|
return xev.mapAccess(idxev)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid expression \"%s\" (type %s does not support indexing)", exprToString(node.X), xev.DwarfType.String())
|
return nil, fmt.Errorf("invalid expression \"%s\" (type %s does not support indexing)", exprToString(node.X), xev.DwarfType.String())
|
||||||
|
|
||||||
@ -285,16 +289,6 @@ func (scope *EvalScope) evalReslice(node *ast.SliceExpr) (*Variable, error) {
|
|||||||
if xev.Unreadable != nil {
|
if xev.Unreadable != nil {
|
||||||
return nil, xev.Unreadable
|
return nil, xev.Unreadable
|
||||||
}
|
}
|
||||||
if xev.base == 0 {
|
|
||||||
return nil, fmt.Errorf("can not slice \"%s\"", exprToString(node.X))
|
|
||||||
}
|
|
||||||
|
|
||||||
switch xev.Kind {
|
|
||||||
case reflect.Slice, reflect.Array, reflect.String:
|
|
||||||
//ok
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("cannot slice \"%s\" (type %s)", exprToString(node.X), xev.DwarfType.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
var low, high int64
|
var low, high int64
|
||||||
|
|
||||||
@ -322,8 +316,21 @@ func (scope *EvalScope) evalReslice(node *ast.SliceExpr) (*Variable, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := xev.reslice(low, high)
|
switch xev.Kind {
|
||||||
return r, err
|
case reflect.Slice, reflect.Array, reflect.String:
|
||||||
|
if xev.base == 0 {
|
||||||
|
return nil, fmt.Errorf("can not slice \"%s\"", exprToString(node.X))
|
||||||
|
}
|
||||||
|
return xev.reslice(low, high)
|
||||||
|
case reflect.Map:
|
||||||
|
if node.High != nil {
|
||||||
|
return nil, fmt.Errorf("second slice argument must be empty for maps")
|
||||||
|
}
|
||||||
|
xev.mapSkip += int(low)
|
||||||
|
return xev, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("can not slice \"%s\" (type %s)", exprToString(node.X), xev.DwarfType.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluates a pointer dereference expression: *<subexpr>
|
// Evaluates a pointer dereference expression: *<subexpr>
|
||||||
@ -654,6 +661,24 @@ func (v *Variable) asInt() (int64, error) {
|
|||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Variable) asUint() (uint64, error) {
|
||||||
|
if v.DwarfType == nil {
|
||||||
|
if v.Value.Kind() != constant.Int {
|
||||||
|
return 0, fmt.Errorf("can not convert constant %s to uint", v.Value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v.loadValue()
|
||||||
|
if v.Unreadable != nil {
|
||||||
|
return 0, v.Unreadable
|
||||||
|
}
|
||||||
|
if _, ok := v.DwarfType.(*dwarf.UintType); !ok {
|
||||||
|
return 0, fmt.Errorf("can not convert value of type %s to uint", v.DwarfType.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n, _ := constant.Uint64Val(v.Value)
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (v *Variable) isType(typ dwarf.Type, kind reflect.Kind) error {
|
func (v *Variable) isType(typ dwarf.Type, kind reflect.Kind) error {
|
||||||
if v.DwarfType != nil {
|
if v.DwarfType != nil {
|
||||||
if typ != nil && typ.String() != v.RealType.String() {
|
if typ != nil && typ.String() != v.RealType.String() {
|
||||||
@ -723,6 +748,40 @@ func (v *Variable) sliceAccess(idx int) (*Variable, error) {
|
|||||||
return newVariable("", v.base+uintptr(int64(idx)*v.stride), v.fieldType, v.thread), nil
|
return newVariable("", v.base+uintptr(int64(idx)*v.stride), v.fieldType, v.thread), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Variable) mapAccess(idx *Variable) (*Variable, error) {
|
||||||
|
it := v.mapIterator()
|
||||||
|
if it == nil {
|
||||||
|
return nil, fmt.Errorf("can not access unreadable map: %v", v.Unreadable)
|
||||||
|
}
|
||||||
|
|
||||||
|
first := true
|
||||||
|
for it.next() {
|
||||||
|
key := it.key()
|
||||||
|
key.loadValue()
|
||||||
|
if key.Unreadable != nil {
|
||||||
|
return nil, fmt.Errorf("can not access unreadable map: %v", key.Unreadable)
|
||||||
|
}
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
if err := idx.isType(key.DwarfType, key.Kind); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eql, err := compareOp(token.EQL, key, idx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if eql {
|
||||||
|
return it.value(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v.Unreadable != nil {
|
||||||
|
return nil, v.Unreadable
|
||||||
|
}
|
||||||
|
// go would return zero for the map value type here, we do not have the ability to create zeroes
|
||||||
|
return nil, fmt.Errorf("key not found")
|
||||||
|
}
|
||||||
|
|
||||||
func (v *Variable) reslice(low int64, high int64) (*Variable, error) {
|
func (v *Variable) reslice(low int64, high int64) (*Variable, error) {
|
||||||
if low < 0 || low >= v.Len || high < 0 || high > v.Len {
|
if low < 0 || low >= v.Len || high < 0 || high > v.Len {
|
||||||
return nil, fmt.Errorf("index out of bounds")
|
return nil, fmt.Errorf("index out of bounds")
|
||||||
|
|||||||
@ -23,6 +23,9 @@ const (
|
|||||||
|
|
||||||
ChanRecv = "chan receive"
|
ChanRecv = "chan receive"
|
||||||
ChanSend = "chan send"
|
ChanSend = "chan send"
|
||||||
|
|
||||||
|
hashTophashEmpty = 0 // used by map reading code, indicates an empty bucket
|
||||||
|
hashMinTopHash = 4 // used by map reading code, indicates minimum value of tophash that isn't empty or evacuated
|
||||||
)
|
)
|
||||||
|
|
||||||
// Represents a variable.
|
// Represents a variable.
|
||||||
@ -42,12 +45,15 @@ type Variable struct {
|
|||||||
|
|
||||||
// base address of arrays, base address of the backing array for slices (0 for nil slices)
|
// base address of arrays, base address of the backing array for slices (0 for nil slices)
|
||||||
// base address of the backing byte array for strings
|
// base address of the backing byte array for strings
|
||||||
// address of the struct backing a chan variable
|
// address of the struct backing chan and map variables
|
||||||
// address of the function entry point for function variables (0 for nil function pointers)
|
// address of the function entry point for function variables (0 for nil function pointers)
|
||||||
base uintptr
|
base uintptr
|
||||||
stride int64
|
stride int64
|
||||||
fieldType dwarf.Type
|
fieldType dwarf.Type
|
||||||
|
|
||||||
|
// number of elements to skip when loading a map
|
||||||
|
mapSkip int
|
||||||
|
|
||||||
Children []Variable
|
Children []Variable
|
||||||
|
|
||||||
loaded bool
|
loaded bool
|
||||||
@ -126,6 +132,8 @@ func newVariable(name string, addr uintptr, dwarfType dwarf.Type, thread *Thread
|
|||||||
structtyp, isstruct := t.Type.(*dwarf.StructType)
|
structtyp, isstruct := t.Type.(*dwarf.StructType)
|
||||||
if isstruct && strings.HasPrefix(structtyp.StructName, "hchan<") {
|
if isstruct && strings.HasPrefix(structtyp.StructName, "hchan<") {
|
||||||
v.Kind = reflect.Chan
|
v.Kind = reflect.Chan
|
||||||
|
} else if isstruct && strings.HasPrefix(structtyp.StructName, "hash<") {
|
||||||
|
v.Kind = reflect.Map
|
||||||
} else {
|
} else {
|
||||||
v.Kind = reflect.Ptr
|
v.Kind = reflect.Ptr
|
||||||
}
|
}
|
||||||
@ -716,6 +724,9 @@ func (v *Variable) loadValueInternal(recurseLevel int) {
|
|||||||
v.Len = sv.Len
|
v.Len = sv.Len
|
||||||
v.base = sv.Addr
|
v.base = sv.Addr
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
v.loadMap(recurseLevel)
|
||||||
|
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
var val string
|
var val string
|
||||||
val, v.Unreadable = v.thread.readStringValue(v.base, v.Len)
|
val, v.Unreadable = v.thread.readStringValue(v.base, v.Len)
|
||||||
@ -762,8 +773,6 @@ func (v *Variable) loadValueInternal(recurseLevel int) {
|
|||||||
v.Value = constant.MakeFloat64(val)
|
v.Value = constant.MakeFloat64(val)
|
||||||
case reflect.Func:
|
case reflect.Func:
|
||||||
v.readFunctionPtr()
|
v.readFunctionPtr()
|
||||||
case reflect.Map:
|
|
||||||
fallthrough
|
|
||||||
default:
|
default:
|
||||||
v.Unreadable = fmt.Errorf("unknown or unsupported kind: \"%s\"", v.Kind.String())
|
v.Unreadable = fmt.Errorf("unknown or unsupported kind: \"%s\"", v.Kind.String())
|
||||||
}
|
}
|
||||||
@ -1093,6 +1102,249 @@ func (v *Variable) readFunctionPtr() {
|
|||||||
v.Value = constant.MakeString(fn.Name)
|
v.Value = constant.MakeString(fn.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Variable) loadMap(recurseLevel int) {
|
||||||
|
it := v.mapIterator()
|
||||||
|
if it == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for skip := 0; skip < v.mapSkip; skip++ {
|
||||||
|
if ok := it.next(); !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
errcount := 0
|
||||||
|
for it.next() {
|
||||||
|
if count >= maxArrayValues {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
key := it.key()
|
||||||
|
val := it.value()
|
||||||
|
key.loadValue()
|
||||||
|
val.loadValue()
|
||||||
|
if key.Unreadable != nil || val.Unreadable != nil {
|
||||||
|
errcount++
|
||||||
|
}
|
||||||
|
v.Children = append(v.Children, *key)
|
||||||
|
v.Children = append(v.Children, *val)
|
||||||
|
count++
|
||||||
|
if errcount > maxErrCount {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
idx int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code derived from go/src/runtime/hashmap.go
|
||||||
|
func (v *Variable) mapIterator() *mapIterator {
|
||||||
|
sv := v.maybeDereference()
|
||||||
|
v.base = sv.Addr
|
||||||
|
|
||||||
|
maptype, ok := sv.RealType.(*dwarf.StructType)
|
||||||
|
if !ok {
|
||||||
|
v.Unreadable = fmt.Errorf("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
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range maptype.Field {
|
||||||
|
var err error
|
||||||
|
field, _ := sv.toField(f)
|
||||||
|
switch f.Name {
|
||||||
|
case "count":
|
||||||
|
v.Len, err = field.asInt()
|
||||||
|
case "B":
|
||||||
|
var b uint64
|
||||||
|
b, err = field.asUint()
|
||||||
|
it.numbuckets = 1 << b
|
||||||
|
it.oldmask = (1 << (b - 1)) - 1
|
||||||
|
case "buckets":
|
||||||
|
it.buckets = field.maybeDereference()
|
||||||
|
case "oldbuckets":
|
||||||
|
it.oldbuckets = field.maybeDereference()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
v.Unreadable = err
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *mapIterator) nextBucket() bool {
|
||||||
|
if it.overflow != nil && it.overflow.Addr > 0 {
|
||||||
|
it.b = it.overflow
|
||||||
|
} else {
|
||||||
|
it.b = nil
|
||||||
|
|
||||||
|
for it.bidx < it.numbuckets {
|
||||||
|
it.b = it.buckets.clone()
|
||||||
|
it.b.Addr += uintptr(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 += uintptr(uint64(it.oldbuckets.DwarfType.Size()) * oldbidx)
|
||||||
|
|
||||||
|
if 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.tophashes = nil
|
||||||
|
it.keys = nil
|
||||||
|
it.values = nil
|
||||||
|
it.overflow = nil
|
||||||
|
|
||||||
|
for _, f := range it.b.DwarfType.(*dwarf.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":
|
||||||
|
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 = fmt.Errorf("malformed map type")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if it.tophashes.Kind != reflect.Array || it.keys.Kind != reflect.Array || it.values.Kind != reflect.Array {
|
||||||
|
it.v.Unreadable = fmt.Errorf("malformed map type: keys, values or tophash of a bucket is not an array")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if it.tophashes.Len != it.keys.Len || it.tophashes.Len != it.values.Len {
|
||||||
|
it.v.Unreadable = fmt.Errorf("malformed map type: inconsistent array length in bucket")
|
||||||
|
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 != hashTophashEmpty {
|
||||||
|
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 mapEvacuated(b *Variable) bool {
|
||||||
|
if b.Addr == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, f := range b.DwarfType.(*dwarf.StructType).Field {
|
||||||
|
if f.Name != "tophash" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tophashes, _ := b.toField(f)
|
||||||
|
tophash0var, _ := tophashes.sliceAccess(0)
|
||||||
|
tophash0, err := tophash0var.asUint()
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return tophash0 > hashTophashEmpty && tophash0 < hashMinTopHash
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Fetches all variables of a specific type in the current function scope
|
// Fetches all variables of a specific type in the current function scope
|
||||||
func (scope *EvalScope) variablesByTag(tag dwarf.Tag) ([]*Variable, error) {
|
func (scope *EvalScope) variablesByTag(tag dwarf.Tag) ([]*Variable, error) {
|
||||||
reader := scope.DwarfReader()
|
reader := scope.DwarfReader()
|
||||||
|
|||||||
@ -163,13 +163,13 @@ func (v *Variable) writeMapTo(buf *bytes.Buffer, newlines, includeType bool, ind
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(v.Children) != int(v.Len) {
|
if len(v.Children)/2 != int(v.Len) {
|
||||||
if nl {
|
if nl {
|
||||||
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
|
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(buf, ",")
|
fmt.Fprintf(buf, ",")
|
||||||
}
|
}
|
||||||
fmt.Fprintf(buf, "...+%d more", int(v.Len)-len(v.Children))
|
fmt.Fprintf(buf, "...+%d more", int(v.Len)-(len(v.Children)/2))
|
||||||
}
|
}
|
||||||
|
|
||||||
if nl {
|
if nl {
|
||||||
|
|||||||
@ -389,6 +389,14 @@ func TestEvalExpression(t *testing.T) {
|
|||||||
{"ch1", true, "chan int 0/2", "", "chan int", nil},
|
{"ch1", true, "chan int 0/2", "", "chan int", nil},
|
||||||
{"ch1+1", false, "", "", "", fmt.Errorf("can not convert 1 constant to chan int")},
|
{"ch1+1", false, "", "", "", fmt.Errorf("can not convert 1 constant to chan int")},
|
||||||
|
|
||||||
|
// maps
|
||||||
|
{"m1[\"Malone\"]", false, "struct main.astruct {A: 2, B: 3}", "", "struct main.astruct", nil},
|
||||||
|
{"m2[1].B", false, "11", "", "int", nil},
|
||||||
|
{"m2[c1.sa[2].B-4].A", false, "10", "", "int", nil},
|
||||||
|
{"m2[*p1].B", false, "11", "", "int", nil},
|
||||||
|
{"m3[as1]", false, "42", "", "int", nil},
|
||||||
|
{"mnil[\"Malone\"]", false, "", "", "", fmt.Errorf("key not found")},
|
||||||
|
|
||||||
// combined expressions
|
// combined expressions
|
||||||
{"c1.pb.a.A", true, "1", "", "int", nil},
|
{"c1.pb.a.A", true, "1", "", "int", nil},
|
||||||
{"c1.sa[1].B", false, "3", "", "int", nil},
|
{"c1.sa[1].B", false, "3", "", "int", nil},
|
||||||
@ -462,6 +470,9 @@ func TestEvalExpression(t *testing.T) {
|
|||||||
{"ch1 == nil", false, "false", "", "", nil},
|
{"ch1 == nil", false, "false", "", "", nil},
|
||||||
{"chnil == nil", false, "true", "", "", nil},
|
{"chnil == nil", false, "true", "", "", nil},
|
||||||
{"ch1 == chnil", false, "", "", "", fmt.Errorf("can not compare chan variables")},
|
{"ch1 == chnil", false, "", "", "", fmt.Errorf("can not compare chan variables")},
|
||||||
|
{"m1 == nil", false, "false", "", "", nil},
|
||||||
|
{"mnil == m1", false, "", "", "", fmt.Errorf("can not compare map variables")},
|
||||||
|
{"mnil == nil", false, "true", "", "", nil},
|
||||||
|
|
||||||
// errors
|
// errors
|
||||||
{"&3", false, "", "", "", fmt.Errorf("can not take address of \"3\"")},
|
{"&3", false, "", "", "", fmt.Errorf("can not take address of \"3\"")},
|
||||||
@ -521,3 +532,38 @@ func TestEvalAddrAndCast(t *testing.T) {
|
|||||||
assertVariable(t, a, varTest{aaddrstr, false, "struct main.astruct {A: 1, B: 2}", "", "struct main.astruct", nil})
|
assertVariable(t, a, varTest{aaddrstr, false, "struct main.astruct {A: 1, B: 2}", "", "struct main.astruct", nil})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMapEvaluation(t *testing.T) {
|
||||||
|
withTestProcess("testvariables3", t, func(p *proc.Process, fixture protest.Fixture) {
|
||||||
|
assertNoError(p.Continue(), t, "Continue() returned an error")
|
||||||
|
m1v, err := evalVariable(p, "m1")
|
||||||
|
assertNoError(err, t, "EvalVariable()")
|
||||||
|
m1 := api.ConvertVar(m1v)
|
||||||
|
t.Logf("m1 = %v", m1.MultilineString(""))
|
||||||
|
|
||||||
|
if m1.Type != "map[string]main.astruct" {
|
||||||
|
t.Fatalf("Wrong type: %s", m1.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m1.Children)/2 != 41 {
|
||||||
|
t.Fatalf("Wrong number of children: %d", len(m1.Children)/2)
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for i := range m1.Children {
|
||||||
|
if i%2 == 0 && m1.Children[i].Value == "Malone" {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("Could not find Malone")
|
||||||
|
}
|
||||||
|
|
||||||
|
m1sliced, err := evalVariable(p, "m1[10:]")
|
||||||
|
assertNoError(err, t, "EvalVariable(m1[10:])")
|
||||||
|
if len(m1sliced.Children)/2 != int(m1.Len-10) {
|
||||||
|
t.Fatalf("Wrong number of children (after slicing): %d", len(m1sliced.Children)/2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user