diff --git a/_fixtures/testvariables3.go b/_fixtures/testvariables3.go index 8e7c5305b858de244c90276c6921dcd91af0de26..aa69d7a8fc5a69879fa92a121c75913de9d5e3f9 100644 --- a/_fixtures/testvariables3.go +++ b/_fixtures/testvariables3.go @@ -26,6 +26,14 @@ func afunc(x int) int { type functype func(int) int +func (a *astruct) Error() string { + return "not an error" +} + +func (b *bstruct) Error() string { + return "not an error" +} + func main() { i1 := 1 i2 := 2 @@ -96,11 +104,16 @@ func main() { i4 := 800 i5 := -3 i6 := -500 + var err1 error = c1.sa[0] + var err2 error = c1.pb + var errnil error = nil + var iface1 interface{} = c1.sa[0] + var ifacenil interface{} = nil var amb1 = 1 runtime.Breakpoint() for amb1 := 0; amb1 < 10; 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, m1, mnil, m2, m3, up1, i4, i5, i6) + 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, up1, i4, i5, i6, err1, err2, errnil, iface1, ifacenil) } diff --git a/proc/eval.go b/proc/eval.go index 762103a7691c1dd71347c395543901256a96b487..ca78979788b994cbc3a7e8b7b745930634978da6 100644 --- a/proc/eval.go +++ b/proc/eval.go @@ -55,6 +55,9 @@ func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) { // if it's not a package variable then it must be a struct member access return scope.evalStructSelector(node) + case *ast.TypeAssertExpr: // .() + return scope.evalTypeAssert(node) + case *ast.IndexExpr: return scope.evalIndex(node) @@ -166,26 +169,11 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { fnnode = p.X } - var styp, typ dwarf.Type - - if snode, ok := fnnode.(*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 := scope.findType(exprToString(snode.X)) - if err != nil { - return nil, err - } - typ = &dwarf.PtrType{dwarf.CommonType{int64(scope.Thread.dbp.arch.PtrSize()), exprToString(fnnode)}, ptyp} - styp = typ - } else { - styp, err = scope.findType(exprToString(fnnode)) - if err != nil { - return nil, err - } - typ = resolveTypedef(styp) + styp, err := scope.Thread.dbp.findTypeExpr(fnnode) + if err != nil { + return nil, err } + typ := resolveTypedef(styp) converr := fmt.Errorf("can not convert \"%s\" to %s", exprToString(node.Args[0]), typ.String()) @@ -312,6 +300,35 @@ func (scope *EvalScope) evalStructSelector(node *ast.SelectorExpr) (*Variable, e return xv.structMember(node.Sel.Name) } +// Evaluates expressions .() +func (scope *EvalScope) evalTypeAssert(node *ast.TypeAssertExpr) (*Variable, error) { + xv, err := scope.evalAST(node.X) + if err != nil { + return nil, err + } + if xv.Kind != reflect.Interface { + return nil, fmt.Errorf("expression \"%s\" not an interface", exprToString(node.X)) + } + xv.loadInterface(0, false) + if xv.Unreadable != nil { + return nil, xv.Unreadable + } + if xv.Children[0].Unreadable != nil { + return nil, xv.Children[0].Unreadable + } + if xv.Children[0].Addr == 0 { + return nil, fmt.Errorf("interface conversion: %s is nil, not %s", xv.DwarfType.String(), exprToString(node.Type)) + } + typ, err := scope.Thread.dbp.findTypeExpr(node.Type) + if err != nil { + return nil, err + } + if xv.Children[0].DwarfType.String() != typ.String() { + return nil, fmt.Errorf("interface conversion: %s is %s, not %s", xv.DwarfType.String(), xv.Children[0].TypeString(), typ) + } + return &xv.Children[0], nil +} + // Evaluates expressions [] (subscript access to arrays, slices and maps) func (scope *EvalScope) evalIndex(node *ast.IndexExpr) (*Variable, error) { xev, err := scope.evalAST(node.X) @@ -761,13 +778,13 @@ func equalChildren(xv, yv *Variable, shortcircuit bool) (bool, error) { return r, nil } -func (scope *EvalScope) findType(name string) (dwarf.Type, error) { - reader := scope.DwarfReader() +func (dbp *Process) findType(name string) (dwarf.Type, error) { + reader := dbp.DwarfReader() typentry, err := reader.SeekToTypeNamed(name) if err != nil { return nil, err } - return scope.Thread.dbp.dwarf.Type(typentry.Offset) + return dbp.dwarf.Type(typentry.Offset) } func (v *Variable) asInt() (int64, error) { diff --git a/proc/variables.go b/proc/variables.go index 50925f5464baa2feecce0209b2607822ff50f597..084d7adf89aa0db445af9ee6f8bcc3f09a3dc02b 100644 --- a/proc/variables.go +++ b/proc/variables.go @@ -5,6 +5,7 @@ import ( "debug/dwarf" "encoding/binary" "fmt" + "go/ast" "go/constant" "go/parser" "go/token" @@ -110,6 +111,14 @@ type EvalScope struct { CFA int64 } +type IsNilErr struct { + name string +} + +func (err *IsNilErr) Error() string { + return fmt.Sprintf("%s is nil", err.name) +} + func newVariable(name string, addr uintptr, dwarfType dwarf.Type, thread *Thread) *Variable { v := &Variable{ Name: name, @@ -142,6 +151,8 @@ func newVariable(name string, addr uintptr, dwarfType dwarf.Type, thread *Thread if v.Addr != 0 { v.base, v.Len, v.Unreadable = v.thread.readStringInfo(v.Addr) } + case t.StructName == "runtime.iface" || t.StructName == "runtime.eface": + v.Kind = reflect.Interface case strings.HasPrefix(t.StructName, "[]"): v.Kind = reflect.Slice if v.Addr != 0 { @@ -249,7 +260,7 @@ func (v *Variable) toField(field *dwarf.StructField) (*Variable, error) { return v.clone(), nil } if v.Addr == 0 { - return nil, fmt.Errorf("%s is nil", v.Name) + return nil, &IsNilErr{v.Name} } name := "" @@ -770,6 +781,9 @@ func (v *Variable) loadValueInternal(recurseLevel int) { } } + case reflect.Interface: + v.loadInterface(recurseLevel, true) + case reflect.Complex64, reflect.Complex128: v.readComplex(v.RealType.(*dwarf.ComplexType).ByteSize) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: @@ -1366,6 +1380,101 @@ func mapEvacuated(b *Variable) bool { return true } +func (v *Variable) loadInterface(recurseLevel int, loadData bool) { + var typestring, data *Variable + isnil := false + + for _, f := range v.RealType.(*dwarf.StructType).Field { + switch f.Name { + case "tab": // for runtime.iface + tab, _ := v.toField(f) + _type, err := tab.structMember("_type") + if err != nil { + _, isnil = err.(*IsNilErr) + if !isnil { + v.Unreadable = fmt.Errorf("invalid interface type: %v", err) + return + } + } else { + typestring, err = _type.structMember("_string") + if err != nil { + v.Unreadable = fmt.Errorf("invalid interface type: %v", err) + return + } + typestring = typestring.maybeDereference() + } + case "_type": // for runtime.eface + var err error + _type, _ := v.toField(f) + typestring, err = _type.structMember("_string") + if err != nil { + _, isnil = err.(*IsNilErr) + if !isnil { + v.Unreadable = fmt.Errorf("invalid interface type: %v", err) + return + } + } else { + typestring = typestring.maybeDereference() + } + case "data": + data, _ = v.toField(f) + } + } + + if isnil { + // interface to nil + data = data.maybeDereference() + v.Children = []Variable{*data} + v.Children[0].loadValue() + return + } + + if typestring == nil || data == nil || typestring.Addr == 0 || typestring.Kind != reflect.String { + v.Unreadable = fmt.Errorf("invalid interface type") + return + } + typestring.loadValue() + if typestring.Unreadable != nil { + v.Unreadable = fmt.Errorf("invalid interface type: %v", typestring.Unreadable) + return + } + + t, err := parser.ParseExpr(constant.StringVal(typestring.Value)) + if err != nil { + v.Unreadable = fmt.Errorf("invalid interface type, unparsable data type: %v", err) + return + } + + typ, err := v.thread.dbp.findTypeExpr(t) + if err != nil { + v.Unreadable = fmt.Errorf("invalid interface type: %v", err) + return + } + + data = newVariable("data", data.Addr, typ, data.thread) + + v.Children = []Variable{*data} + if loadData { + v.Children[0].loadValue() + } + return +} + +func (dbp *Process) findTypeExpr(expr ast.Expr) (dwarf.Type, error) { + 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)) + if err != nil { + return nil, err + } + return &dwarf.PtrType{dwarf.CommonType{int64(dbp.arch.PtrSize()), exprToString(expr)}, ptyp}, nil + } + return dbp.findType(exprToString(expr)) +} + // Fetches all variables of a specific type in the current function scope func (scope *EvalScope) variablesByTag(tag dwarf.Tag) ([]*Variable, error) { reader := scope.DwarfReader() diff --git a/service/api/conversions.go b/service/api/conversions.go index c0843ecde54f08ad1de7da2dfa81e08ab1d2b0ed..1bf9ff162e0fd78022f46196dd0a17e3e4acfccd 100644 --- a/service/api/conversions.go +++ b/service/api/conversions.go @@ -68,10 +68,16 @@ func ConvertVar(v *proc.Variable) *Variable { if v.DwarfType != nil { r.Type = v.DwarfType.String() + if r.Type == "*void" { + r.Type = "unsafe.Pointer" + } } if v.RealType != nil { r.RealType = v.RealType.String() + if r.RealType == "*void" { + r.Type = "unsafe.Pointer" + } } if v.Unreadable != nil { diff --git a/service/api/prettyprint.go b/service/api/prettyprint.go index ed945f81ee8ed7a9d9f1f512c78fd9dfba7f71a1..44667725ad8e1c7dbbe8b787ce835a9ce1bf449c 100644 --- a/service/api/prettyprint.go +++ b/service/api/prettyprint.go @@ -69,6 +69,19 @@ func (v *Variable) writeTo(buf *bytes.Buffer, top, newlines, includeType bool, i } case reflect.Struct: v.writeStructTo(buf, newlines, includeType, indent) + case reflect.Interface: + if includeType { + if v.Children[0].Kind == reflect.Invalid { + fmt.Fprintf(buf, "%s ", v.Type) + if v.Children[0].Addr == 0 { + fmt.Fprintf(buf, "nil") + return + } + } else { + fmt.Fprintf(buf, "%s(%s) ", v.Type, v.Children[0].Type) + } + } + v.Children[0].writeTo(buf, false, newlines, false, indent) case reflect.Map: v.writeMapTo(buf, newlines, includeType, indent) case reflect.Func: @@ -192,7 +205,7 @@ func (v *Variable) shouldNewlineArray(newlines bool) bool { kind, hasptr := (&v.Children[0]).recursiveKind() switch kind { - case reflect.Slice, reflect.Array, reflect.Struct, reflect.Map: + case reflect.Slice, reflect.Array, reflect.Struct, reflect.Map, reflect.Interface: return true case reflect.String: if hasptr { @@ -233,7 +246,7 @@ func (v *Variable) shouldNewlineStruct(newlines bool) bool { kind, hasptr := (&v.Children[i]).recursiveKind() switch kind { - case reflect.Slice, reflect.Array, reflect.Struct, reflect.Map: + case reflect.Slice, reflect.Array, reflect.Struct, reflect.Map, reflect.Interface: return true case reflect.String: if hasptr { diff --git a/service/test/variables_test.go b/service/test/variables_test.go index d977a2a62e9b8565d0522941325595335254e283..847d2fca3bfc26448172c8af7bfe67ebe4471257 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -399,6 +399,20 @@ func TestEvalExpression(t *testing.T) { {"mnil[\"Malone\"]", false, "", "", "", fmt.Errorf("key not found")}, {"m1[80:]", false, "", "", "", fmt.Errorf("map index out of bounds")}, + // interfaces + {"err1", true, "error(*struct main.astruct) *{A: 1, B: 2}", "", "error", nil}, + {"err2", true, "error(*struct main.bstruct) *{a: main.astruct {A: 1, B: 2}}", "", "error", nil}, + {"errnil", true, "error nil", "", "error", nil}, + {"iface1", true, "interface {}(*struct main.astruct) *{A: 1, B: 2}", "", "interface {}", nil}, + {"ifacenil", true, "interface {} nil", "", "interface {}", nil}, + {"err1 == err2", false, "false", "", "", nil}, + {"err1 == iface1", false, "", "", "", fmt.Errorf("mismatched types \"error\" and \"interface {}\"")}, + {"errnil == nil", false, "false", "", "", nil}, + {"nil == errnil", false, "false", "", "", nil}, + {"err1.(*main.astruct)", false, "*struct main.astruct {A: 1, B: 2}", "", "*struct main.astruct", nil}, + {"err1.(*main.bstruct)", false, "", "", "", fmt.Errorf("interface conversion: error is *struct main.astruct, not *struct main.bstruct")}, + {"errnil.(*main.astruct)", false, "", "", "", fmt.Errorf("interface conversion: error is nil, not *main.astruct")}, + // combined expressions {"c1.pb.a.A", true, "1", "", "int", nil}, {"c1.sa[1].B", false, "3", "", "int", nil},