diff --git a/_fixtures/testvariables2.go b/_fixtures/testvariables2.go index e5ae7daf..e46f7c6e 100644 --- a/_fixtures/testvariables2.go +++ b/_fixtures/testvariables2.go @@ -62,6 +62,10 @@ func (a *astruct) Error() string { return "not an error" } +func (a astruct) NonPointerRecieverMethod() { + return +} + func (b *bstruct) Error() string { return "not an error" } @@ -246,6 +250,8 @@ func main() { var nilstruct *astruct = nil + var as2 astruct + s4 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0} var amb1 = 1 @@ -255,5 +261,5 @@ func main() { } runtime.Breakpoint() - fmt.Println(i1, i2, i3, p1, amb1, s1, s3, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, 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, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, errtypednil, emptyslice, emptymap, byteslice, runeslice, longstr, nilstruct, s4) + fmt.Println(i1, i2, i3, p1, amb1, s1, s3, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, 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, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, errtypednil, emptyslice, emptymap, byteslice, runeslice, longstr, nilstruct, as2, as2.NonPointerRecieverMethod, s4) } diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index 059457c0..be0216a6 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -12,9 +12,11 @@ import ( "go/token" "reflect" "strconv" + "strings" "github.com/derekparker/delve/pkg/dwarf/godwarf" "github.com/derekparker/delve/pkg/dwarf/reader" + "github.com/derekparker/delve/pkg/goversion" ) var OperationOnSpecialFloatError = errors.New("operations on non-finite floats not implemented") @@ -599,6 +601,13 @@ func (scope *EvalScope) evalStructSelector(node *ast.SelectorExpr) (*Variable, e if err != nil { return nil, err } + rv, err := xv.findMethod(node.Sel.Name) + if err != nil { + return nil, err + } + if rv != nil { + return rv, nil + } return xv.structMember(node.Sel.Name) } @@ -782,14 +791,18 @@ func (scope *EvalScope) evalAddrOf(node *ast.UnaryExpr) (*Variable, error) { return nil, fmt.Errorf("can not take address of \"%s\"", exprToString(node.X)) } - xev.OnlyAddr = true + return xev.pointerToVariable(), nil +} - typename := "*" + xev.DwarfType.Common().Name - rv := scope.newVariable("", 0, &godwarf.PtrType{CommonType: godwarf.CommonType{ByteSize: int64(scope.BinInfo.Arch.PtrSize()), Name: typename}, Type: xev.DwarfType}, scope.Mem) - rv.Children = []Variable{*xev} +func (v *Variable) pointerToVariable() *Variable { + v.OnlyAddr = true + + typename := "*" + v.DwarfType.Common().Name + rv := v.newVariable("", 0, &godwarf.PtrType{CommonType: godwarf.CommonType{ByteSize: int64(v.bi.Arch.PtrSize()), Name: typename}, Type: v.DwarfType}, v.mem) + rv.Children = []Variable{*v} rv.loaded = true - return rv, nil + return rv } func constantUnaryOp(op token.Token, y constant.Value) (r constant.Value, err error) { @@ -1304,6 +1317,77 @@ func (v *Variable) reslice(low int64, high int64) (*Variable, error) { return r, nil } +// findMethod finds method mname in the type of variable v +func (v *Variable) findMethod(mname string) (*Variable, error) { + if _, isiface := v.RealType.(*godwarf.InterfaceType); isiface { + v.loadInterface(0, false, loadFullValue) + if v.Unreadable != nil { + return nil, v.Unreadable + } + return v.Children[0].findMethod(mname) + } + + typ := v.DwarfType + ptyp, isptr := typ.(*godwarf.PtrType) + if isptr { + typ = ptyp.Type + } + + if _, istypedef := typ.(*godwarf.TypedefType); !istypedef { + return nil, nil + } + + typePath := typ.Common().Name + dot := strings.LastIndex(typePath, ".") + if dot < 0 { + // probably just a C type + return nil, nil + } + + pkg := typePath[:dot] + receiver := typePath[dot+1:] + + if fn, ok := v.bi.LookupFunc[fmt.Sprintf("%s.%s.%s", pkg, receiver, mname)]; ok { + r, err := functionToVariable(fn, v.bi, v.mem) + if err != nil { + return nil, err + } + if isptr { + r.Children = append(r.Children, *(v.maybeDereference())) + } else { + r.Children = append(r.Children, *v) + } + return r, nil + } + + if fn, ok := v.bi.LookupFunc[fmt.Sprintf("%s.(*%s).%s", pkg, receiver, mname)]; ok { + r, err := functionToVariable(fn, v.bi, v.mem) + if err != nil { + return nil, err + } + if isptr { + r.Children = append(r.Children, *v) + } else { + r.Children = append(r.Children, *(v.pointerToVariable())) + } + return r, nil + } + + return nil, nil +} + +func functionToVariable(fn *Function, bi *BinaryInfo, mem MemoryReadWriter) (*Variable, error) { + typ, err := fn.fakeType(bi, true) + if err != nil { + return nil, err + } + v := newVariable(fn.Name, 0, typ, bi, mem) + v.Value = constant.MakeString(fn.Name) + v.loaded = true + v.Base = uintptr(fn.Entry) + return v, nil +} + func fakeSliceType(fieldType godwarf.Type) godwarf.Type { return &godwarf.SliceType{ StructType: godwarf.StructType{ @@ -1318,3 +1402,61 @@ func fakeSliceType(fieldType godwarf.Type) godwarf.Type { ElemType: fieldType, } } + +var errMethodEvalUnsupported = errors.New("evaluating methods not supported on this version of Go") + +func (fn *Function) fakeType(bi *BinaryInfo, removeReceiver bool) (*godwarf.FuncType, error) { + if producer := bi.Producer(); producer == "" || !goversion.ProducerAfterOrEqual(producer, 1, 10) { + // versions of Go prior to 1.10 do not distinguish between parameters and + // return values, therefore we can't use a subprogram DIE to derive a + // function type. + return nil, errMethodEvalUnsupported + } + _, formalArgs, err := funcCallArgs(fn, bi, true) + if err != nil { + return nil, err + } + + if removeReceiver { + formalArgs = formalArgs[1:] + } + + args := make([]string, 0, len(formalArgs)) + rets := make([]string, 0, len(formalArgs)) + + for _, formalArg := range formalArgs { + var s string + if strings.HasPrefix(formalArg.name, "~") { + s = formalArg.typ.String() + } else { + s = fmt.Sprintf("%s %s", formalArg.name, formalArg.typ.String()) + } + if formalArg.isret { + rets = append(rets, s) + } else { + args = append(args, s) + } + } + + argstr := strings.Join(args, ", ") + var retstr string + switch len(rets) { + case 0: + retstr = "" + case 1: + retstr = " " + rets[0] + default: + retstr = " (" + strings.Join(rets, ", ") + ")" + } + return &godwarf.FuncType{ + CommonType: godwarf.CommonType{ + Name: "func(" + argstr + ")" + retstr, + ReflectKind: reflect.Func, + }, + //TODO(aarzilli): at the moment we aren't using the ParamType and + // ReturnType fields of FuncType anywhere (when this is returned to the + // client it's first converted to a string and the function calling code + // reads the subroutine entry because it needs to know the stack offsets). + // If we start using them they should be filled here. + }, nil +} diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go index b0191503..a728b286 100644 --- a/pkg/proc/fncall.go +++ b/pkg/proc/fncall.go @@ -236,51 +236,18 @@ func funcCallEvalExpr(p Process, expr string) (fn *Function, argvars []*Variable } type funcCallArg struct { - name string - typ godwarf.Type - off int64 + name string + typ godwarf.Type + off int64 + isret bool } // funcCallArgFrame checks type and pointer escaping for the arguments and // returns the argument frame. func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo) (argmem []byte, err error) { - const CFA = 0x1000 - vrdr := reader.Variables(bi.dwarf, fn.offset, fn.Entry, int(^uint(0)>>1), false) - formalArgs := []funcCallArg{} - - // typechecks arguments, calculates argument frame size - argFrameSize := int64(0) - for vrdr.Next() { - e := vrdr.Entry() - if e.Tag != dwarf.TagFormalParameter { - continue - } - entry, argname, typ, err := readVarEntry(e, bi) - if err != nil { - return nil, err - } - typ = resolveTypedef(typ) - locprog, _, err := bi.locationExpr(entry, dwarf.AttrLocation, fn.Entry) - if err != nil { - return nil, fmt.Errorf("could not get argument location of %s: %v", argname, err) - } - off, _, err := op.ExecuteStackProgram(op.DwarfRegisters{CFA: CFA, FrameBase: CFA}, locprog) - if err != nil { - return nil, fmt.Errorf("unsupported location expression for argument %s: %v", argname, err) - } - - off -= CFA - - if e := off + typ.Size(); e > argFrameSize { - argFrameSize = e - } - - if isret, _ := entry.Val(dwarf.AttrVarParam).(bool); !isret { - formalArgs = append(formalArgs, funcCallArg{name: argname, typ: typ, off: off}) - } - } - if err := vrdr.Err(); err != nil { - return nil, fmt.Errorf("DWARF read error: %v", err) + argFrameSize, formalArgs, err := funcCallArgs(fn, bi, false) + if err != nil { + return nil, err } if len(actualArgs) > len(formalArgs) { return nil, ErrTooManyArguments @@ -289,10 +256,6 @@ func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo return nil, ErrNotEnoughArguments } - sort.Slice(formalArgs, func(i, j int) bool { - return formalArgs[i].off < formalArgs[j].off - }) - // constructs arguments frame argmem = make([]byte, argFrameSize) argmemWriter := &bufferMemoryReadWriter{argmem} @@ -317,6 +280,51 @@ func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo return argmem, nil } +func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize int64, formalArgs []funcCallArg, err error) { + const CFA = 0x1000 + vrdr := reader.Variables(bi.dwarf, fn.offset, fn.Entry, int(^uint(0)>>1), false) + + // typechecks arguments, calculates argument frame size + for vrdr.Next() { + e := vrdr.Entry() + if e.Tag != dwarf.TagFormalParameter { + continue + } + entry, argname, typ, err := readVarEntry(e, bi) + if err != nil { + return 0, nil, err + } + typ = resolveTypedef(typ) + locprog, _, err := bi.locationExpr(entry, dwarf.AttrLocation, fn.Entry) + if err != nil { + return 0, nil, fmt.Errorf("could not get argument location of %s: %v", argname, err) + } + off, _, err := op.ExecuteStackProgram(op.DwarfRegisters{CFA: CFA, FrameBase: CFA}, locprog) + if err != nil { + return 0, nil, fmt.Errorf("unsupported location expression for argument %s: %v", argname, err) + } + + off -= CFA + + if e := off + typ.Size(); e > argFrameSize { + argFrameSize = e + } + + if isret, _ := entry.Val(dwarf.AttrVarParam).(bool); !isret || includeRet { + formalArgs = append(formalArgs, funcCallArg{name: argname, typ: typ, off: off, isret: isret}) + } + } + if err := vrdr.Err(); err != nil { + return 0, nil, fmt.Errorf("DWARF read error: %v", err) + } + + sort.Slice(formalArgs, func(i, j int) bool { + return formalArgs[i].off < formalArgs[j].off + }) + + return argFrameSize, formalArgs, nil +} + func escapeCheck(v *Variable, name string, g *G) error { switch v.Kind { case reflect.Ptr: diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 141699c8..57861a50 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -791,6 +791,11 @@ func TestEvalExpression(t *testing.T) { {"afunc", true, `main.afunc`, `main.afunc`, `func()`, nil}, {"main.afunc2", true, `main.afunc2`, `main.afunc2`, `func()`, nil}, + + {"s2[0].Error", false, "main.(*astruct).Error", "main.(*astruct).Error", "func() string", nil}, + {"s2[0].NonPointerRecieverMethod", false, "main.astruct.NonPointerRecieverMethod", "main.astruct.NonPointerRecieverMethod", "func()", nil}, + {"as2.Error", false, "main.(*astruct).Error", "main.(*astruct).Error", "func() string", nil}, + {"as2.NonPointerRecieverMethod", false, "main.astruct.NonPointerRecieverMethod", "main.astruct.NonPointerRecieverMethod", "func()", nil}, } ver, _ := goversion.Parse(runtime.Version()) @@ -808,6 +813,10 @@ func TestEvalExpression(t *testing.T) { assertNoError(proc.Continue(p), t, "Continue() returned an error") for _, tc := range testcases { variable, err := evalVariable(p, tc.name, pnormalLoadConfig) + if err != nil && err.Error() == "evaluating methods not supported on this version of Go" { + // this type of eval is unsupported with the current version of Go. + continue + } if tc.err == nil { assertNoError(err, t, fmt.Sprintf("EvalExpression(%s) returned an error", tc.name)) assertVariable(t, variable, tc)