diff --git a/_fixtures/testvariables.go b/_fixtures/testvariables.go index 52b44cf4..37ae096f 100644 --- a/_fixtures/testvariables.go +++ b/_fixtures/testvariables.go @@ -1,6 +1,7 @@ package main import "fmt" +import "runtime" type FooBar struct { Baz int @@ -19,6 +20,7 @@ type Nest struct { } func barfoo() { + runtime.Breakpoint() a1 := "bur" fmt.Println(a1) } @@ -56,6 +58,7 @@ func foobar(baz string, bar FooBar) { ba = make([]int, 200, 200) // Test array size capping ) + runtime.Breakpoint() barfoo() fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, b1, b2, baz, neg, i8, u8, u16, u32, u64, up, f32, c64, c128, i32, bar, f, ms, ba, p1) } diff --git a/_fixtures/testvariables3.go b/_fixtures/testvariables3.go index 8ce2dd80..72df030d 100644 --- a/_fixtures/testvariables3.go +++ b/_fixtures/testvariables3.go @@ -10,5 +10,5 @@ func main() { i2 := 2 p1 := &i1 runtime.Breakpoint() - fmt.Printf("%d %d %v\n", i1, i2, p1) + fmt.Println(i1, i2, p1) } diff --git a/_fixtures/testvariables4.go b/_fixtures/testvariables4.go index 4f7db54b..3492cdfe 100644 --- a/_fixtures/testvariables4.go +++ b/_fixtures/testvariables4.go @@ -1,12 +1,12 @@ package main import ( - "fmt" - "runtime" + "fmt" + "runtime" ) type A struct { - val int + val int } type C struct { @@ -14,20 +14,20 @@ type C struct { } type B struct { - A + A *C - a A - ptr *A + a A + ptr *A } func main() { - b := B{A: A{-314}, C: &C{"hello"}, a: A{42}, ptr: &A{1337}} - runtime.Breakpoint() - fmt.Println(b) - fmt.Println(b.val) - fmt.Println(b.A.val) - fmt.Println(b.a.val) - fmt.Println(b.ptr.val) - fmt.Println(b.C.s) - fmt.Println(b.s) -} \ No newline at end of file + b := B{A: A{-314}, C: &C{"hello"}, a: A{42}, ptr: &A{1337}} + runtime.Breakpoint() + fmt.Println(b) + fmt.Println(b.val) + fmt.Println(b.A.val) + fmt.Println(b.a.val) + fmt.Println(b.ptr.val) + fmt.Println(b.C.s) + fmt.Println(b.s) +} diff --git a/cmd/dlv/main.go b/cmd/dlv/main.go index bf78d7a6..2114fd30 100644 --- a/cmd/dlv/main.go +++ b/cmd/dlv/main.go @@ -195,7 +195,7 @@ starts and attaches to it, and enables you to immediately begin debugging your p } if state.BreakpointInfo != nil { for _, arg := range state.BreakpointInfo.Arguments { - args = append(args, arg.Value) + args = append(args, arg.SinglelineString()) } } fmt.Printf("%s(%s) %s:%d\n", fname, strings.Join(args, ", "), state.CurrentThread.File, state.CurrentThread.Line) diff --git a/proc/proc.go b/proc/proc.go index 4edc951d..09565517 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -707,9 +707,9 @@ func (dbp *Process) getGoInformation() (ver GoVersion, isextld bool, err error) return } - ver, ok := parseVersionString(vv.Value) + ver, ok := parseVersionString(vv.Value.(string)) if !ok { - err = fmt.Errorf("Could not parse version number: %s\n", vv.Value) + err = fmt.Errorf("Could not parse version number: %v\n", vv.Value) return } diff --git a/proc/proc_test.go b/proc/proc_test.go index 4cea2c97..0657424e 100644 --- a/proc/proc_test.go +++ b/proc/proc_test.go @@ -7,7 +7,9 @@ import ( "net/http" "os" "path/filepath" + "reflect" "runtime" + "strconv" "strings" "testing" "time" @@ -332,7 +334,7 @@ func TestNextConcurrent(t *testing.T) { } v, err := evalVariable(p, "n") assertNoError(err, t, "EvalVariable") - if v.Value != initV.Value { + if v.Value.(int64) != initV.Value.(int64) { t.Fatal("Did not end up on same goroutine") } } @@ -809,3 +811,221 @@ func TestIssue239(t *testing.T) { assertNoError(p.Continue(), t, fmt.Sprintf("Continue()")) }) } + +func evalVariable(p *Process, symbol string) (*Variable, error) { + scope, err := p.CurrentThread.Scope() + if err != nil { + return nil, err + } + return scope.EvalVariable(symbol) +} + +func setVariable(p *Process, symbol, value string) error { + scope, err := p.CurrentThread.Scope() + if err != nil { + return err + } + return scope.SetVariable(symbol, value) +} + +func TestVariableEvaluation(t *testing.T) { + testcases := []struct { + name string + st reflect.Kind + value interface{} + length, cap int64 + childrenlen int + }{ + {"a1", reflect.String, "foofoofoofoofoofoo", 18, 0, 0}, + {"a11", reflect.Array, nil, 3, -1, 3}, + {"a12", reflect.Slice, nil, 2, 2, 2}, + {"a13", reflect.Slice, nil, 3, 3, 3}, + {"a2", reflect.Int, int64(6), 0, 0, 0}, + {"a3", reflect.Float64, float64(7.23), 0, 0, 0}, + {"a4", reflect.Array, nil, 2, -1, 2}, + {"a5", reflect.Slice, nil, 5, 5, 5}, + {"a6", reflect.Struct, nil, 2, 0, 2}, + {"a7", reflect.Ptr, nil, 1, 0, 1}, + {"a8", reflect.Struct, nil, 2, 0, 2}, + {"a9", reflect.Ptr, nil, 1, 0, 1}, + {"baz", reflect.String, "bazburzum", 9, 0, 0}, + {"neg", reflect.Int, int64(-1), 0, 0, 0}, + {"f32", reflect.Float32, float64(float32(1.2)), 0, 0, 0}, + {"c64", reflect.Complex64, nil, 2, 0, 2}, + {"c128", reflect.Complex128, nil, 2, 0, 2}, + {"a6.Baz", reflect.Int, int64(8), 0, 0, 0}, + {"a7.Baz", reflect.Int, int64(5), 0, 0, 0}, + {"a8.Baz", reflect.String, "feh", 3, 0, 0}, + {"a8", reflect.Struct, nil, 2, 0, 2}, + {"i32", reflect.Array, nil, 2, -1, 2}, + {"b1", reflect.Bool, true, 0, 0, 0}, + {"b2", reflect.Bool, false, 0, 0, 0}, + {"f", reflect.Func, "main.barfoo", 0, 0, 0}, + {"ba", reflect.Slice, nil, 200, 200, 64}, + } + + withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) { + assertNoError(p.Continue(), t, "Continue() returned an error") + + for _, tc := range testcases { + v, err := evalVariable(p, tc.name) + assertNoError(err, t, fmt.Sprintf("EvalVariable(%s)", tc.name)) + + if v.Kind != tc.st { + t.Fatalf("%s simple type: expected: %s got: %s", tc.name, tc.st, v.Kind.String()) + } + if v.Value == nil && tc.value != nil { + t.Fatalf("%s value: expected: %v got: %v", tc.name, tc.value, v.Value) + } else { + switch x := v.Value.(type) { + case int64: + if y, ok := tc.value.(int64); !ok || x != y { + t.Fatalf("%s value: expected: %v got: %v", tc.name, tc.value, v.Value) + } + case float64: + if y, ok := tc.value.(float64); !ok || x != y { + t.Fatalf("%s value: expected: %v got: %v", tc.name, tc.value, v.Value) + } + case string: + if y, ok := tc.value.(string); !ok || x != y { + t.Fatalf("%s value: expected: %v got: %v", tc.name, tc.value, v.Value) + } + } + } + if v.Len != tc.length { + t.Fatalf("%s len: expected: %d got: %d", tc.name, tc.length, v.Len) + } + if v.Cap != tc.cap { + t.Fatalf("%s cap: expected: %d got: %d", tc.name, tc.cap, v.Cap) + } + if len(v.Children) != tc.childrenlen { + t.Fatalf("%s children len: expected %d got: %d", tc.name, tc.childrenlen, len(v.Children)) + } + } + }) +} + +func TestFrameEvaluation(t *testing.T) { + withTestProcess("goroutinestackprog", t, func(p *Process, fixture protest.Fixture) { + _, err := setFunctionBreakpoint(p, "main.stacktraceme") + assertNoError(err, t, "setFunctionBreakpoint") + assertNoError(p.Continue(), t, "Continue()") + + /**** Testing evaluation on goroutines ****/ + gs, err := p.GoroutinesInfo() + assertNoError(err, t, "GoroutinesInfo") + found := make([]bool, 10) + for _, g := range gs { + frame := -1 + frames, err := p.GoroutineStacktrace(g, 10) + assertNoError(err, t, "GoroutineStacktrace()") + for i := range frames { + if frames[i].Call.Fn != nil && frames[i].Call.Fn.Name == "main.agoroutine" { + frame = i + break + } + } + + if frame < 0 { + t.Logf("Goroutine %d: could not find correct frame", g.Id) + continue + } + + scope, err := p.ConvertEvalScope(g.Id, frame) + assertNoError(err, t, "ConvertEvalScope()") + t.Logf("scope = %v", scope) + v, err := scope.EvalVariable("i") + t.Logf("v = %v", v) + if err != nil { + t.Logf("Goroutine %d: %v\n", g.Id, err) + continue + } + found[v.Value.(int64)] = true + } + + for i := range found { + if !found[i] { + t.Fatalf("Goroutine %d not found\n", i) + } + } + + /**** Testing evaluation on frames ****/ + assertNoError(p.Continue(), t, "Continue() 2") + g, err := p.CurrentThread.GetG() + assertNoError(err, t, "GetG()") + + for i := 0; i <= 3; i++ { + scope, err := p.ConvertEvalScope(g.Id, i+1) + assertNoError(err, t, fmt.Sprintf("ConvertEvalScope() on frame %d", i+1)) + v, err := scope.EvalVariable("n") + assertNoError(err, t, fmt.Sprintf("EvalVariable() on frame %d", i+1)) + n := v.Value.(int64) + t.Logf("frame %d n %d\n", i+1, n) + if n != int64(3-i) { + t.Fatalf("On frame %d value of n is %d (not %d)", i+1, n, 3-i) + } + } + }) +} + +func TestPointerSetting(t *testing.T) { + withTestProcess("testvariables3", t, func(p *Process, fixture protest.Fixture) { + assertNoError(p.Continue(), t, "Continue() returned an error") + + pval := func(n int64) { + variable, err := evalVariable(p, "p1") + assertNoError(err, t, "EvalVariable()") + if variable.Children[0].Value.(int64) != n { + t.Fatalf("Wrong value of p1, *%d expected *%d", variable.Children[0].Value.(int64), n) + } + } + + pval(1) + + // change p1 to point to i2 + scope, err := p.CurrentThread.Scope() + assertNoError(err, t, "Scope()") + i2addr, err := scope.ExtractVariableInfo("i2") + assertNoError(err, t, "EvalVariableAddr()") + assertNoError(setVariable(p, "p1", strconv.Itoa(int(i2addr.Addr))), t, "SetVariable()") + pval(2) + + // change the value of i2 check that p1 also changes + assertNoError(setVariable(p, "i2", "5"), t, "SetVariable()") + pval(5) + }) +} + +func TestVariableFunctionScoping(t *testing.T) { + withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) { + err := p.Continue() + assertNoError(err, t, "Continue() returned an error") + + _, err = evalVariable(p, "a1") + assertNoError(err, t, "Unable to find variable a1") + + _, err = evalVariable(p, "a2") + assertNoError(err, t, "Unable to find variable a1") + + // Move scopes, a1 exists here by a2 does not + err = p.Continue() + assertNoError(err, t, "Continue() returned an error") + + _, err = evalVariable(p, "a1") + assertNoError(err, t, "Unable to find variable a1") + + _, err = evalVariable(p, "a2") + if err == nil { + t.Fatalf("Can eval out of scope variable a2") + } + }) +} + +func TestRecursiveStructure(t *testing.T) { + withTestProcess("testvariables2", t, func(p *Process, fixture protest.Fixture) { + assertNoError(p.Continue(), t, "Continue()") + v, err := evalVariable(p, "aas") + assertNoError(err, t, "EvalVariable()") + t.Logf("v: %v\n", v) + }) +} diff --git a/proc/variables.go b/proc/variables.go index 061aa9b4..22d0da2f 100644 --- a/proc/variables.go +++ b/proc/variables.go @@ -8,6 +8,7 @@ import ( "go/ast" "go/parser" "go/token" + "reflect" "strconv" "strings" "unsafe" @@ -29,16 +30,24 @@ const ( type Variable struct { Addr uintptr Name string - Value string - Type string - dwarfType dwarf.Type + DwarfType dwarf.Type + RealType dwarf.Type + Kind reflect.Kind thread *Thread - Len int64 - Cap int64 + Value interface{} + + Len int64 + Cap int64 + base uintptr stride int64 fieldType dwarf.Type + + Children []Variable + + loaded bool + Unreadable error } // Represents a runtime M (OS thread) structure. @@ -91,24 +100,40 @@ type EvalScope struct { CFA int64 } -func newVariable(name string, addr uintptr, dwarfType dwarf.Type, thread *Thread) (*Variable, error) { +func newVariable(name string, addr uintptr, dwarfType dwarf.Type, thread *Thread) *Variable { v := &Variable{ Name: name, Addr: addr, - dwarfType: dwarfType, + DwarfType: dwarfType, thread: thread, - Type: dwarfType.String(), } - switch t := dwarfType.(type) { + v.RealType = v.DwarfType + for { + if tt, ok := v.RealType.(*dwarf.TypedefType); ok { + v.RealType = tt.Type + } else { + break + } + } + + switch t := v.RealType.(type) { + case *dwarf.PtrType: + v.Kind = reflect.Ptr case *dwarf.StructType: - if strings.HasPrefix(t.StructName, "[]") { - err := v.loadSliceInfo(t) - if err != nil { - return nil, err + switch { + case t.StructName == "string": + v.Kind = reflect.String + case strings.HasPrefix(t.StructName, "[]"): + v.Kind = reflect.Slice + if v.Addr != 0 { + v.loadSliceInfo(t) } + default: + v.Kind = reflect.Struct } case *dwarf.ArrayType: + v.Kind = reflect.Array v.base = v.Addr v.Len = t.Count v.Cap = -1 @@ -118,12 +143,48 @@ func newVariable(name string, addr uintptr, dwarfType dwarf.Type, thread *Thread if t.Count > 0 { v.stride = t.ByteSize / t.Count } + case *dwarf.ComplexType: + switch t.ByteSize { + case 8: + v.Kind = reflect.Complex64 + case 16: + v.Kind = reflect.Complex128 + } + case *dwarf.IntType: + v.Kind = reflect.Int + case *dwarf.UintType: + v.Kind = reflect.Uint + case *dwarf.FloatType: + switch t.ByteSize { + case 4: + v.Kind = reflect.Float32 + case 8: + v.Kind = reflect.Float64 + } + case *dwarf.BoolType: + v.Kind = reflect.Bool + case *dwarf.FuncType: + v.Kind = reflect.Func + case *dwarf.VoidType: + v.Kind = reflect.Invalid + case *dwarf.UnspecifiedType: + v.Kind = reflect.Invalid + default: + v.Unreadable = fmt.Errorf("Unknown type: %T", t) } - return v, nil + return v +} + +func (v *Variable) clone() *Variable { + r := *v + return &r } func (v *Variable) toField(field *dwarf.StructField) (*Variable, error) { + if v.Unreadable != nil { + return v.clone(), nil + } if v.Addr == 0 { return nil, fmt.Errorf("%s is nil", v.Name) } @@ -137,7 +198,7 @@ func (v *Variable) toField(field *dwarf.StructField) (*Variable, error) { name = fmt.Sprintf("%s.%s", v.Name, field.Name) } } - return newVariable(name, uintptr(int64(v.Addr)+field.ByteOffset), field.Type, v.thread) + return newVariable(name, uintptr(int64(v.Addr)+field.ByteOffset), field.Type, v.thread), nil } func (scope *EvalScope) DwarfReader() *reader.Reader { @@ -271,7 +332,7 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) { if err != nil { return nil, err } - waitreason, err := thread.readString(uintptr(waitReasonAddr)) + waitreason, _, err := thread.readString(uintptr(waitReasonAddr)) if err != nil { return nil, err } @@ -355,11 +416,14 @@ func (scope *EvalScope) ExtractVariableInfo(name string) (*Variable, error) { return nil, origErr } v.Name = name + return v, nil } else { - for _, memberName := range memberNames { - v, err = v.structMember(memberName) - if err != nil { - return nil, err + if len(memberNames) > 0 { + for i := range memberNames { + v, err = v.structMember(memberNames[i]) + if err != nil { + return nil, err + } } } } @@ -372,8 +436,8 @@ func (scope *EvalScope) EvalVariable(name string) (*Variable, error) { if err != nil { return nil, err } - err = v.loadValue(true) - return v, err + v.loadValue() + return v, nil } // Sets the value of the named variable @@ -382,6 +446,9 @@ func (scope *EvalScope) SetVariable(name, value string) error { if err != nil { return err } + if v.Unreadable != nil { + return fmt.Errorf("Variable \"%s\" is unreadable: %v\n", name, v.Unreadable) + } return v.setValue(value) } @@ -391,8 +458,8 @@ func (scope *EvalScope) extractVariableFromEntry(entry *dwarf.Entry) (*Variable, if err != nil { return nil, err } - err = v.loadValue(true) - return v, err + v.loadValue() + return v, nil } func (scope *EvalScope) extractVarInfo(varName string) (*Variable, error) { @@ -459,8 +526,8 @@ func (dbp *Process) EvalPackageVariable(name string) (*Variable, error) { if err != nil { return nil, err } - err = v.loadValue(true) - return v, err + v.loadValue() + return v, nil } func (scope *EvalScope) packageVarAddr(name string) (*Variable, error) { @@ -483,13 +550,16 @@ func (scope *EvalScope) packageVarAddr(name string) (*Variable, error) { } func (v *Variable) structMember(memberName string) (*Variable, error) { - structVar, err := v.maybeDereference() - structVar.Name = v.Name - if err != nil { - return nil, err + if v.Unreadable != nil { + return v.clone(), nil } - structVar = structVar.resolveTypedefs() - switch t := structVar.dwarfType.(type) { + structVar := v.maybeDereference() + structVar.Name = v.Name + if structVar.Unreadable != nil { + return structVar, nil + } + + switch t := structVar.RealType.(type) { case *dwarf.StructType: for _, field := range t.Field { if field.Name != memberName { @@ -530,7 +600,7 @@ func (v *Variable) structMember(memberName string) (*Variable, error) { } return nil, fmt.Errorf("%s has no member %s", v.Name, memberName) default: - return nil, fmt.Errorf("%s type %s is not a struct", v.Name, structVar.dwarfType) + return nil, fmt.Errorf("%s type %s is not a struct", v.Name, structVar.DwarfType) } } @@ -570,142 +640,92 @@ func (scope *EvalScope) extractVarInfoFromEntry(entry *dwarf.Entry, rdr *reader. return nil, err } - return newVariable(n, uintptr(addr), t, scope.Thread) + return newVariable(n, uintptr(addr), t, scope.Thread), nil } // If v is a pointer a new variable is returned containing the value pointed by v. -func (v *Variable) maybeDereference() (*Variable, error) { - v = v.resolveTypedefs() +func (v *Variable) maybeDereference() *Variable { + if v.Unreadable != nil { + return v + } - switch t := v.dwarfType.(type) { + switch t := v.RealType.(type) { case *dwarf.PtrType: ptrval, err := v.thread.readUintRaw(uintptr(v.Addr), int64(v.thread.dbp.arch.PtrSize())) + r := newVariable("", uintptr(ptrval), t.Type, v.thread) if err != nil { - return nil, err + r.Unreadable = err } - return newVariable("", uintptr(ptrval), t.Type, v.thread) + return r default: - return v, nil + return v } } -// Returns a Variable with the same address but a concrete dwarfType. -func (v *Variable) resolveTypedefs() *Variable { - typ := v.dwarfType - for { - if tt, ok := typ.(*dwarf.TypedefType); ok { - typ = tt.Type - } else { - break - } - } - r := *v - r.dwarfType = typ - return &r -} - // Extracts the value of the variable at the given address. -func (v *Variable) loadValue(printStructName bool) (err error) { - v.Value, err = v.loadValueInternal(printStructName, 0) - return +func (v *Variable) loadValue() { + v.loadValueInternal(0) } -func (v *Variable) loadValueInternal(printStructName bool, recurseLevel int) (string, error) { - v = v.resolveTypedefs() - - switch t := v.dwarfType.(type) { - case *dwarf.PtrType: - ptrv, err := v.maybeDereference() - if err != nil { - return "", err - } - - if ptrv.Addr == 0 { - return fmt.Sprintf("%s nil", t.String()), nil - } - - // Don't increase the recursion level when dereferencing pointers - val, err := ptrv.loadValueInternal(printStructName, recurseLevel) - if err != nil { - return "", err - } - - return fmt.Sprintf("*%s", val), nil - case *dwarf.StructType: - switch { - case t.StructName == "string": - return v.thread.readString(uintptr(v.Addr)) - case strings.HasPrefix(t.StructName, "[]"): - return v.loadArrayValues(recurseLevel) - default: - // Recursively call extractValue to grab - // the value of all the members of the struct. - if recurseLevel <= maxVariableRecurse { - errcount := 0 - fields := make([]string, 0, len(t.Field)) - for i, field := range t.Field { - var ( - err error - val string - fieldvar *Variable - ) - - fieldvar, err = v.toField(field) - if err == nil { - val, err = fieldvar.loadValueInternal(printStructName, recurseLevel+1) - } - if err != nil { - errcount++ - val = fmt.Sprintf("", err.Error()) - } - - fields = append(fields, fmt.Sprintf("%s: %s", field.Name, val)) - - if errcount > maxErrCount { - fields = append(fields, fmt.Sprintf("...+%d more", len(t.Field)-i)) - } - } - if printStructName { - return fmt.Sprintf("%s {%s}", t.StructName, strings.Join(fields, ", ")), nil - } - return fmt.Sprintf("{%s}", strings.Join(fields, ", ")), nil - } - // no fields - if printStructName { - return fmt.Sprintf("%s {...}", t.StructName), nil - } - return "{...}", nil - } - case *dwarf.ArrayType: - return v.loadArrayValues(recurseLevel) - case *dwarf.ComplexType: - return v.readComplex(t.ByteSize) - case *dwarf.IntType: - return v.readInt(t.ByteSize) - case *dwarf.UintType: - return v.readUint(t.ByteSize) - case *dwarf.FloatType: - return v.readFloat(t.ByteSize) - case *dwarf.BoolType: - return v.readBool() - case *dwarf.FuncType: - return v.readFunctionPtr() - case *dwarf.VoidType: - return "(void)", nil - case *dwarf.UnspecifiedType: - return "(unknown)", nil - default: - fmt.Printf("Unknown type: %T\n", t) +func (v *Variable) loadValueInternal(recurseLevel int) { + if v.Unreadable != nil || v.loaded || v.Addr == 0 { + return } + v.loaded = true + switch v.Kind { + case reflect.Ptr: + v.Len = 1 + v.Children = []Variable{*v.maybeDereference()} + // Don't increase the recursion level when dereferencing pointers + v.Children[0].loadValueInternal(recurseLevel) - return "", fmt.Errorf("could not find value for type %s", v.dwarfType) + case reflect.String: + v.Value, v.Len, v.Unreadable = v.thread.readString(uintptr(v.Addr)) + + case reflect.Slice, reflect.Array: + v.loadArrayValues(recurseLevel) + + case reflect.Struct: + t := v.RealType.(*dwarf.StructType) + v.Len = int64(len(t.Field)) + // Recursively call extractValue to grab + // the value of all the members of the struct. + if recurseLevel <= maxVariableRecurse { + v.Children = make([]Variable, 0, len(t.Field)) + for i, field := range t.Field { + f, _ := v.toField(field) + v.Children = append(v.Children, *f) + v.Children[i].Name = field.Name + v.Children[i].loadValueInternal(recurseLevel + 1) + } + } + + case reflect.Complex64, reflect.Complex128: + v.readComplex(v.RealType.(*dwarf.ComplexType).ByteSize) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v.Value, v.Unreadable = v.thread.readIntRaw(v.Addr, v.RealType.(*dwarf.IntType).ByteSize) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v.Value, v.Unreadable = v.thread.readUintRaw(v.Addr, v.RealType.(*dwarf.UintType).ByteSize) + case reflect.Bool: + val, err := v.thread.readMemory(v.Addr, 1) + v.Unreadable = err + if err == nil { + v.Value = val[0] != 0 + } + case reflect.Float32, reflect.Float64: + v.Value, v.Unreadable = v.readFloatRaw(v.RealType.(*dwarf.FloatType).ByteSize) + case reflect.Func: + v.readFunctionPtr() + case reflect.Map: + fallthrough + default: + v.Unreadable = fmt.Errorf("unknown or unsupported kind: \"%s\"", v.Kind.String()) + } } func (v *Variable) setValue(value string) error { - v = v.resolveTypedefs() - - switch t := v.dwarfType.(type) { + switch t := v.RealType.(type) { case *dwarf.PtrType: return v.writeUint(false, value, int64(v.thread.dbp.arch.PtrSize())) case *dwarf.ComplexType: @@ -719,22 +739,22 @@ func (v *Variable) setValue(value string) error { case *dwarf.BoolType: return v.writeBool(value) default: - return fmt.Errorf("Can not set value of variables of type: %T\n", t) + return fmt.Errorf("Can not set value of variables of kind: %s\n", v.RealType.String()) } } -func (thread *Thread) readString(addr uintptr) (string, error) { +func (thread *Thread) readString(addr uintptr) (string, int64, error) { // string data structure is always two ptrs in size. Addr, followed by len // http://research.swtch.com/godata // read len val, err := thread.readMemory(addr+uintptr(thread.dbp.arch.PtrSize()), thread.dbp.arch.PtrSize()) if err != nil { - return "", fmt.Errorf("could not read string len %s", err) + return "", 0, fmt.Errorf("could not read string len %s", err) } - strlen := int(binary.LittleEndian.Uint64(val)) + strlen := int64(binary.LittleEndian.Uint64(val)) if strlen < 0 { - return "", fmt.Errorf("invalid length: %d", strlen) + return "", 0, fmt.Errorf("invalid length: %d", strlen) } count := strlen @@ -745,28 +765,24 @@ func (thread *Thread) readString(addr uintptr) (string, error) { // read addr val, err = thread.readMemory(addr, thread.dbp.arch.PtrSize()) if err != nil { - return "", fmt.Errorf("could not read string pointer %s", err) + return "", 0, fmt.Errorf("could not read string pointer %s", err) } addr = uintptr(binary.LittleEndian.Uint64(val)) if addr == 0 { - return "", nil + return "", 0, nil } - val, err = thread.readMemory(addr, count) + val, err = thread.readMemory(addr, int(count)) if err != nil { - return "", fmt.Errorf("could not read string at %#v due to %s", addr, err) + return "", 0, fmt.Errorf("could not read string at %#v due to %s", addr, err) } retstr := *(*string)(unsafe.Pointer(&val)) - if count != strlen { - retstr = retstr + fmt.Sprintf("...+%d more", strlen-count) - } - - return retstr, nil + return retstr, strlen, nil } -func (v *Variable) loadSliceInfo(t *dwarf.StructType) error { +func (v *Variable) loadSliceInfo(t *dwarf.StructType) { var err error for _, f := range t.Field { switch f.Name { @@ -778,31 +794,30 @@ func (v *Variable) loadSliceInfo(t *dwarf.StructType) error { // Dereference array type to get value type ptrType, ok := f.Type.(*dwarf.PtrType) if !ok { - return fmt.Errorf("Invalid type %s in slice array", f.Type) + v.Unreadable = fmt.Errorf("Invalid type %s in slice array", f.Type) + return } v.fieldType = ptrType.Type } case "len": - lstrAddr, err := v.toField(f) + lstrAddr, _ := v.toField(f) + lstrAddr.loadValue() + err = lstrAddr.Unreadable if err == nil { - err = lstrAddr.loadValue(true) - } - if err == nil { - v.Len, err = strconv.ParseInt(lstrAddr.Value, 10, 64) + v.Len = lstrAddr.Value.(int64) } case "cap": - cstrAddr, err := v.toField(f) + cstrAddr, _ := v.toField(f) + cstrAddr.loadValue() + err = cstrAddr.Unreadable if err == nil { - err = cstrAddr.loadValue(true) - } - if err == nil { - v.Cap, err = strconv.ParseInt(cstrAddr.Value, 10, 64) + v.Cap = cstrAddr.Value.(int64) } } - } - - if err != nil { - return nil + if err != nil { + v.Unreadable = err + return + } } v.stride = v.fieldType.Size() @@ -810,45 +825,37 @@ func (v *Variable) loadSliceInfo(t *dwarf.StructType) error { v.stride = int64(v.thread.dbp.arch.PtrSize()) } - return nil + return } -func (v *Variable) loadArrayValues(recurseLevel int) (string, error) { - vals := make([]string, 0) +func (v *Variable) loadArrayValues(recurseLevel int) { + if v.Unreadable != nil { + return + } + errcount := 0 for i := int64(0); i < v.Len; i++ { // Cap number of elements if i >= maxArrayValues { - vals = append(vals, fmt.Sprintf("...+%d more", v.Len-maxArrayValues)) break } - var val string - fieldvar, err := newVariable("", uintptr(int64(v.base)+(i*v.stride)), v.fieldType, v.thread) - if err == nil { - val, err = fieldvar.loadValueInternal(false, recurseLevel+1) - } - if err != nil { + fieldvar := newVariable("", uintptr(int64(v.base)+(i*v.stride)), v.fieldType, v.thread) + fieldvar.loadValueInternal(recurseLevel + 1) + + if fieldvar.Unreadable != nil { errcount++ - val = fmt.Sprintf("", err.Error()) } - vals = append(vals, val) + v.Children = append(v.Children, *fieldvar) if errcount > maxErrCount { - vals = append(vals, fmt.Sprintf("...+%d more", v.Len-i)) break } } - - if v.Cap < 0 { - return fmt.Sprintf("%s [%s]", v.dwarfType, strings.Join(vals, ",")), nil - } else { - return fmt.Sprintf("[]%s len: %d, cap: %d, [%s]", v.fieldType, v.Len, v.Cap, strings.Join(vals, ",")), nil - } } -func (v *Variable) readComplex(size int64) (string, error) { +func (v *Variable) readComplex(size int64) { var fs int64 switch size { case 8: @@ -856,19 +863,18 @@ func (v *Variable) readComplex(size int64) (string, error) { case 16: fs = 8 default: - return "", fmt.Errorf("invalid size (%d) for complex type", size) + v.Unreadable = fmt.Errorf("invalid size (%d) for complex type", size) + return } - r, err := v.readFloat(fs) - if err != nil { - return "", err - } - imagvar := *v - imagvar.Addr += uintptr(fs) - i, err := imagvar.readFloat(fs) - if err != nil { - return "", err - } - return fmt.Sprintf("(%s + %si)", r, i), nil + + ftyp := &dwarf.FloatType{BasicType: dwarf.BasicType{CommonType: dwarf.CommonType{ByteSize: fs, Name: fmt.Sprintf("float%d", fs)}, BitSize: fs * 8, BitOffset: 0}} + + realvar := newVariable("real", v.Addr, ftyp, v.thread) + imagvar := newVariable("imaginary", v.Addr+uintptr(fs), ftyp, v.thread) + realvar.loadValue() + imagvar.loadValue() + v.Len = 2 + v.Children = []Variable{*realvar, *imagvar} } func (v *Variable) writeComplex(value string, size int64) error { @@ -946,14 +952,6 @@ func (v *Variable) writeComplex(value string, size int64) error { return imagaddr.writeFloatRaw(imag, int64(size/2)) } -func (v *Variable) readInt(size int64) (string, error) { - n, err := v.thread.readIntRaw(v.Addr, size) - if err != nil { - return "", err - } - return strconv.FormatInt(n, 10), nil -} - func (thread *Thread) readIntRaw(addr uintptr, size int64) (int64, error) { var n int64 @@ -976,14 +974,6 @@ func (thread *Thread) readIntRaw(addr uintptr, size int64) (int64, error) { return n, nil } -func (v *Variable) readUint(size int64) (string, error) { - n, err := v.thread.readUintRaw(v.Addr, size) - if err != nil { - return "", err - } - return strconv.FormatUint(n, 10), nil -} - func (v *Variable) writeUint(signed bool, value string, size int64) error { var ( n uint64 @@ -1039,10 +1029,10 @@ func (thread *Thread) readUintRaw(addr uintptr, size int64) (uint64, error) { return n, nil } -func (v *Variable) readFloat(size int64) (string, error) { +func (v *Variable) readFloatRaw(size int64) (float64, error) { val, err := v.thread.readMemory(v.Addr, int(size)) if err != nil { - return "", err + return 0.0, err } buf := bytes.NewBuffer(val) @@ -1050,14 +1040,14 @@ func (v *Variable) readFloat(size int64) (string, error) { case 4: n := float32(0) binary.Read(buf, binary.LittleEndian, &n) - return strconv.FormatFloat(float64(n), 'f', -1, int(size)*8), nil + return float64(n), nil case 8: n := float64(0) binary.Read(buf, binary.LittleEndian, &n) - return strconv.FormatFloat(n, 'f', -1, int(size)*8), nil + return n, nil } - return "", fmt.Errorf("could not read float") + return 0.0, fmt.Errorf("could not read float") } func (v *Variable) writeFloat(value string, size int64) error { @@ -1084,19 +1074,6 @@ func (v *Variable) writeFloatRaw(f float64, size int64) error { return err } -func (v *Variable) readBool() (string, error) { - val, err := v.thread.readMemory(v.Addr, 1) - if err != nil { - return "", err - } - - if val[0] == 0 { - return "false", nil - } - - return "true", nil -} - func (v *Variable) writeBool(value string) error { b, err := strconv.ParseBool(value) if err != nil { @@ -1110,30 +1087,34 @@ func (v *Variable) writeBool(value string) error { return err } -func (v *Variable) readFunctionPtr() (string, error) { +func (v *Variable) readFunctionPtr() { val, err := v.thread.readMemory(v.Addr, v.thread.dbp.arch.PtrSize()) if err != nil { - return "", err + v.Unreadable = err + return } // dereference pointer to find function pc fnaddr := uintptr(binary.LittleEndian.Uint64(val)) if fnaddr == 0 { - return "nil", nil + v.Unreadable = err + return } val, err = v.thread.readMemory(fnaddr, v.thread.dbp.arch.PtrSize()) if err != nil { - return "", err + v.Unreadable = err + return } funcAddr := binary.LittleEndian.Uint64(val) fn := v.thread.dbp.goSymTable.PCToFunc(uint64(funcAddr)) if fn == nil { - return "", fmt.Errorf("could not find function for %#v", funcAddr) + v.Unreadable = fmt.Errorf("could not find function for %#v", funcAddr) + return } - return fn.Name, nil + v.Value = fn.Name } // Fetches all variables of a specific type in the current function scope diff --git a/proc/variables_test.go b/proc/variables_test.go deleted file mode 100644 index 14fcb449..00000000 --- a/proc/variables_test.go +++ /dev/null @@ -1,419 +0,0 @@ -package proc - -import ( - "fmt" - "sort" - "strconv" - "testing" - - protest "github.com/derekparker/delve/proc/test" -) - -type varTest struct { - name string - value string - setTo string - varType string - err error -} - -func assertVariable(t *testing.T, variable *Variable, expected varTest) { - if variable.Name != expected.name { - t.Fatalf("Expected %s got %s\n", expected.name, variable.Name) - } - - if variable.Type != expected.varType { - t.Fatalf("Expected %s got %s (for variable %s)\n", expected.varType, variable.Type, expected.name) - } - - if variable.Value != expected.value { - t.Fatalf("Expected %#v got %#v (for variable %s)\n", expected.value, variable.Value, expected.name) - } -} - -func evalVariable(p *Process, symbol string) (*Variable, error) { - scope, err := p.CurrentThread.Scope() - if err != nil { - return nil, err - } - return scope.EvalVariable(symbol) -} - -func (tc *varTest) settable() bool { - return tc.setTo != "" -} - -func (tc *varTest) afterSet() varTest { - r := *tc - r.value = r.setTo - return r -} - -func setVariable(p *Process, symbol, value string) error { - scope, err := p.CurrentThread.Scope() - if err != nil { - return err - } - return scope.SetVariable(symbol, value) -} - -const varTestBreakpointLineNumber = 59 - -func TestVariableEvaluation(t *testing.T) { - testcases := []varTest{ - {"a1", "foofoofoofoofoofoo", "", "struct string", nil}, - {"a10", "ofo", "", "struct string", nil}, - {"a11", "[3]main.FooBar [{Baz: 1, Bur: a},{Baz: 2, Bur: b},{Baz: 3, Bur: c}]", "", "[3]main.FooBar", nil}, - {"a12", "[]main.FooBar len: 2, cap: 2, [{Baz: 4, Bur: d},{Baz: 5, Bur: e}]", "", "struct []main.FooBar", nil}, - {"a13", "[]*main.FooBar len: 3, cap: 3, [*{Baz: 6, Bur: f},*{Baz: 7, Bur: g},*{Baz: 8, Bur: h}]", "", "struct []*main.FooBar", nil}, - {"a2", "6", "10", "int", nil}, - {"a3", "7.23", "3.1", "float64", nil}, - {"a4", "[2]int [1,2]", "", "[2]int", nil}, - {"a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "", "struct []int", nil}, - {"a6", "main.FooBar {Baz: 8, Bur: word}", "", "main.FooBar", nil}, - {"a7", "*main.FooBar {Baz: 5, Bur: strum}", "", "*main.FooBar", nil}, - {"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "", "main.FooBar2", nil}, - {"a9", "*main.FooBar nil", "", "*main.FooBar", nil}, - {"baz", "bazburzum", "", "struct string", nil}, - {"neg", "-1", "-20", "int", nil}, - {"f32", "1.2", "1.1", "float32", nil}, - {"c64", "(1 + 2i)", "(4 + 5i)", "complex64", nil}, - {"c128", "(2 + 3i)", "(6.3 + 7i)", "complex128", nil}, - {"a6.Baz", "8", "20", "int", nil}, - {"a7.Baz", "5", "25", "int", nil}, - {"a8.Baz", "feh", "", "struct string", nil}, - {"a9.Baz", "nil", "", "int", fmt.Errorf("a9 is nil")}, - {"a9.NonExistent", "nil", "", "int", fmt.Errorf("a9 has no member NonExistent")}, - {"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "", "main.FooBar2", nil}, // reread variable after member - {"i32", "[2]int32 [1,2]", "", "[2]int32", nil}, - {"b1", "true", "false", "bool", nil}, - {"b2", "false", "true", "bool", nil}, - {"i8", "1", "2", "int8", nil}, - {"u16", "65535", "0", "uint16", nil}, - {"u32", "4294967295", "1", "uint32", nil}, - {"u64", "18446744073709551615", "2", "uint64", nil}, - {"u8", "255", "3", "uint8", nil}, - {"up", "5", "4", "uintptr", nil}, - {"f", "main.barfoo", "", "func()", nil}, - {"ba", "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "", "struct []int", nil}, - {"ms", "main.Nest {Level: 0, Nest: *main.Nest {Level: 1, Nest: *main.Nest {...}}}", "", "main.Nest", nil}, - {"ms.Nest.Nest", "*main.Nest {Level: 2, Nest: *main.Nest {Level: 3, Nest: *main.Nest {...}}}", "", "*main.Nest", nil}, - {"ms.Nest.Nest.Nest.Nest.Nest", "*main.Nest nil", "", "*main.Nest", nil}, - {"ms.Nest.Nest.Nest.Nest.Nest.Nest", "", "", "*main.Nest", fmt.Errorf("ms.Nest.Nest.Nest.Nest.Nest is nil")}, - {"main.p1", "10", "12", "int", nil}, - {"p1", "10", "13", "int", nil}, - {"NonExistent", "", "", "", fmt.Errorf("could not find symbol value for NonExistent")}, - } - - withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) { - pc, _, _ := p.goSymTable.LineToPC(fixture.Source, varTestBreakpointLineNumber) - - _, err := p.SetBreakpoint(pc) - assertNoError(err, t, "SetBreakpoint() returned an error") - - err = p.Continue() - assertNoError(err, t, "Continue() returned an error") - - for _, tc := range testcases { - variable, err := evalVariable(p, tc.name) - if tc.err == nil { - assertNoError(err, t, "EvalVariable() returned an error") - assertVariable(t, variable, tc) - } else { - if tc.err.Error() != err.Error() { - t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error()) - } - } - - if tc.settable() { - assertNoError(setVariable(p, tc.name, tc.setTo), t, "SetVariable()") - variable, err = evalVariable(p, tc.name) - assertNoError(err, t, "EvalVariable()") - assertVariable(t, variable, tc.afterSet()) - - assertNoError(setVariable(p, tc.name, tc.value), t, "SetVariable()") - variable, err := evalVariable(p, tc.name) - assertNoError(err, t, "EvalVariable()") - assertVariable(t, variable, tc) - } - } - }) -} - -func TestVariableFunctionScoping(t *testing.T) { - withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) { - pc, _, _ := p.goSymTable.LineToPC(fixture.Source, varTestBreakpointLineNumber) - - _, err := p.SetBreakpoint(pc) - assertNoError(err, t, "SetBreakpoint() returned an error") - - err = p.Continue() - assertNoError(err, t, "Continue() returned an error") - p.ClearBreakpoint(pc) - - _, err = evalVariable(p, "a1") - assertNoError(err, t, "Unable to find variable a1") - - _, err = evalVariable(p, "a2") - assertNoError(err, t, "Unable to find variable a1") - - // Move scopes, a1 exists here by a2 does not - pc, _, _ = p.goSymTable.LineToPC(fixture.Source, 23) - - _, err = p.SetBreakpoint(pc) - assertNoError(err, t, "SetBreakpoint() returned an error") - - err = p.Continue() - assertNoError(err, t, "Continue() returned an error") - - _, err = evalVariable(p, "a1") - assertNoError(err, t, "Unable to find variable a1") - - _, err = evalVariable(p, "a2") - if err == nil { - t.Fatalf("Can eval out of scope variable a2") - } - }) -} - -type varArray []*Variable - -// Len is part of sort.Interface. -func (s varArray) Len() int { - return len(s) -} - -// Swap is part of sort.Interface. -func (s varArray) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} - -// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter. -func (s varArray) Less(i, j int) bool { - return s[i].Name < s[j].Name -} - -func TestLocalVariables(t *testing.T) { - testcases := []struct { - fn func(*EvalScope) ([]*Variable, error) - output []varTest - }{ - {(*EvalScope).LocalVariables, - []varTest{ - {"a1", "foofoofoofoofoofoo", "", "struct string", nil}, - {"a10", "ofo", "", "struct string", nil}, - {"a11", "[3]main.FooBar [{Baz: 1, Bur: a},{Baz: 2, Bur: b},{Baz: 3, Bur: c}]", "", "[3]main.FooBar", nil}, - {"a12", "[]main.FooBar len: 2, cap: 2, [{Baz: 4, Bur: d},{Baz: 5, Bur: e}]", "", "struct []main.FooBar", nil}, - {"a13", "[]*main.FooBar len: 3, cap: 3, [*{Baz: 6, Bur: f},*{Baz: 7, Bur: g},*{Baz: 8, Bur: h}]", "", "struct []*main.FooBar", nil}, - {"a2", "6", "", "int", nil}, - {"a3", "7.23", "", "float64", nil}, - {"a4", "[2]int [1,2]", "", "[2]int", nil}, - {"a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "", "struct []int", nil}, - {"a6", "main.FooBar {Baz: 8, Bur: word}", "", "main.FooBar", nil}, - {"a7", "*main.FooBar {Baz: 5, Bur: strum}", "", "*main.FooBar", nil}, - {"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "", "main.FooBar2", nil}, - {"a9", "*main.FooBar nil", "", "*main.FooBar", nil}, - {"b1", "true", "", "bool", nil}, - {"b2", "false", "", "bool", nil}, - {"ba", "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "", "struct []int", nil}, - {"c128", "(2 + 3i)", "", "complex128", nil}, - {"c64", "(1 + 2i)", "", "complex64", nil}, - {"f", "main.barfoo", "", "func()", nil}, - {"f32", "1.2", "", "float32", nil}, - {"i32", "[2]int32 [1,2]", "", "[2]int32", nil}, - {"i8", "1", "", "int8", nil}, - {"ms", "main.Nest {Level: 0, Nest: *main.Nest {Level: 1, Nest: *main.Nest {...}}}", "", "main.Nest", nil}, - {"neg", "-1", "", "int", nil}, - {"u16", "65535", "", "uint16", nil}, - {"u32", "4294967295", "", "uint32", nil}, - {"u64", "18446744073709551615", "", "uint64", nil}, - {"u8", "255", "", "uint8", nil}, - {"up", "5", "", "uintptr", nil}}}, - {(*EvalScope).FunctionArguments, - []varTest{ - {"bar", "main.FooBar {Baz: 10, Bur: lorem}", "", "main.FooBar", nil}, - {"baz", "bazburzum", "", "struct string", nil}}}, - } - - withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) { - pc, _, _ := p.goSymTable.LineToPC(fixture.Source, varTestBreakpointLineNumber) - - _, err := p.SetBreakpoint(pc) - assertNoError(err, t, "SetBreakpoint() returned an error") - - err = p.Continue() - assertNoError(err, t, "Continue() returned an error") - - for _, tc := range testcases { - scope, err := p.CurrentThread.Scope() - assertNoError(err, t, "AsScope()") - vars, err := tc.fn(scope) - assertNoError(err, t, "LocalVariables() returned an error") - - sort.Sort(varArray(vars)) - - if len(tc.output) != len(vars) { - t.Fatalf("Invalid variable count. Expected %d got %d.", len(tc.output), len(vars)) - } - - for i, variable := range vars { - assertVariable(t, variable, tc.output[i]) - } - } - }) -} - -func TestRecursiveStructure(t *testing.T) { - withTestProcess("testvariables2", t, func(p *Process, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") - v, err := evalVariable(p, "aas") - assertNoError(err, t, "EvalVariable()") - t.Logf("v: %v\n", v) - }) -} - -func TestFrameEvaluation(t *testing.T) { - withTestProcess("goroutinestackprog", t, func(p *Process, fixture protest.Fixture) { - _, err := setFunctionBreakpoint(p, "main.stacktraceme") - assertNoError(err, t, "setFunctionBreakpoint") - assertNoError(p.Continue(), t, "Continue()") - - /**** Testing evaluation on goroutines ****/ - gs, err := p.GoroutinesInfo() - assertNoError(err, t, "GoroutinesInfo") - found := make([]bool, 10) - for _, g := range gs { - frame := -1 - frames, err := p.GoroutineStacktrace(g, 10) - assertNoError(err, t, "GoroutineStacktrace()") - for i := range frames { - if frames[i].Call.Fn != nil && frames[i].Call.Fn.Name == "main.agoroutine" { - frame = i - break - } - } - - if frame < 0 { - t.Logf("Goroutine %d: could not find correct frame", g.Id) - continue - } - - scope, err := p.ConvertEvalScope(g.Id, frame) - assertNoError(err, t, "ConvertEvalScope()") - t.Logf("scope = %v", scope) - v, err := scope.EvalVariable("i") - t.Logf("v = %v", v) - if err != nil { - t.Logf("Goroutine %d: %v\n", g.Id, err) - continue - } - i, err := strconv.Atoi(v.Value) - assertNoError(err, t, fmt.Sprintf("strconv.Atoi(%s)", v.Value)) - found[i] = true - } - - for i := range found { - if !found[i] { - t.Fatalf("Goroutine %d not found\n", i) - } - } - - /**** Testing evaluation on frames ****/ - assertNoError(p.Continue(), t, "Continue() 2") - g, err := p.CurrentThread.GetG() - assertNoError(err, t, "GetG()") - - for i := 0; i <= 3; i++ { - scope, err := p.ConvertEvalScope(g.Id, i+1) - assertNoError(err, t, fmt.Sprintf("ConvertEvalScope() on frame %d", i+1)) - v, err := scope.EvalVariable("n") - assertNoError(err, t, fmt.Sprintf("EvalVariable() on frame %d", i+1)) - n, err := strconv.Atoi(v.Value) - assertNoError(err, t, fmt.Sprintf("strconv.Atoi(%s) on frame %d", v.Value, i+1)) - t.Logf("frame %d n %d\n", i+1, n) - if n != 3-i { - t.Fatalf("On frame %d value of n is %d (not %d)", i+1, n, 3-i) - } - } - }) -} - -func TestComplexSetting(t *testing.T) { - withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) { - pc, _, _ := p.goSymTable.LineToPC(fixture.Source, varTestBreakpointLineNumber) - - _, err := p.SetBreakpoint(pc) - assertNoError(err, t, "SetBreakpoint() returned an error") - - err = p.Continue() - assertNoError(err, t, "Continue() returned an error") - - h := func(setExpr, value string) { - assertNoError(setVariable(p, "c128", setExpr), t, "SetVariable()") - variable, err := evalVariable(p, "c128") - assertNoError(err, t, "EvalVariable()") - if variable.Value != value { - t.Fatalf("Wrong value of c128: \"%s\", expected \"%s\" after setting it to \"%s\"", variable.Value, value, setExpr) - } - } - - h("3.2i", "(0 + 3.2i)") - h("1.1", "(1.1 + 0i)") - h("1 + 3.3i", "(1 + 3.3i)") - h("complex128(1.2, 3.4)", "(1.2 + 3.4i)") - }) -} - -func TestPointerSetting(t *testing.T) { - withTestProcess("testvariables3", t, func(p *Process, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue() returned an error") - - pval := func(value string) { - variable, err := evalVariable(p, "p1") - assertNoError(err, t, "EvalVariable()") - if variable.Value != value { - t.Fatalf("Wrong value of p1, \"%s\" expected \"%s\"", variable.Value, value) - } - } - - pval("*1") - - // change p1 to point to i2 - scope, err := p.CurrentThread.Scope() - assertNoError(err, t, "Scope()") - i2addr, err := scope.ExtractVariableInfo("i2") - assertNoError(err, t, "EvalVariableAddr()") - assertNoError(setVariable(p, "p1", strconv.Itoa(int(i2addr.Addr))), t, "SetVariable()") - pval("*2") - - // change the value of i2 check that p1 also changes - assertNoError(setVariable(p, "i2", "5"), t, "SetVariable()") - pval("*5") - }) -} - -func TestEmbeddedStruct(t *testing.T) { - withTestProcess("testvariables4", t, func(p *Process, fixture protest.Fixture) { - testcases := []varTest{ - {"b.val", "-314", "", "int", nil}, - {"b.A.val", "-314", "", "int", nil}, - {"b.a.val", "42", "", "int", nil}, - {"b.ptr.val", "1337", "", "int", nil}, - {"b.C.s", "hello", "", "struct string", nil}, - {"b.s", "hello", "", "struct string", nil}, - } - assertNoError(p.Continue(), t, "Continue()") - - for _, tc := range testcases { - variable, err := evalVariable(p, tc.name) - if tc.err == nil { - assertNoError(err, t, "EvalVariable() returned an error") - assertVariable(t, variable, tc) - } else { - if tc.err.Error() != err.Error() { - t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error()) - } - } - } - }) -} diff --git a/service/api/conversions.go b/service/api/conversions.go index c66e876a..60e302c2 100644 --- a/service/api/conversions.go +++ b/service/api/conversions.go @@ -1,7 +1,9 @@ package api import ( + "debug/dwarf" "debug/gosym" + "fmt" "strconv" "github.com/derekparker/delve/proc" @@ -57,12 +59,43 @@ func ConvertThread(th *proc.Thread) *Thread { } // convertVar converts an internal variable to an API Variable. -func ConvertVar(v *proc.Variable) Variable { - return Variable{ - Name: v.Name, - Value: v.Value, - Type: v.Type, +func ConvertVar(v *proc.Variable) *Variable { + r := Variable{ + Addr: v.Addr, + Name: v.Name, + Kind: v.Kind, + Len: v.Len, + Cap: v.Cap, } + + if v.DwarfType != nil { + r.Type = v.DwarfType.String() + } + + if v.RealType != nil { + r.RealType = v.RealType.String() + } + + if v.Unreadable != nil { + r.Unreadable = v.Unreadable.Error() + } + + switch typ := v.RealType.(type) { + case *dwarf.FloatType: + r.Value = strconv.FormatFloat(v.Value.(float64), 'f', -1, int(typ.Size()*8)) + default: + if v.Value != nil { + r.Value = fmt.Sprintf("%v", v.Value) + } + } + + r.Children = make([]Variable, len(v.Children)) + + for i := range v.Children { + r.Children[i] = *ConvertVar(&v.Children[i]) + } + + return &r } func ConvertFunction(fn *gosym.Func) *Function { diff --git a/service/api/prettyprint.go b/service/api/prettyprint.go new file mode 100644 index 00000000..df1d118e --- /dev/null +++ b/service/api/prettyprint.go @@ -0,0 +1,262 @@ +package api + +import ( + "bytes" + "fmt" + "reflect" +) + +const ( + // strings longer than this will cause slices, arrays and structs to be printed on multiple lines when newlines is enabled + maxShortStringLen = 7 + // string used for one indentation level (when printing on multiple lines) + indentString = "\t" +) + +// Returns a representation of v on a single line +func (v *Variable) SinglelineString() string { + var buf bytes.Buffer + v.writeTo(&buf, false, true, "") + return buf.String() +} + +// Returns a representation of v on multiple lines +func (v *Variable) MultilineString(indent string) string { + var buf bytes.Buffer + buf.WriteString(indent) + v.writeTo(&buf, true, true, indent) + return buf.String() +} + +func (v *Variable) writeTo(buf *bytes.Buffer, newlines, includeType bool, indent string) { + if v.Unreadable != "" { + fmt.Fprintf(buf, "(unreadable %s)", v.Unreadable) + return + } + + if v.Addr == 0 { + fmt.Fprintf(buf, "%s nil", v.Type) + return + } + + switch v.Kind { + case reflect.Slice: + v.writeSliceTo(buf, newlines, includeType, indent) + case reflect.Array: + v.writeArrayTo(buf, newlines, includeType, indent) + case reflect.Ptr: + fmt.Fprintf(buf, "*") + v.Children[0].writeTo(buf, newlines, includeType, indent) + case reflect.String: + v.writeStringTo(buf) + case reflect.Struct: + v.writeStructTo(buf, newlines, includeType, indent) + case reflect.Map: + v.writeMapTo(buf, newlines, includeType, indent) + case reflect.Func: + fmt.Fprintf(buf, "%s", v.Value) + case reflect.Complex64, reflect.Complex128: + switch v.RealType { + case "complex64": + fmt.Fprintf(buf, "(%s + %si)", v.Children[0].Value, v.Children[1].Value) + case "complex128": + fmt.Fprintf(buf, "(%s + %si)", v.Children[0].Value, v.Children[1].Value) + } + default: + if v.Value != "" { + buf.Write([]byte(v.Value)) + } else { + fmt.Fprintf(buf, "(unknown %s)", v.Kind) + } + } +} + +func (v *Variable) writeStringTo(buf *bytes.Buffer) { + s := v.Value + if len(s) != int(v.Len) { + s = fmt.Sprintf("%s...+%d more", s, int(v.Len)-len(s)) + } + fmt.Fprintf(buf, "%q", s) +} + +func (v *Variable) writeSliceTo(buf *bytes.Buffer, newlines, includeType bool, indent string) { + if includeType { + fmt.Fprintf(buf, "%s len: %d, cap: %d, ", v.Type[len("struct "):], v.Len, v.Cap) + } + v.writeSliceOrArrayTo(buf, newlines, indent) +} + +func (v *Variable) writeArrayTo(buf *bytes.Buffer, newlines, includeType bool, indent string) { + if includeType { + fmt.Fprintf(buf, "%s ", v.Type) + } + v.writeSliceOrArrayTo(buf, newlines, indent) +} + +func (v *Variable) writeStructTo(buf *bytes.Buffer, newlines, includeType bool, indent string) { + if int(v.Len) != len(v.Children) { + fmt.Fprintf(buf, "(*%s)(0x%x)", v.Type, v.Addr) + return + } + + if includeType { + fmt.Fprintf(buf, "%s ", v.Type) + } + + nl := v.shouldNewlineStruct(newlines) + + fmt.Fprintf(buf, "{") + + for i := range v.Children { + if nl { + fmt.Fprintf(buf, "\n%s%s", indent, indentString) + } + fmt.Fprintf(buf, "%s: ", v.Children[i].Name) + v.Children[i].writeTo(buf, nl, true, indent+indentString) + if i != len(v.Children)-1 || nl { + fmt.Fprintf(buf, ",") + if !nl { + fmt.Fprintf(buf, " ") + } + } + } + + if nl { + fmt.Fprintf(buf, "\n%s", indent) + } + fmt.Fprintf(buf, "}") +} + +func (v *Variable) writeMapTo(buf *bytes.Buffer, newlines, includeType bool, indent string) { + if includeType { + fmt.Fprintf(buf, "%s ", v.Type) + } + + nl := newlines && (len(v.Children) > 0) + + fmt.Fprintf(buf, "[") + + for i := 0; i < len(v.Children); i += 2 { + key := &v.Children[i] + value := &v.Children[i+1] + + if nl { + fmt.Fprintf(buf, "\n%s%s", indent, indentString) + } + + key.writeTo(buf, false, false, indent+indentString) + fmt.Fprintf(buf, ": ") + value.writeTo(buf, nl, false, indent+indentString) + if i != len(v.Children)-1 || nl { + fmt.Fprintf(buf, ", ") + } + } + + if len(v.Children) != int(v.Len) { + if nl { + fmt.Fprintf(buf, "\n%s%s", indent, indentString) + } else { + fmt.Fprintf(buf, ",") + } + fmt.Fprintf(buf, "...+%d more", int(v.Len)-len(v.Children)) + } + + if nl { + fmt.Fprintf(buf, "\n%s", indent) + } + fmt.Fprintf(buf, "]") +} + +func (v *Variable) shouldNewlineArray(newlines bool) bool { + if !newlines || len(v.Children) == 0 { + return false + } + + kind, hasptr := (&v.Children[0]).recursiveKind() + + switch kind { + case reflect.Slice, reflect.Array, reflect.Struct, reflect.Map: + return true + case reflect.String: + if hasptr { + return true + } + for i := range v.Children { + if len(v.Children[i].Value) > maxShortStringLen { + return true + } + } + return false + default: + return false + } +} + +func (v *Variable) recursiveKind() (reflect.Kind, bool) { + hasptr := false + var kind reflect.Kind + for { + kind = v.Kind + if kind == reflect.Ptr { + hasptr = true + v = &(v.Children[0]) + } else { + break + } + } + return kind, hasptr +} + +func (v *Variable) shouldNewlineStruct(newlines bool) bool { + if !newlines || len(v.Children) == 0 { + return false + } + + for i := range v.Children { + kind, hasptr := (&v.Children[i]).recursiveKind() + + switch kind { + case reflect.Slice, reflect.Array, reflect.Struct, reflect.Map: + return true + case reflect.String: + if hasptr { + return true + } + if len(v.Children[i].Value) > maxShortStringLen { + return true + } + } + } + + return false +} + +func (v *Variable) writeSliceOrArrayTo(buf *bytes.Buffer, newlines bool, indent string) { + nl := v.shouldNewlineArray(newlines) + fmt.Fprintf(buf, "[") + + for i := range v.Children { + if nl { + fmt.Fprintf(buf, "\n%s%s", indent, indentString) + } + v.Children[i].writeTo(buf, nl, false, indent+indentString) + if i != len(v.Children)-1 || nl { + fmt.Fprintf(buf, ",") + } + } + + if len(v.Children) != int(v.Len) { + if nl { + fmt.Fprintf(buf, "\n%s%s", indent, indentString) + } else { + fmt.Fprintf(buf, ",") + } + fmt.Fprintf(buf, "...+%d more", int(v.Len)-len(v.Children)) + } + + if nl { + fmt.Fprintf(buf, "\n%s", indent) + } + + fmt.Fprintf(buf, "]") +} diff --git a/service/api/types.go b/service/api/types.go index 807e3152..c7444917 100644 --- a/service/api/types.go +++ b/service/api/types.go @@ -1,5 +1,7 @@ package api +import "reflect" + // DebuggerState represents the current context of the debugger. type DebuggerState struct { // Breakpoint is the current breakpoint at which the debugged process is @@ -104,9 +106,35 @@ type Function struct { // Variable describes a variable. type Variable struct { - Name string `json:"name"` + // Name of the variable or struct member + Name string `json:"name"` + // Address of the variable or struct member + Addr uintptr `json:"addr"` + // Go type of the variable + Type string `json:"type"` + // Type of the variable after resolving any typedefs + RealType string `json:"realType"` + + Kind reflect.Kind `json:"kind"` + + //Strings have their length capped at proc.maxArrayValues, use Len for the real length of a string + //Function variables will store the name of the function in this field Value string `json:"value"` - Type string `json:"type"` + + // Number of elements in an array or a slice, number of keys for a map, number of struct members for a struct, length of strings + Len int64 `json:"len"` + // Cap value for slices + Cap int64 `json:"cap"` + + // Array and slice elements, member fields of structs, key/value pairs of maps, value of complex numbers + // The Name field in this slice will always be the empty string except for structs (when it will be the field name) and for complex numbers (when it will be "real" and "imaginary") + // For maps each map entry will have to items in this slice, even numbered items will represent map keys and odd numbered items will represent their values + // This field's length is capped at proc.maxArrayValues for slices and arrays and 2*proc.maxArrayValues for maps, in the circumnstances where the cap takes effect len(Children) != Len + // The other length cap applied to this field is related to maximum recursion depth, when the maximum recursion depth is reached this field is left empty, contrary to the previous one this cap also applies to structs (otherwise structs will always have all thier member fields returned) + Children []Variable `json:"children"` + + // Unreadable addresses will have this field set + Unreadable string `json:"unreadable"` } // Goroutine represents the information relevant to Delve from the runtime's diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index aa6eebe0..03c835cd 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -312,11 +312,11 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error if err != nil { return err } - bpi.Variables[i] = api.ConvertVar(v) + bpi.Variables[i] = *api.ConvertVar(v) } - vars, err := functionArguments(s) + args, err := s.FunctionArguments() if err == nil { - bpi.Arguments = vars + bpi.Arguments = convertVars(args) } return nil } @@ -376,7 +376,7 @@ func (d *Debugger) PackageVariables(threadID int, filter string) ([]api.Variable } for _, v := range pv { if regex.Match([]byte(v.Name)) { - vars = append(vars, api.ConvertVar(v)) + vars = append(vars, *api.ConvertVar(v)) } } return vars, err @@ -397,7 +397,7 @@ func (d *Debugger) Registers(threadID int) (string, error) { func convertVars(pv []*proc.Variable) []api.Variable { vars := make([]api.Variable, 0, len(pv)) for _, v := range pv { - vars = append(vars, api.ConvertVar(v)) + vars = append(vars, *api.ConvertVar(v)) } return vars } @@ -419,19 +419,11 @@ func (d *Debugger) FunctionArguments(scope api.EvalScope) ([]api.Variable, error if err != nil { return nil, err } - return functionArguments(s) -} - -func functionArguments(s *proc.EvalScope) ([]api.Variable, error) { pv, err := s.FunctionArguments() if err != nil { return nil, err } - vars := make([]api.Variable, 0, len(pv)) - for _, v := range pv { - vars = append(vars, api.ConvertVar(v)) - } - return vars, nil + return convertVars(pv), nil } func (d *Debugger) EvalVariableInScope(scope api.EvalScope, symbol string) (*api.Variable, error) { @@ -443,8 +435,7 @@ func (d *Debugger) EvalVariableInScope(scope api.EvalScope, symbol string) (*api if err != nil { return nil, err } - converted := api.ConvertVar(v) - return &converted, err + return api.ConvertVar(v), err } func (d *Debugger) SetVariableInScope(scope api.EvalScope, symbol, value string) error { @@ -492,17 +483,19 @@ func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, full bool) ([]ap for i := range rawlocs { frame := api.Stackframe{Location: api.ConvertLocation(rawlocs[i].Call)} if full { + var err error scope := rawlocs[i].Scope(d.process.CurrentThread) - lv, err := scope.LocalVariables() + locals, err := scope.LocalVariables() if err != nil { return nil, err } - av, err := scope.FunctionArguments() + arguments, err := scope.FunctionArguments() if err != nil { return nil, err } - frame.Locals = convertVars(lv) - frame.Arguments = convertVars(av) + + frame.Locals = convertVars(locals) + frame.Arguments = convertVars(arguments) } locations = append(locations, frame) } diff --git a/service/test/integration_test.go b/service/test/integration_test.go index d54b81c6..bb591137 100644 --- a/service/test/integration_test.go +++ b/service/test/integration_test.go @@ -456,8 +456,12 @@ func TestClientServer_traceContinue(t *testing.T) { t.Fatalf("Wrong variable returned %s", bpi.Variables[0].Name) } - if bpi.Variables[0].Value != strconv.Itoa(count-1) { - t.Fatalf("Wrong variable value %s (%d)", bpi.Variables[0].Value, count) + t.Logf("Variable i is %v", bpi.Variables[0]) + + n, err := strconv.Atoi(bpi.Variables[0].Value) + + if err != nil || n != count-1 { + t.Fatalf("Wrong variable value %q (%v %d)", bpi.Variables[0].Value, err, count) } } if state.Exited { @@ -604,10 +608,6 @@ func TestClientServer_FindLocations(t *testing.T) { func TestClientServer_EvalVariable(t *testing.T) { withTestClient("testvariables", t, func(c service.Client) { - fp := testProgPath(t, "testvariables") - _, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 59}) - assertNoError(err, t, "CreateBreakpoint()") - state := <-c.Continue() if state.Err != nil { @@ -617,20 +617,16 @@ func TestClientServer_EvalVariable(t *testing.T) { var1, err := c.EvalVariable(api.EvalScope{-1, 0}, "a1") assertNoError(err, t, "EvalVariable") - t.Logf("var1: <%s>", var1.Value) + t.Logf("var1: %s", var1.SinglelineString()) if var1.Value != "foofoofoofoofoofoo" { - t.Fatalf("Wrong variable value: %v", var1.Value) + t.Fatalf("Wrong variable value: %s", var1.Value) } }) } func TestClientServer_SetVariable(t *testing.T) { withTestClient("testvariables", t, func(c service.Client) { - fp := testProgPath(t, "testvariables") - _, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 59}) - assertNoError(err, t, "CreateBreakpoint()") - state := <-c.Continue() if state.Err != nil { @@ -641,10 +637,12 @@ func TestClientServer_SetVariable(t *testing.T) { a2, err := c.EvalVariable(api.EvalScope{-1, 0}, "a2") - t.Logf("a2: <%s>", a2.Value) + t.Logf("a2: %v", a2) - if a2.Value != "8" { - t.Fatalf("Wrong variable value: %v", a2.Value) + n, err := strconv.Atoi(a2.Value) + + if err != nil && n != 8 { + t.Fatalf("Wrong variable value: %v", a2) } }) } @@ -676,9 +674,11 @@ func TestClientServer_FullStacktrace(t *testing.T) { if arg.Name != "i" { continue } - n, err := strconv.Atoi(arg.Value) - assertNoError(err, t, fmt.Sprintf("Wrong value for i in goroutine %d (%s)", g.ID, arg.Value)) - found[n] = true + t.Logf("frame %d, variable i is %v\n", arg) + argn, err := strconv.Atoi(arg.Value) + if err == nil { + found[argn] = true + } } } } @@ -707,10 +707,9 @@ func TestClientServer_FullStacktrace(t *testing.T) { if v == nil { t.Fatalf("Could not find value of variable n in frame %d", i) } - n, err := strconv.Atoi(v.Value) - assertNoError(err, t, fmt.Sprintf("Wrong value for n: %s", v.Value)) - if n != cur { - t.Fatalf("Expected value %d got %d", cur, n) + vn, err := strconv.Atoi(v.Value) + if err != nil || vn != cur { + t.Fatalf("Expected value %d got %d (error: %v)", cur, vn, err) } cur-- if cur < 0 { diff --git a/service/test/variables_test.go b/service/test/variables_test.go new file mode 100644 index 00000000..4d8e0c77 --- /dev/null +++ b/service/test/variables_test.go @@ -0,0 +1,346 @@ +package servicetest + +import ( + "fmt" + "sort" + "strings" + "testing" + + "github.com/derekparker/delve/proc" + "github.com/derekparker/delve/service/api" + + protest "github.com/derekparker/delve/proc/test" +) + +type varTest struct { + name string + value string + setTo string + varType string + err error +} + +func matchStringOrPrefix(output, target string) bool { + if strings.HasSuffix(target, "…") { + prefix := target[:len(target)-len("…")] + b := strings.HasPrefix(output, prefix) + return b + } else { + return output == target + } + +} + +func assertVariable(t *testing.T, variable *proc.Variable, expected varTest) { + if variable.Name != expected.name { + t.Fatalf("Expected %s got %s\n", expected.name, variable.Name) + } + + cv := api.ConvertVar(variable) + + if cv.Type != expected.varType { + t.Fatalf("Expected %s got %s (for variable %s)\n", expected.varType, cv.Type, expected.name) + } + + if ss := cv.SinglelineString(); !matchStringOrPrefix(ss, expected.value) { + t.Fatalf("Expected %#v got %#v (for variable %s)\n", expected.value, ss, expected.name) + } +} + +func evalVariable(p *proc.Process, symbol string) (*proc.Variable, error) { + scope, err := p.CurrentThread.Scope() + if err != nil { + return nil, err + } + return scope.EvalVariable(symbol) +} + +func (tc *varTest) settable() bool { + return tc.setTo != "" +} + +func (tc *varTest) afterSet() varTest { + r := *tc + r.value = r.setTo + return r +} + +func setVariable(p *proc.Process, symbol, value string) error { + scope, err := p.CurrentThread.Scope() + if err != nil { + return err + } + return scope.SetVariable(symbol, value) +} + +const varTestBreakpointLineNumber = 59 + +func withTestProcess(name string, t *testing.T, fn func(p *proc.Process, fixture protest.Fixture)) { + fixture := protest.BuildFixture(name) + p, err := proc.Launch([]string{fixture.Path}) + if err != nil { + t.Fatal("Launch():", err) + } + + defer func() { + p.Halt() + p.Kill() + }() + + fn(p, fixture) +} + +func TestVariableEvaluation(t *testing.T) { + testcases := []varTest{ + {"a1", "\"foofoofoofoofoofoo\"", "", "struct string", nil}, + {"a11", "[3]main.FooBar [{Baz: 1, Bur: \"a\"},{Baz: 2, Bur: \"b\"},{Baz: 3, Bur: \"c\"}]", "", "[3]main.FooBar", nil}, + {"a12", "[]main.FooBar len: 2, cap: 2, [{Baz: 4, Bur: \"d\"},{Baz: 5, Bur: \"e\"}]", "", "struct []main.FooBar", nil}, + {"a13", "[]*main.FooBar len: 3, cap: 3, [*{Baz: 6, Bur: \"f\"},*{Baz: 7, Bur: \"g\"},*{Baz: 8, Bur: \"h\"}]", "", "struct []*main.FooBar", nil}, + {"a2", "6", "10", "int", nil}, + {"a3", "7.23", "3.1", "float64", nil}, + {"a4", "[2]int [1,2]", "", "[2]int", nil}, + {"a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "", "struct []int", nil}, + {"a6", "main.FooBar {Baz: 8, Bur: \"word\"}", "", "main.FooBar", nil}, + {"a7", "*main.FooBar {Baz: 5, Bur: \"strum\"}", "", "*main.FooBar", nil}, + {"a8", "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil}, + {"a9", "*main.FooBar nil", "", "*main.FooBar", nil}, + {"baz", "\"bazburzum\"", "", "struct string", nil}, + {"neg", "-1", "-20", "int", nil}, + {"f32", "1.2", "1.1", "float32", nil}, + {"c64", "(1 + 2i)", "(4 + 5i)", "complex64", nil}, + {"c128", "(2 + 3i)", "(6.3 + 7i)", "complex128", nil}, + {"a6.Baz", "8", "20", "int", nil}, + {"a7.Baz", "5", "25", "int", nil}, + {"a8.Baz", "\"feh\"", "", "struct string", nil}, + {"a9.Baz", "nil", "", "int", fmt.Errorf("a9 is nil")}, + {"a9.NonExistent", "nil", "", "int", fmt.Errorf("a9 has no member NonExistent")}, + {"a8", "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil}, // reread variable after member + {"i32", "[2]int32 [1,2]", "", "[2]int32", nil}, + {"b1", "true", "false", "bool", nil}, + {"b2", "false", "true", "bool", nil}, + {"i8", "1", "2", "int8", nil}, + {"u16", "65535", "0", "uint16", nil}, + {"u32", "4294967295", "1", "uint32", nil}, + {"u64", "18446744073709551615", "2", "uint64", nil}, + {"u8", "255", "3", "uint8", nil}, + {"up", "5", "4", "uintptr", nil}, + {"f", "main.barfoo", "", "func()", nil}, + {"ba", "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "", "struct []int", nil}, + {"ms", "main.Nest {Level: 0, Nest: *main.Nest {Level: 1, Nest: *(*main.Nest)(…", "", "main.Nest", nil}, + {"ms.Nest.Nest", "*main.Nest {Level: 2, Nest: *main.Nest {Level: 3, Nest: *(*main.Nest)(…", "", "*main.Nest", nil}, + {"ms.Nest.Nest.Nest.Nest.Nest", "*main.Nest nil", "", "*main.Nest", nil}, + {"ms.Nest.Nest.Nest.Nest.Nest.Nest", "", "", "*main.Nest", fmt.Errorf("ms.Nest.Nest.Nest.Nest.Nest is nil")}, + {"main.p1", "10", "12", "int", nil}, + {"p1", "10", "13", "int", nil}, + {"NonExistent", "", "", "", fmt.Errorf("could not find symbol value for NonExistent")}, + } + + withTestProcess("testvariables", t, func(p *proc.Process, fixture protest.Fixture) { + err := p.Continue() + assertNoError(err, t, "Continue() returned an error") + + for _, tc := range testcases { + variable, err := evalVariable(p, tc.name) + if tc.err == nil { + assertNoError(err, t, "EvalVariable() returned an error") + assertVariable(t, variable, tc) + } else { + if err == nil { + t.Fatalf("Expected error %s, got no error: %s\n", tc.err.Error(), api.ConvertVar(variable).SinglelineString()) + } + if tc.err.Error() != err.Error() { + t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error()) + } + } + + if tc.settable() { + assertNoError(setVariable(p, tc.name, tc.setTo), t, "SetVariable()") + variable, err = evalVariable(p, tc.name) + assertNoError(err, t, "EvalVariable()") + assertVariable(t, variable, tc.afterSet()) + + assertNoError(setVariable(p, tc.name, tc.value), t, "SetVariable()") + variable, err := evalVariable(p, tc.name) + assertNoError(err, t, "EvalVariable()") + assertVariable(t, variable, tc) + } + } + }) +} + +func TestMultilineVariableEvaluation(t *testing.T) { + testcases := []varTest{ + {"a1", "\"foofoofoofoofoofoo\"", "", "struct string", nil}, + {"a11", `[3]main.FooBar [ + {Baz: 1, Bur: "a"}, + {Baz: 2, Bur: "b"}, + {Baz: 3, Bur: "c"}, +]`, "", "[3]main.FooBar", nil}, + {"a12", `[]main.FooBar len: 2, cap: 2, [ + {Baz: 4, Bur: "d"}, + {Baz: 5, Bur: "e"}, +]`, "", "struct []main.FooBar", nil}, + {"a13", `[]*main.FooBar len: 3, cap: 3, [ + *{Baz: 6, Bur: "f"}, + *{Baz: 7, Bur: "g"}, + *{Baz: 8, Bur: "h"}, +]`, "", "struct []*main.FooBar", nil}, + {"a2", "6", "10", "int", nil}, + {"a4", "[2]int [1,2]", "", "[2]int", nil}, + {"a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "", "struct []int", nil}, + {"a6", "main.FooBar {Baz: 8, Bur: \"word\"}", "", "main.FooBar", nil}, + {"a7", "*main.FooBar {Baz: 5, Bur: \"strum\"}", "", "*main.FooBar", nil}, + {"a8", "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil}, + {"a9", "*main.FooBar nil", "", "*main.FooBar", nil}, + {"a8", "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil}, // reread variable after member + {"i32", "[2]int32 [1,2]", "", "[2]int32", nil}, + {"ba", "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "", "struct []int", nil}, + {"ms", `main.Nest { + Level: 0, + Nest: *main.Nest { + Level: 1, + Nest: *(*main.Nest)(…`, "", "main.Nest", nil}, + } + + withTestProcess("testvariables", t, func(p *proc.Process, fixture protest.Fixture) { + err := p.Continue() + assertNoError(err, t, "Continue() returned an error") + + for _, tc := range testcases { + variable, err := evalVariable(p, tc.name) + assertNoError(err, t, "EvalVariable() returned an error") + if ms := api.ConvertVar(variable).MultilineString(""); !matchStringOrPrefix(ms, tc.value) { + t.Fatalf("Expected %s got %s (variable %s)\n", tc.value, ms, variable.Name) + } + } + }) +} + +type varArray []*proc.Variable + +// Len is part of sort.Interface. +func (s varArray) Len() int { + return len(s) +} + +// Swap is part of sort.Interface. +func (s varArray) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter. +func (s varArray) Less(i, j int) bool { + return s[i].Name < s[j].Name +} + +func TestLocalVariables(t *testing.T) { + testcases := []struct { + fn func(*proc.EvalScope) ([]*proc.Variable, error) + output []varTest + }{ + {(*proc.EvalScope).LocalVariables, + []varTest{ + {"a1", "\"foofoofoofoofoofoo\"", "", "struct string", nil}, + {"a10", "\"ofo\"", "", "struct string", nil}, + {"a11", "[3]main.FooBar [{Baz: 1, Bur: \"a\"},{Baz: 2, Bur: \"b\"},{Baz: 3, Bur: \"c\"}]", "", "[3]main.FooBar", nil}, + {"a12", "[]main.FooBar len: 2, cap: 2, [{Baz: 4, Bur: \"d\"},{Baz: 5, Bur: \"e\"}]", "", "struct []main.FooBar", nil}, + {"a13", "[]*main.FooBar len: 3, cap: 3, [*{Baz: 6, Bur: \"f\"},*{Baz: 7, Bur: \"g\"},*{Baz: 8, Bur: \"h\"}]", "", "struct []*main.FooBar", nil}, + {"a2", "6", "", "int", nil}, + {"a3", "7.23", "", "float64", nil}, + {"a4", "[2]int [1,2]", "", "[2]int", nil}, + {"a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "", "struct []int", nil}, + {"a6", "main.FooBar {Baz: 8, Bur: \"word\"}", "", "main.FooBar", nil}, + {"a7", "*main.FooBar {Baz: 5, Bur: \"strum\"}", "", "*main.FooBar", nil}, + {"a8", "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil}, + {"a9", "*main.FooBar nil", "", "*main.FooBar", nil}, + {"b1", "true", "", "bool", nil}, + {"b2", "false", "", "bool", nil}, + {"ba", "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "", "struct []int", nil}, + {"c128", "(2 + 3i)", "", "complex128", nil}, + {"c64", "(1 + 2i)", "", "complex64", nil}, + {"f", "main.barfoo", "", "func()", nil}, + {"f32", "1.2", "", "float32", nil}, + {"i32", "[2]int32 [1,2]", "", "[2]int32", nil}, + {"i8", "1", "", "int8", nil}, + {"ms", "main.Nest {Level: 0, Nest: *main.Nest {Level: 1, Nest: *(*main.Nest)…", "", "main.Nest", nil}, + {"neg", "-1", "", "int", nil}, + {"u16", "65535", "", "uint16", nil}, + {"u32", "4294967295", "", "uint32", nil}, + {"u64", "18446744073709551615", "", "uint64", nil}, + {"u8", "255", "", "uint8", nil}, + {"up", "5", "", "uintptr", nil}}}, + {(*proc.EvalScope).FunctionArguments, + []varTest{ + {"bar", "main.FooBar {Baz: 10, Bur: \"lorem\"}", "", "main.FooBar", nil}, + {"baz", "\"bazburzum\"", "", "struct string", nil}}}, + } + + withTestProcess("testvariables", t, func(p *proc.Process, fixture protest.Fixture) { + err := p.Continue() + assertNoError(err, t, "Continue() returned an error") + + for _, tc := range testcases { + scope, err := p.CurrentThread.Scope() + assertNoError(err, t, "AsScope()") + vars, err := tc.fn(scope) + assertNoError(err, t, "LocalVariables() returned an error") + + sort.Sort(varArray(vars)) + + if len(tc.output) != len(vars) { + t.Fatalf("Invalid variable count. Expected %d got %d.", len(tc.output), len(vars)) + } + + for i, variable := range vars { + assertVariable(t, variable, tc.output[i]) + } + } + }) +} + +func TestEmbeddedStruct(t *testing.T) { + withTestProcess("testvariables4", t, func(p *proc.Process, fixture protest.Fixture) { + testcases := []varTest{ + {"b.val", "-314", "", "int", nil}, + {"b.A.val", "-314", "", "int", nil}, + {"b.a.val", "42", "", "int", nil}, + {"b.ptr.val", "1337", "", "int", nil}, + {"b.C.s", "\"hello\"", "", "struct string", nil}, + {"b.s", "\"hello\"", "", "struct string", nil}, + } + assertNoError(p.Continue(), t, "Continue()") + + for _, tc := range testcases { + variable, err := evalVariable(p, tc.name) + if tc.err == nil { + assertNoError(err, t, "EvalVariable() returned an error") + assertVariable(t, variable, tc) + } else { + if tc.err.Error() != err.Error() { + t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error()) + } + } + } + }) +} + +func TestComplexSetting(t *testing.T) { + withTestProcess("testvariables", t, func(p *proc.Process, fixture protest.Fixture) { + err := p.Continue() + assertNoError(err, t, "Continue() returned an error") + + h := func(setExpr, value string) { + assertNoError(setVariable(p, "c128", setExpr), t, "SetVariable()") + variable, err := evalVariable(p, "c128") + assertNoError(err, t, "EvalVariable()") + if s := api.ConvertVar(variable).SinglelineString(); s != value { + t.Fatalf("Wrong value of c128: \"%s\", expected \"%s\" after setting it to \"%s\"", s, value, setExpr) + } + } + + h("3.2i", "(0 + 3.2i)") + h("1.1", "(1.1 + 0i)") + h("1 + 3.3i", "(1 + 3.3i)") + h("complex128(1.2, 3.4)", "(1.2 + 3.4i)") + }) +} diff --git a/terminal/command.go b/terminal/command.go index e15938a4..972c8356 100644 --- a/terminal/command.go +++ b/terminal/command.go @@ -629,7 +629,8 @@ func printVar(t *Term, scope api.EvalScope, args ...string) error { if err != nil { return err } - fmt.Println(val.Value) + + fmt.Println(val.MultilineString("")) return nil } @@ -650,7 +651,7 @@ func filterVariables(vars []api.Variable, filter string) []string { data := make([]string, 0, len(vars)) for _, v := range vars { if reg == nil || reg.Match([]byte(v.Name)) { - data = append(data, fmt.Sprintf("%s = %s", v.Name, v.Value)) + data = append(data, fmt.Sprintf("%s = %s", v.Name, v.SinglelineString())) } } return data @@ -801,10 +802,10 @@ func printStack(stack []api.Stackframe, ind string) { fmt.Printf("%sat %s:%d\n", s, shortenFilePath(stack[i].File), stack[i].Line) for j := range stack[i].Arguments { - fmt.Printf("%s %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].Value) + fmt.Printf("%s %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].SinglelineString()) } for j := range stack[i].Locals { - fmt.Printf("%s %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].Value) + fmt.Printf("%s %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].SinglelineString()) } } } @@ -829,7 +830,7 @@ func printcontext(t *Term, state *api.DebuggerState) error { if state.Breakpoint.Tracepoint { var arg []string for _, ar := range state.CurrentThread.Function.Args { - arg = append(arg, ar.Value) + arg = append(arg, ar.SinglelineString()) } args = strings.Join(arg, ", ") } @@ -864,7 +865,7 @@ func printcontext(t *Term, state *api.DebuggerState) error { ss := make([]string, len(bpi.Variables)) for i, v := range bpi.Variables { - ss[i] = fmt.Sprintf("%s: <%v>", v.Name, v.Value) + ss[i] = fmt.Sprintf("%s: %v", v.Name, v.MultilineString("")) } fmt.Printf("\t%s\n", strings.Join(ss, ", "))