proc: evaluate var.method expressions

Evaluates var.method expressions into a variable holding the
corresponding method with the receiver variable as a child, in
preparation for extending CallFunction so that it can call methods.
This commit is contained in:
aarzilli 2018-07-30 21:02:35 +02:00 committed by Derek Parker
parent 9335c54014
commit 51994aafd3
4 changed files with 215 additions and 50 deletions

@ -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)
}

@ -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
}

@ -239,49 +239,16 @@ type funcCallArg struct {
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)
argFrameSize, formalArgs, err := funcCallArgs(fn, bi, false)
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)
}
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:

@ -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)