Changed api.Variable to have a machine readable value
The new contents of api.Variable are documented in proc/variables.go. Implements #243
This commit is contained in:
parent
91939bc9e7
commit
50b5fc92e2
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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("<unreadable: %s>", 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("<unreadable: %s>", 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
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
@ -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 {
|
||||
|
262
service/api/prettyprint.go
Normal file
262
service/api/prettyprint.go
Normal file
@ -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, "]")
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
346
service/test/variables_test.go
Normal file
346
service/test/variables_test.go
Normal file
@ -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)")
|
||||
})
|
||||
}
|
@ -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, ", "))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user