diff --git a/_fixtures/fncall.go b/_fixtures/fncall.go index 4abf3ee610a442108aeb79a7768722ceba8833b5..44298608fd318abd03d06054934171a62d145dfb 100644 --- a/_fixtures/fncall.go +++ b/_fixtures/fncall.go @@ -35,6 +35,26 @@ func stringsJoin(v []string, sep string) string { return strings.Join(v, sep) } +type astruct struct { + X int +} + +func (a astruct) VRcvr(x int) string { + return fmt.Sprintf("%d + %d = %d", x, a.X, x+a.X) +} + +func (pa *astruct) PRcvr(x int) string { + return fmt.Sprintf("%d - %d = %d", x, pa.X, x-pa.X) +} + +type PRcvrable interface { + PRcvr(int) string +} + +type VRcvrable interface { + VRcvr(int) string +} + var zero = 0 func main() { @@ -42,7 +62,14 @@ func main() { intslice := []int{1, 2, 3} stringslice := []string{"one", "two", "three"} comma := "," + a := astruct{X: 3} + pa := &astruct{X: 6} + + var vable_a VRcvrable = a + var vable_pa VRcvrable = pa + var pable_pa PRcvrable = pa + runtime.Breakpoint() call1(one, two) - fmt.Println(one, two, zero, callpanic, callstacktrace, stringsJoin, intslice, stringslice, comma) + fmt.Println(one, two, zero, callpanic, callstacktrace, stringsJoin, intslice, stringslice, comma, a.VRcvr, a.PRcvr, pa, vable_a, vable_pa, pable_pa) } diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go index a728b286486edb0d02a7d51ec7f9002f50b49e52..87a787864082cf07996a5cb4034d47d577b36ffa 100644 --- a/pkg/proc/fncall.go +++ b/pkg/proc/fncall.go @@ -207,7 +207,6 @@ func funcCallEvalExpr(p Process, expr string) (fn *Function, argvars []*Variable return nil, nil, ErrNotACallExpr } - //TODO(aarzilli): must evaluate . and treat them appropriately fnvar, err := scope.evalAST(callexpr.Fun) if err != nil { return nil, nil, err @@ -223,13 +222,18 @@ func funcCallEvalExpr(p Process, expr string) (fn *Function, argvars []*Variable return nil, nil, ErrNotAGoFunction } - argvars = make([]*Variable, len(callexpr.Args)) + argvars = make([]*Variable, 0, len(callexpr.Args)+1) + if len(fnvar.Children) > 0 { + // receiver argument + argvars = append(argvars, &fnvar.Children[0]) + } for i := range callexpr.Args { - argvars[i], err = scope.evalAST(callexpr.Args[i]) + argvar, err := scope.evalAST(callexpr.Args[i]) if err != nil { return nil, nil, err } - argvars[i].Name = exprToString(callexpr.Args[i]) + argvar.Name = exprToString(callexpr.Args[i]) + argvars = append(argvars, argvar) } return fn, argvars, nil diff --git a/pkg/proc/types.go b/pkg/proc/types.go index cf28319a7cc9586b8b68fba2eb3edc241eaf9dea..eb24d069af08ea8d94d8f059fef0eae5a88c02ae 100644 --- a/pkg/proc/types.go +++ b/pkg/proc/types.go @@ -192,6 +192,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(debugLineBytes []byte, wg *sync.WaitGrou bi.consts = make(map[dwarf.Offset]*constantType) bi.runtimeTypeToDIE = make(map[uint64]runtimeTypeDIE) reader := bi.DwarfReader() + ardr := bi.DwarfReader() var cu *compileUnit = nil var pu *partialUnit = nil var partialUnits = make(map[dwarf.Offset]*partialUnit) @@ -322,9 +323,7 @@ outer: } } } - if off, ok := entry.Val(godwarf.AttrGoRuntimeType).(uint64); ok { - bi.runtimeTypeToDIE[off] = runtimeTypeDIE{entry.Offset, -1} - } + bi.registerRuntimeTypeToDIE(entry, ardr) reader.SkipChildren() case dwarf.TagVariable: @@ -516,6 +515,37 @@ func (bi *BinaryInfo) expandPackagesInType(expr ast.Expr) { } } +func (bi *BinaryInfo) registerRuntimeTypeToDIE(entry *dwarf.Entry, ardr *reader.Reader) { + if off, ok := entry.Val(godwarf.AttrGoRuntimeType).(uint64); ok { + if _, ok := bi.runtimeTypeToDIE[off]; !ok { + bi.runtimeTypeToDIE[off] = runtimeTypeDIE{entry.Offset, -1} + } + return + } + + if entry.Tag != dwarf.TagTypedef { + return + } + + // For named structs the compiler will emit a TagStructType entry and a + // TagTypedef entry. The AttrGoRuntimeType is set on the TagStructType + // entry but we prefer to use the typedef instead, to make interface + // values consistent with other variables. + + rtypOff, ok := entry.Val(dwarf.AttrType).(dwarf.Offset) + if !ok { + return + } + ardr.Seek(rtypOff) + rentry, _ := ardr.Next() + if rentry == nil { + return + } + if off, ok := rentry.Val(godwarf.AttrGoRuntimeType).(uint64); ok { + bi.runtimeTypeToDIE[off] = runtimeTypeDIE{entry.Offset, -1} + } +} + // runtimeTypeToDIE returns the DIE corresponding to the runtime._type. // This is done in three different ways depending on the version of go. // * Before go1.7 the type name is retrieved directly from the runtime._type diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index 4b98620a2e22132d112f457d3e7f316e79d17b85..fcd2dce1a9a05b151a20bf3b61e88b44537b8ed2 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -733,6 +733,7 @@ func (v *Variable) structMember(memberName string) (*Variable, error) { if v.Unreadable != nil { return v.clone(), nil } + vname := v.Name switch v.Kind { case reflect.Chan: v = v.clone() @@ -789,12 +790,12 @@ func (v *Variable) structMember(memberName string) (*Variable, error) { return embeddedField, nil } } - return nil, fmt.Errorf("%s has no member %s", v.Name, memberName) + return nil, fmt.Errorf("%s has no member %s", vname, memberName) default: if v.Name == "" { return nil, fmt.Errorf("type %s is not a struct", structVar.TypeString()) } - return nil, fmt.Errorf("%s (type %s) is not a struct", v.Name, structVar.TypeString()) + return nil, fmt.Errorf("%s (type %s) is not a struct", vname, structVar.TypeString()) } } diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 57861a5094056226dc429511c3dce5d1e121ec59..19e5e1fad23ce720a52d13e156ca78cfbc9a3578 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -1087,6 +1087,28 @@ func TestCallFunction(t *testing.T) { {`stringsJoin(stringslice, comma)`, []string{`:string:"one,two,three"`}, nil}, {`stringsJoin(s1, comma)`, nil, errors.New("could not find symbol value for s1")}, {`stringsJoin(intslice, comma)`, nil, errors.New("can not convert value of type []int to []string")}, + + // The following set of calls was constructed using https://docs.google.com/document/d/1bMwCey-gmqZVTpRax-ESeVuZGmjwbocYs1iHplK-cjo/pub as a reference + + {`a.VRcvr(1)`, []string{`:string:"1 + 3 = 4"`}, nil}, // direct call of a method with value receiver / on a value + {`a.PRcvr(2)`, []string{`:string:"2 - 3 = -1"`}, nil}, // direct call of a method with pointer receiver / on a value + {`pa.VRcvr(3)`, []string{`:string:"3 + 6 = 9"`}, nil}, // direct call of a method with value receiver / on a pointer + {`pa.PRcvr(4)`, []string{`:string:"4 - 6 = -2"`}, nil}, // direct call of a method with pointer receiver / on a pointer + + {`vable_pa.VRcvr(6)`, []string{`:string:"6 + 6 = 12"`}, nil}, // indirect call of method on interface / containing value with value method + {`pable_pa.PRcvr(7)`, []string{`:string:"7 - 6 = 1"`}, nil}, // indirect call of method on interface / containing pointer with value method + {`vable_a.VRcvr(5)`, []string{`:string:"5 + 3 = 8"`}, nil}, // indirect call of method on interface / containing pointer with pointer method + + {`pa.nonexistent()`, nil, errors.New("pa has no member nonexistent")}, + {`a.nonexistent()`, nil, errors.New("a has no member nonexistent")}, + {`vable_pa.nonexistent()`, nil, errors.New("vable_pa has no member nonexistent")}, + {`vable_a.nonexistent()`, nil, errors.New("vable_a has no member nonexistent")}, + {`pable_pa.nonexistent()`, nil, errors.New("pable_pa has no member nonexistent")}, + + //TODO(aarzilli): indirect call of func value / set to top-level func + //TODO(aarzilli): indirect call of func value / set to func literal + //TODO(aarzilli): indirect call of func value / set to value method + //TODO(aarzilli): indirect call of func value / set to pointer method } withTestProcess("fncall", t, func(p proc.Process, fixture protest.Fixture) {