From a7a0cc75e128c696af0d90a811cd7235339074db Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Mon, 25 Apr 2016 01:14:42 +0200 Subject: [PATCH] proc: allow use of quoted type names in type casts (#434) If we are unable to correctly guess how a type is serialized in debug_info let the user specify it directly. Workaround for #455 --- _fixtures/testvariables2.go | 12 +++++++++++- dwarf/reader/reader.go | 2 +- proc/eval.go | 8 +++++++- proc/types.go | 11 ++++++++++- service/test/variables_test.go | 33 ++++++++++++++++++++++++++++++++- 5 files changed, 61 insertions(+), 5 deletions(-) diff --git a/_fixtures/testvariables2.go b/_fixtures/testvariables2.go index 9dea8875..9826010a 100644 --- a/_fixtures/testvariables2.go +++ b/_fixtures/testvariables2.go @@ -187,6 +187,16 @@ func main() { b2 := B{A: A{42}, a: A{47}} var sd D + var mapanonstruct1 map[string]struct{} + var anonstruct1 struct{ val constant.Value } + var anonstruct2 struct{ i, j int } + var anoniface1 interface { + SomeFunction(struct{ val constant.Value }) + OtherFunction(i, j int) + } + var anonfunc func(a struct{ i int }, b interface{}, c struct{ val constant.Value }) + + for i := range benchparr { benchparr[i] = &benchstruct{} } @@ -197,5 +207,5 @@ func main() { fmt.Println(amb1) } 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) + 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) } diff --git a/dwarf/reader/reader.go b/dwarf/reader/reader.go index 0ad6c416..252d1a87 100755 --- a/dwarf/reader/reader.go +++ b/dwarf/reader/reader.go @@ -104,7 +104,7 @@ func (reader *Reader) AddrForMember(member string, initialInstructions []byte) ( } } -var TypeNotFoundErr = errors.New("no type entry found") +var TypeNotFoundErr = errors.New("no type entry found, use 'types' for a list of valid types") // SeekToType moves the reader to the type specified by the entry, // optionally resolving typedefs and pointer types. If the reader is set diff --git a/proc/eval.go b/proc/eval.go index 8bd6e807..7f979400 100644 --- a/proc/eval.go +++ b/proc/eval.go @@ -41,7 +41,13 @@ func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) { if err == nil { return v, nil } - if err != reader.TypeNotFoundErr { + _, isident := node.Fun.(*ast.Ident) + // we don't support function calls at the moment except for a few + // builtin functions so just return the type error here if the function + // isn't an identifier. + // More sophisticated logic will be required when function calls + // are implemented. + if err != reader.TypeNotFoundErr || !isident { return v, err } } diff --git a/proc/types.go b/proc/types.go index 8970d8c8..75fbe9b6 100644 --- a/proc/types.go +++ b/proc/types.go @@ -3,8 +3,10 @@ package proc import ( "github.com/derekparker/delve/dwarf/reader" "go/ast" + "go/token" "golang.org/x/debug/dwarf" "reflect" + "strconv" "strings" "sync" ) @@ -24,13 +26,20 @@ func (dbp *Process) pointerTo(typ dwarf.Type) dwarf.Type { func (dbp *Process) findTypeExpr(expr ast.Expr) (dwarf.Type, error) { dbp.loadPackageMap() + if lit, islit := expr.(*ast.BasicLit); islit && lit.Kind == token.STRING { + // Allow users to specify type names verbatim as quoted + // string. Useful as a catch-all workaround for cases where we don't + // parse/serialize types correctly or can not resolve package paths. + typn, _ := strconv.Unquote(lit.Value) + return dbp.findType(typn) + } dbp.expandPackagesInType(expr) if snode, ok := expr.(*ast.StarExpr); ok { // Pointer types only appear in the dwarf informations when // a pointer to the type is used in the target program, here // we create a pointer type on the fly so that the user can // specify a pointer to any variable used in the target program - ptyp, err := dbp.findType(exprToString(snode.X)) + ptyp, err := dbp.findTypeExpr(snode.X) if err != nil { return nil, err } diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 505385bd..b69dfd84 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -598,7 +598,7 @@ func TestEvalExpression(t *testing.T) { {"i2 << i3", false, "", "", "int", fmt.Errorf("shift count type int, must be unsigned integer")}, {"*(i2 + i3)", false, "", "", "", fmt.Errorf("expression \"(i2 + i3)\" (int) can not be dereferenced")}, {"i2.member", false, "", "", "", fmt.Errorf("i2 (type int) is not a struct")}, - {"fmt.Println(\"hello\")", false, "", "", "", fmt.Errorf("function calls are not supported")}, + {"fmt.Println(\"hello\")", false, "", "", "", fmt.Errorf("no type entry found, use 'types' for a list of valid types")}, {"*nil", false, "", "", "", fmt.Errorf("nil can not be dereferenced")}, {"!nil", false, "", "", "", fmt.Errorf("operator ! can not be applied to \"nil\"")}, {"&nil", false, "", "", "", fmt.Errorf("can not take address of \"nil\"")}, @@ -718,3 +718,34 @@ func TestUnsafePointer(t *testing.T) { } }) } + +type issue426TestCase struct { + name string + typ string +} + +func TestIssue426(t *testing.T) { + // type casts using quoted type names + testcases := []issue426TestCase{ + {"iface1", `interface {}`}, + {"mapanonstruct1", `map[string]struct {}`}, + {"anonstruct1", `struct { val go/constant.Value }`}, + {"anonfunc", `func(struct { i int }, interface {}, struct { val go/constant.Value })`}, + {"anonstruct2", `struct { i int; j int }`}, + {"anoniface1", `interface { OtherFunction(int, int); SomeFunction(struct { val go/constant.Value }) }`}, + } + + // Serialization of type expressions (go/ast.Expr) containing anonymous structs or interfaces + // differs from the serialization used by the linker to produce DWARF type information + withTestProcess("testvariables2", t, func(p *proc.Process, fixture protest.Fixture) { + assertNoError(p.Continue(), t, "Continue() returned an error") + for _, testcase := range testcases { + v, err := evalVariable(p, testcase.name, pnormalLoadConfig) + assertNoError(err, t, fmt.Sprintf("EvalVariable(%s)", testcase.name)) + t.Logf("%s → %s", testcase.name, v.RealType.String()) + expr := fmt.Sprintf("(*%q)(%d)", testcase.typ, v.Addr) + _, err = evalVariable(p, expr, pnormalLoadConfig) + assertNoError(err, t, fmt.Sprintf("EvalVariable(%s)", expr)) + } + }) +}