proc: Improvements to Variable printing (#444)

* service/api: Removed unused fields of service/api.Function

* proc/eval: Set return variable name to input expression

* all: fine-grained control of loadValue for better variable printing

Makes proc.(*Variable).loadValue loading parameters configurable
through one extra argument of type LoadConfig.
This interface is also exposed through the API so clients can control
how much of a variable delve should read.
This commit is contained in:
Alessandro Arzilli 2016-04-24 19:15:39 +02:00 committed by Derek Parker
parent 60946a759c
commit 473b66387c
23 changed files with 782 additions and 468 deletions

@ -40,6 +40,10 @@ type B struct {
ptr *A
}
type D struct {
u1, u2, u3, u4, u5, u6 uint32
}
func afunc(x int) int {
return x + 2
}
@ -181,6 +185,7 @@ func main() {
aas[0].aas = aas
b := B{A: A{-314}, C: &C{"hello"}, a: A{42}, ptr: &A{1337}}
b2 := B{A: A{42}, a: A{47}}
var sd D
for i := range benchparr {
benchparr[i] = &benchstruct{}
@ -192,5 +197,5 @@ func main() {
fmt.Println(amb1)
}
runtime.Breakpoint()
fmt.Println(i1, i2, i3, p1, amb1, s1, s3, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2)
fmt.Println(i1, i2, i3, p1, amb1, s1, s3, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd)
}

@ -259,7 +259,7 @@ func traceCmd(cmd *cobra.Command, args []string) {
return 1
}
for i := range funcs {
_, err = client.CreateBreakpoint(&api.Breakpoint{FunctionName: funcs[i], Tracepoint: true, Line: -1, Stacktrace: traceStackDepth})
_, err = client.CreateBreakpoint(&api.Breakpoint{FunctionName: funcs[i], Tracepoint: true, Line: -1, Stacktrace: traceStackDepth, LoadArgs: &terminal.ShortLoadConfig})
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1

@ -28,6 +28,8 @@ type Breakpoint struct {
Goroutine bool // Retrieve goroutine information
Stacktrace int // Number of stack frames to retrieve
Variables []string // Variables to evaluate
LoadArgs *LoadConfig
LoadLocals *LoadConfig
HitCount map[int]uint64 // Number of times a breakpoint has been reached in a certain goroutine
TotalHitCount uint64 // Number of times a breakpoint has been reached

@ -16,7 +16,7 @@ import (
)
// EvalExpression returns the value of the given expression.
func (scope *EvalScope) EvalExpression(expr string) (*Variable, error) {
func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable, error) {
t, err := parser.ParseExpr(expr)
if err != nil {
return nil, err
@ -26,7 +26,10 @@ func (scope *EvalScope) EvalExpression(expr string) (*Variable, error) {
if err != nil {
return nil, err
}
ev.loadValue()
ev.loadValue(cfg)
if ev.Name == "" {
ev.Name = expr
}
return ev, nil
}
@ -113,7 +116,7 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) {
if err != nil {
return nil, err
}
argv.loadValue()
argv.loadValue(loadSingleValue)
if argv.Unreadable != nil {
return nil, argv.Unreadable
}
@ -277,7 +280,7 @@ func capBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
case reflect.Slice:
return newConstant(constant.MakeInt64(arg.Cap), arg.mem), nil
case reflect.Chan:
arg.loadValue()
arg.loadValue(loadFullValue)
if arg.Unreadable != nil {
return nil, arg.Unreadable
}
@ -310,7 +313,7 @@ func lenBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
}
return newConstant(constant.MakeInt64(arg.Len), arg.mem), nil
case reflect.Chan:
arg.loadValue()
arg.loadValue(loadFullValue)
if arg.Unreadable != nil {
return nil, arg.Unreadable
}
@ -340,8 +343,8 @@ func complexBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
realev := args[0]
imagev := args[1]
realev.loadValue()
imagev.loadValue()
realev.loadValue(loadSingleValue)
imagev.loadValue(loadSingleValue)
if realev.Unreadable != nil {
return nil, realev.Unreadable
@ -387,7 +390,7 @@ func imagBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
}
arg := args[0]
arg.loadValue()
arg.loadValue(loadSingleValue)
if arg.Unreadable != nil {
return nil, arg.Unreadable
@ -406,7 +409,7 @@ func realBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
}
arg := args[0]
arg.loadValue()
arg.loadValue(loadSingleValue)
if arg.Unreadable != nil {
return nil, arg.Unreadable
@ -470,7 +473,7 @@ func (scope *EvalScope) evalTypeAssert(node *ast.TypeAssertExpr) (*Variable, err
if xv.Kind != reflect.Interface {
return nil, fmt.Errorf("expression \"%s\" not an interface", exprToString(node.X))
}
xv.loadInterface(0, false)
xv.loadInterface(0, false, loadFullValue)
if xv.Unreadable != nil {
return nil, xv.Unreadable
}
@ -517,7 +520,7 @@ func (scope *EvalScope) evalIndex(node *ast.IndexExpr) (*Variable, error) {
return xev.sliceAccess(int(n))
case reflect.Map:
idxev.loadValue()
idxev.loadValue(loadFullValue)
if idxev.Unreadable != nil {
return nil, idxev.Unreadable
}
@ -576,7 +579,7 @@ func (scope *EvalScope) evalReslice(node *ast.SliceExpr) (*Variable, error) {
return nil, fmt.Errorf("second slice argument must be empty for maps")
}
xev.mapSkip += int(low)
xev.loadValue()
xev.loadValue(loadFullValue)
if xev.Unreadable != nil {
return nil, xev.Unreadable
}
@ -675,7 +678,7 @@ func (scope *EvalScope) evalUnary(node *ast.UnaryExpr) (*Variable, error) {
return nil, err
}
xv.loadValue()
xv.loadValue(loadSingleValue)
if xv.Unreadable != nil {
return nil, xv.Unreadable
}
@ -774,8 +777,8 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) {
return nil, err
}
xv.loadValue()
yv.loadValue()
xv.loadValue(loadFullValue)
yv.loadValue(loadFullValue)
if xv.Unreadable != nil {
return nil, xv.Unreadable
@ -943,7 +946,7 @@ func (v *Variable) asInt() (int64, error) {
return 0, fmt.Errorf("can not convert constant %s to int", v.Value)
}
} else {
v.loadValue()
v.loadValue(loadSingleValue)
if v.Unreadable != nil {
return 0, v.Unreadable
}
@ -961,7 +964,7 @@ func (v *Variable) asUint() (uint64, error) {
return 0, fmt.Errorf("can not convert constant %s to uint", v.Value)
}
} else {
v.loadValue()
v.loadValue(loadSingleValue)
if v.Unreadable != nil {
return 0, v.Unreadable
}
@ -1048,7 +1051,7 @@ func (v *Variable) mapAccess(idx *Variable) (*Variable, error) {
first := true
for it.next() {
key := it.key()
key.loadValue()
key.loadValue(loadFullValue)
if key.Unreadable != nil {
return nil, fmt.Errorf("can not access unreadable map: %v", key.Unreadable)
}

@ -805,7 +805,7 @@ func (dbp *Process) execPtraceFunc(fn func()) {
}
func (dbp *Process) getGoInformation() (ver GoVersion, isextld bool, err error) {
vv, err := dbp.EvalPackageVariable("runtime.buildVersion")
vv, err := dbp.EvalPackageVariable("runtime.buildVersion", LoadConfig{true, 0, 64, 0, 0})
if err != nil {
err = fmt.Errorf("Could not determine version number: %v\n", err)
return

@ -19,6 +19,8 @@ import (
protest "github.com/derekparker/delve/proc/test"
)
var normalLoadConfig = LoadConfig{true, 1, 64, 64, -1}
func init() {
runtime.GOMAXPROCS(4)
os.Setenv("GOMAXPROCS", "4")
@ -960,7 +962,7 @@ func evalVariable(p *Process, symbol string) (*Variable, error) {
if err != nil {
return nil, err
}
return scope.EvalVariable(symbol)
return scope.EvalVariable(symbol, normalLoadConfig)
}
func setVariable(p *Process, symbol, value string) error {
@ -1085,7 +1087,7 @@ func TestFrameEvaluation(t *testing.T) {
scope, err := p.ConvertEvalScope(g.ID, frame)
assertNoError(err, t, "ConvertEvalScope()")
t.Logf("scope = %v", scope)
v, err := scope.EvalVariable("i")
v, err := scope.EvalVariable("i", normalLoadConfig)
t.Logf("v = %v", v)
if err != nil {
t.Logf("Goroutine %d: %v\n", g.ID, err)
@ -1109,7 +1111,7 @@ func TestFrameEvaluation(t *testing.T) {
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")
v, err := scope.EvalVariable("n", normalLoadConfig)
assertNoError(err, t, fmt.Sprintf("EvalVariable() on frame %d", i+1))
n, _ := constant.Int64Val(v.Value)
t.Logf("frame %d n %d\n", i+1, n)
@ -1138,7 +1140,7 @@ func TestPointerSetting(t *testing.T) {
// change p1 to point to i2
scope, err := p.CurrentThread.Scope()
assertNoError(err, t, "Scope()")
i2addr, err := scope.EvalExpression("i2")
i2addr, err := scope.EvalExpression("i2", normalLoadConfig)
assertNoError(err, t, "EvalExpression()")
assertNoError(setVariable(p, "p1", fmt.Sprintf("(*int)(0x%x)", i2addr.Addr)), t, "SetVariable()")
pval(2)
@ -1278,10 +1280,10 @@ func TestBreakpointCountsWithDetection(t *testing.T) {
}
scope, err := th.Scope()
assertNoError(err, t, "Scope()")
v, err := scope.EvalVariable("i")
v, err := scope.EvalVariable("i", normalLoadConfig)
assertNoError(err, t, "evalVariable")
i, _ := constant.Int64Val(v.Value)
v, err = scope.EvalVariable("id")
v, err = scope.EvalVariable("id", normalLoadConfig)
assertNoError(err, t, "evalVariable")
id, _ := constant.Int64Val(v.Value)
m[id] = i
@ -1409,7 +1411,7 @@ func BenchmarkLocalVariables(b *testing.B) {
scope, err := p.CurrentThread.Scope()
assertNoError(err, b, "Scope()")
for i := 0; i < b.N; i++ {
_, err := scope.LocalVariables()
_, err := scope.LocalVariables(normalLoadConfig)
assertNoError(err, b, "LocalVariables()")
}
})
@ -1634,7 +1636,7 @@ func TestPackageVariables(t *testing.T) {
assertNoError(err, t, "Continue()")
scope, err := p.CurrentThread.Scope()
assertNoError(err, t, "Scope()")
vars, err := scope.PackageVariables()
vars, err := scope.PackageVariables(normalLoadConfig)
assertNoError(err, t, "PackageVariables()")
failed := false
for _, v := range vars {

@ -19,9 +19,7 @@ import (
)
const (
maxVariableRecurse = 1 // How far to recurse when evaluating nested types.
maxArrayValues = 64 // Max value for reading large arrays.
maxErrCount = 3 // Max number of read errors to accept while evaluating slices, arrays and structs
maxErrCount = 3 // Max number of read errors to accept while evaluating slices, arrays and structs
maxArrayStridePrefetch = 1024 // Maximum size of array stride for which we will prefetch the array contents
@ -68,6 +66,22 @@ type Variable struct {
Unreadable error
}
type LoadConfig struct {
// FollowPointers requests pointers to be automatically dereferenced.
FollowPointers bool
// MaxVariableRecurse is how far to recurse when evaluating nested types.
MaxVariableRecurse int
// MaxStringLen is the maximum number of bytes read from a string
MaxStringLen int
// MaxArrayValues is the maximum number of elements read from an array, a slice or a map.
MaxArrayValues int
// MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields.
MaxStructFields int
}
var loadSingleValue = LoadConfig{false, 0, 64, 0, 0}
var loadFullValue = LoadConfig{true, 1, 64, 64, -1}
// M represents a runtime M (OS thread) structure.
type M struct {
procid int // Thread ID or port.
@ -250,6 +264,7 @@ func newConstant(val constant.Value, mem memoryReadWriter) *Variable {
}
var nilVariable = &Variable{
Name: "nil",
Addr: 0,
Base: 0,
Kind: reflect.Ptr,
@ -355,7 +370,7 @@ func (gvar *Variable) parseG() (*G, error) {
}
return nil, NoGError{ tid: id }
}
gvar.loadValue()
gvar.loadValue(loadFullValue)
if gvar.Unreadable != nil {
return nil, gvar.Unreadable
}
@ -393,7 +408,7 @@ func (v *Variable) toFieldNamed(name string) *Variable {
if err != nil {
return nil
}
v.loadValue()
v.loadValue(loadFullValue)
if v.Unreadable != nil {
return nil
}
@ -435,8 +450,8 @@ func (g *G) Go() Location {
}
// EvalVariable returns the value of the given expression (backwards compatibility).
func (scope *EvalScope) EvalVariable(name string) (*Variable, error) {
return scope.EvalExpression(name)
func (scope *EvalScope) EvalVariable(name string, cfg LoadConfig) (*Variable, error) {
return scope.EvalExpression(name, cfg)
}
// SetVariable sets the value of the named variable
@ -469,7 +484,7 @@ func (scope *EvalScope) SetVariable(name, value string) error {
return err
}
yv.loadValue()
yv.loadValue(loadSingleValue)
if err := yv.isType(xv.RealType, xv.Kind); err != nil {
return err
@ -482,13 +497,13 @@ func (scope *EvalScope) SetVariable(name, value string) error {
return xv.setValue(yv)
}
func (scope *EvalScope) extractVariableFromEntry(entry *dwarf.Entry) (*Variable, error) {
func (scope *EvalScope) extractVariableFromEntry(entry *dwarf.Entry, cfg LoadConfig) (*Variable, error) {
rdr := scope.DwarfReader()
v, err := scope.extractVarInfoFromEntry(entry, rdr)
if err != nil {
return nil, err
}
v.loadValue()
v.loadValue(cfg)
return v, nil
}
@ -518,17 +533,17 @@ func (scope *EvalScope) extractVarInfo(varName string) (*Variable, error) {
}
// LocalVariables returns all local variables from the current function scope.
func (scope *EvalScope) LocalVariables() ([]*Variable, error) {
return scope.variablesByTag(dwarf.TagVariable)
func (scope *EvalScope) LocalVariables(cfg LoadConfig) ([]*Variable, error) {
return scope.variablesByTag(dwarf.TagVariable, cfg)
}
// FunctionArguments returns the name, value, and type of all current function arguments.
func (scope *EvalScope) FunctionArguments() ([]*Variable, error) {
return scope.variablesByTag(dwarf.TagFormalParameter)
func (scope *EvalScope) FunctionArguments(cfg LoadConfig) ([]*Variable, error) {
return scope.variablesByTag(dwarf.TagFormalParameter, cfg)
}
// PackageVariables returns the name, value, and type of all package variables in the application.
func (scope *EvalScope) PackageVariables() ([]*Variable, error) {
func (scope *EvalScope) PackageVariables(cfg LoadConfig) ([]*Variable, error) {
var vars []*Variable
reader := scope.DwarfReader()
@ -548,7 +563,7 @@ func (scope *EvalScope) PackageVariables() ([]*Variable, error) {
}
// Ignore errors trying to extract values
val, err := scope.extractVariableFromEntry(entry)
val, err := scope.extractVariableFromEntry(entry, cfg)
if err != nil {
continue
}
@ -560,14 +575,14 @@ func (scope *EvalScope) PackageVariables() ([]*Variable, error) {
// EvalPackageVariable will evaluate the package level variable
// specified by 'name'.
func (dbp *Process) EvalPackageVariable(name string) (*Variable, error) {
func (dbp *Process) EvalPackageVariable(name string, cfg LoadConfig) (*Variable, error) {
scope := &EvalScope{Thread: dbp.CurrentThread, PC: 0, CFA: 0}
v, err := scope.packageVarAddr(name)
if err != nil {
return nil, err
}
v.loadValue()
v.loadValue(cfg)
return v, nil
}
@ -708,11 +723,11 @@ func (v *Variable) maybeDereference() *Variable {
}
// Extracts the value of the variable at the given address.
func (v *Variable) loadValue() {
v.loadValueInternal(0)
func (v *Variable) loadValue(cfg LoadConfig) {
v.loadValueInternal(0, cfg)
}
func (v *Variable) loadValueInternal(recurseLevel int) {
func (v *Variable) loadValueInternal(recurseLevel int, cfg LoadConfig) {
if v.Unreadable != nil || v.loaded || (v.Addr == 0 && v.Base == 0) {
return
}
@ -722,30 +737,34 @@ func (v *Variable) loadValueInternal(recurseLevel int) {
case reflect.Ptr, reflect.UnsafePointer:
v.Len = 1
v.Children = []Variable{*v.maybeDereference()}
// Don't increase the recursion level when dereferencing pointers
v.Children[0].loadValueInternal(recurseLevel)
if cfg.FollowPointers {
// Don't increase the recursion level when dereferencing pointers
v.Children[0].loadValueInternal(recurseLevel, cfg)
} else {
v.Children[0].OnlyAddr = true
}
case reflect.Chan:
sv := v.clone()
sv.RealType = resolveTypedef(&(sv.RealType.(*dwarf.ChanType).TypedefType))
sv = sv.maybeDereference()
sv.loadValueInternal(recurseLevel)
sv.loadValueInternal(0, loadFullValue)
v.Children = sv.Children
v.Len = sv.Len
v.Base = sv.Addr
case reflect.Map:
if recurseLevel <= maxVariableRecurse {
v.loadMap(recurseLevel)
if recurseLevel <= cfg.MaxVariableRecurse {
v.loadMap(recurseLevel, cfg)
}
case reflect.String:
var val string
val, v.Unreadable = readStringValue(v.mem, v.Base, v.Len)
val, v.Unreadable = readStringValue(v.mem, v.Base, v.Len, cfg)
v.Value = constant.MakeString(val)
case reflect.Slice, reflect.Array:
v.loadArrayValues(recurseLevel)
v.loadArrayValues(recurseLevel, cfg)
case reflect.Struct:
v.mem = cacheMemory(v.mem, v.Addr, int(v.RealType.Size()))
@ -753,18 +772,21 @@ func (v *Variable) loadValueInternal(recurseLevel int) {
v.Len = int64(len(t.Field))
// Recursively call extractValue to grab
// the value of all the members of the struct.
if recurseLevel <= maxVariableRecurse {
if recurseLevel <= cfg.MaxVariableRecurse {
v.Children = make([]Variable, 0, len(t.Field))
for i, field := range t.Field {
if cfg.MaxStructFields >= 0 && len(v.Children) >= cfg.MaxStructFields {
break
}
f, _ := v.toField(field)
v.Children = append(v.Children, *f)
v.Children[i].Name = field.Name
v.Children[i].loadValueInternal(recurseLevel + 1)
v.Children[i].loadValueInternal(recurseLevel+1, cfg)
}
}
case reflect.Interface:
v.loadInterface(recurseLevel, true)
v.loadInterface(recurseLevel, true, cfg)
case reflect.Complex64, reflect.Complex128:
v.readComplex(v.RealType.(*dwarf.ComplexType).ByteSize)
@ -853,10 +875,10 @@ func readStringInfo(mem memoryReadWriter, arch Arch, addr uintptr) (uintptr, int
return addr, strlen, nil
}
func readStringValue(mem memoryReadWriter, addr uintptr, strlen int64) (string, error) {
func readStringValue(mem memoryReadWriter, addr uintptr, strlen int64, cfg LoadConfig) (string, error) {
count := strlen
if count > maxArrayValues {
count = maxArrayValues
if count > int64(cfg.MaxStringLen) {
count = int64(cfg.MaxStringLen)
}
val, err := mem.readMemory(addr, int(count))
@ -869,16 +891,6 @@ func readStringValue(mem memoryReadWriter, addr uintptr, strlen int64) (string,
return retstr, nil
}
func readString(mem memoryReadWriter, arch Arch, addr uintptr) (string, int64, error) {
addr, strlen, err := readStringInfo(mem, arch, addr)
if err != nil {
return "", 0, err
}
retstr, err := readStringValue(mem, addr, strlen)
return retstr, strlen, err
}
func (v *Variable) loadSliceInfo(t *dwarf.SliceType) {
v.mem = cacheMemory(v.mem, v.Addr, int(t.Size()))
@ -900,14 +912,14 @@ func (v *Variable) loadSliceInfo(t *dwarf.SliceType) {
}
case "len":
lstrAddr, _ := v.toField(f)
lstrAddr.loadValue()
lstrAddr.loadValue(loadSingleValue)
err = lstrAddr.Unreadable
if err == nil {
v.Len, _ = constant.Int64Val(lstrAddr.Value)
}
case "cap":
cstrAddr, _ := v.toField(f)
cstrAddr.loadValue()
cstrAddr.loadValue(loadSingleValue)
err = cstrAddr.Unreadable
if err == nil {
v.Cap, _ = constant.Int64Val(cstrAddr.Value)
@ -927,7 +939,7 @@ func (v *Variable) loadSliceInfo(t *dwarf.SliceType) {
return
}
func (v *Variable) loadArrayValues(recurseLevel int) {
func (v *Variable) loadArrayValues(recurseLevel int, cfg LoadConfig) {
if v.Unreadable != nil {
return
}
@ -938,8 +950,8 @@ func (v *Variable) loadArrayValues(recurseLevel int) {
count := v.Len
// Cap number of elements
if count > maxArrayValues {
count = maxArrayValues
if count > int64(cfg.MaxArrayValues) {
count = int64(cfg.MaxArrayValues)
}
if v.stride < maxArrayStridePrefetch {
@ -950,7 +962,7 @@ func (v *Variable) loadArrayValues(recurseLevel int) {
for i := int64(0); i < count; i++ {
fieldvar := v.newVariable("", uintptr(int64(v.Base)+(i*v.stride)), v.fieldType)
fieldvar.loadValueInternal(recurseLevel + 1)
fieldvar.loadValueInternal(recurseLevel+1, cfg)
if fieldvar.Unreadable != nil {
errcount++
@ -979,8 +991,8 @@ func (v *Variable) readComplex(size int64) {
realvar := v.newVariable("real", v.Addr, ftyp)
imagvar := v.newVariable("imaginary", v.Addr+uintptr(fs), ftyp)
realvar.loadValue()
imagvar.loadValue()
realvar.loadValue(loadSingleValue)
imagvar.loadValue(loadSingleValue)
v.Value = constant.BinaryOp(realvar.Value, token.ADD, constant.MakeImag(imagvar.Value))
}
@ -1131,7 +1143,7 @@ func (v *Variable) readFunctionPtr() {
v.Value = constant.MakeString(fn.Name)
}
func (v *Variable) loadMap(recurseLevel int) {
func (v *Variable) loadMap(recurseLevel int, cfg LoadConfig) {
it := v.mapIterator()
if it == nil {
return
@ -1147,13 +1159,13 @@ func (v *Variable) loadMap(recurseLevel int) {
count := 0
errcount := 0
for it.next() {
if count >= maxArrayValues {
if count >= cfg.MaxArrayValues {
break
}
key := it.key()
val := it.value()
key.loadValueInternal(recurseLevel + 1)
val.loadValueInternal(recurseLevel + 1)
key.loadValueInternal(recurseLevel+1, cfg)
val.loadValueInternal(recurseLevel+1, cfg)
if key.Unreadable != nil || val.Unreadable != nil {
errcount++
}
@ -1381,7 +1393,7 @@ func mapEvacuated(b *Variable) bool {
return true
}
func (v *Variable) loadInterface(recurseLevel int, loadData bool) {
func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig) {
var typestring, data *Variable
isnil := false
@ -1431,7 +1443,7 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool) {
data = data.maybeDereference()
v.Children = []Variable{*data}
if loadData {
v.Children[0].loadValueInternal(recurseLevel)
v.Children[0].loadValueInternal(recurseLevel, cfg)
}
return
}
@ -1440,7 +1452,7 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool) {
v.Unreadable = fmt.Errorf("invalid interface type")
return
}
typestring.loadValue()
typestring.loadValue(LoadConfig{false, 0, 512, 0, 0})
if typestring.Unreadable != nil {
v.Unreadable = fmt.Errorf("invalid interface type: %v", typestring.Unreadable)
return
@ -1468,13 +1480,15 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool) {
v.Children = []Variable{*data}
if loadData {
v.Children[0].loadValueInternal(recurseLevel)
v.Children[0].loadValueInternal(recurseLevel, cfg)
} else {
v.Children[0].OnlyAddr = true
}
return
}
// Fetches all variables of a specific type in the current function scope
func (scope *EvalScope) variablesByTag(tag dwarf.Tag) ([]*Variable, error) {
func (scope *EvalScope) variablesByTag(tag dwarf.Tag, cfg LoadConfig) ([]*Variable, error) {
reader := scope.DwarfReader()
_, err := reader.SeekToFunction(scope.PC)
@ -1489,7 +1503,7 @@ func (scope *EvalScope) variablesByTag(tag dwarf.Tag) ([]*Variable, error) {
}
if entry.Tag == tag {
val, err := scope.extractVariableFromEntry(entry)
val, err := scope.extractVariableFromEntry(entry, cfg)
if err != nil {
// skip variables that we can't parse yet
continue

@ -27,6 +27,8 @@ func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint {
Stacktrace: bp.Stacktrace,
Goroutine: bp.Goroutine,
Variables: bp.Variables,
LoadArgs: LoadConfigFromProc(bp.LoadArgs),
LoadLocals: LoadConfigFromProc(bp.LoadLocals),
TotalHitCount: bp.TotalHitCount,
}
@ -217,3 +219,29 @@ func ConvertAsmInstruction(inst proc.AsmInstruction, text string) AsmInstruction
AtPC: inst.AtPC,
}
}
func LoadConfigToProc(cfg *LoadConfig) *proc.LoadConfig {
if cfg == nil {
return nil
}
return &proc.LoadConfig{
cfg.FollowPointers,
cfg.MaxVariableRecurse,
cfg.MaxStringLen,
cfg.MaxArrayValues,
cfg.MaxStructFields,
}
}
func LoadConfigFromProc(cfg *proc.LoadConfig) *LoadConfig {
if cfg == nil {
return nil
}
return &LoadConfig{
cfg.FollowPointers,
cfg.MaxVariableRecurse,
cfg.MaxStringLen,
cfg.MaxArrayValues,
cfg.MaxStructFields,
}
}

@ -24,7 +24,6 @@ func (v *Variable) SinglelineString() string {
// MultilineString 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, true, indent)
return buf.String()
}
@ -48,7 +47,7 @@ func (v *Variable) writeTo(buf io.Writer, top, newlines, includeType bool, inden
case reflect.Ptr:
if v.Type == "" {
fmt.Fprintf(buf, "nil")
} else if v.Children[0].OnlyAddr {
} else if v.Children[0].OnlyAddr && v.Children[0].Addr != 0 {
fmt.Fprintf(buf, "(%s)(0x%x)", v.Type, v.Children[0].Addr)
} else {
fmt.Fprintf(buf, "*")
@ -82,7 +81,18 @@ func (v *Variable) writeTo(buf io.Writer, top, newlines, includeType bool, inden
fmt.Fprintf(buf, "%s(%s) ", v.Type, v.Children[0].Type)
}
}
v.Children[0].writeTo(buf, false, newlines, false, indent)
data := v.Children[0]
if data.Kind == reflect.Ptr {
if data.Children[0].Addr == 0 {
fmt.Fprintf(buf, "nil")
} else if data.Children[0].OnlyAddr {
fmt.Fprintf(buf, "0x%x", v.Children[0].Addr)
} else {
v.Children[0].writeTo(buf, false, newlines, false, indent)
}
} else {
v.Children[0].writeTo(buf, false, newlines, false, indent)
}
case reflect.Map:
v.writeMapTo(buf, newlines, includeType, indent)
case reflect.Func:
@ -125,7 +135,7 @@ func (v *Variable) writeArrayTo(buf io.Writer, newlines, includeType bool, inden
}
func (v *Variable) writeStructTo(buf io.Writer, newlines, includeType bool, indent string) {
if int(v.Len) != len(v.Children) {
if int(v.Len) != len(v.Children) && len(v.Children) == 0 {
fmt.Fprintf(buf, "(*%s)(0x%x)", v.Type, v.Addr)
return
}
@ -152,9 +162,15 @@ func (v *Variable) writeStructTo(buf io.Writer, newlines, includeType bool, inde
}
}
if nl {
fmt.Fprintf(buf, "\n%s", indent)
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))
}
fmt.Fprintf(buf, "}")
}
@ -184,12 +200,16 @@ func (v *Variable) writeMapTo(buf io.Writer, newlines, includeType bool, indent
}
if len(v.Children)/2 != int(v.Len) {
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
if len(v.Children) != 0 {
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)/2))
} else {
fmt.Fprintf(buf, ",")
fmt.Fprintf(buf, "...")
}
fmt.Fprintf(buf, "...+%d more", int(v.Len)-(len(v.Children)/2))
}
if nl {
@ -277,12 +297,16 @@ func (v *Variable) writeSliceOrArrayTo(buf io.Writer, newlines bool, indent stri
}
if len(v.Children) != int(v.Len) {
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
if len(v.Children) != 0 {
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))
} else {
fmt.Fprintf(buf, ",")
fmt.Fprintf(buf, "...")
}
fmt.Fprintf(buf, "...+%d more", int(v.Len)-len(v.Children))
}
if nl {

@ -51,8 +51,12 @@ type Breakpoint struct {
Goroutine bool `json:"goroutine"`
// number of stack frames to retrieve
Stacktrace int `json:"stacktrace"`
// variables to evaluate
// expressions to evaluate
Variables []string `json:"variables,omitempty"`
// LoadArgs requests loading function arguments when the breakpoint is hit
LoadArgs *LoadConfig
// LoadLocals requests loading function locals when the breakpoint is hit
LoadLocals *LoadConfig
// number of times a breakpoint has been reached in a certain goroutine
HitCount map[string]uint64 `json:"hitCount"`
// number of times a breakpoint has been reached
@ -129,10 +133,6 @@ type Function struct {
Value uint64 `json:"value"`
Type byte `json:"type"`
GoType uint64 `json:"goType"`
// Args are the function arguments in a thread context.
Args []Variable `json:"args"`
// Locals are the thread local variables.
Locals []Variable `json:"locals"`
}
// Variable describes a variable.
@ -170,6 +170,20 @@ type Variable struct {
Unreadable string `json:"unreadable"`
}
// LoadConfig describes how to load values from target's memory
type LoadConfig struct {
// FollowPointers requests pointers to be automatically dereferenced.
FollowPointers bool
// MaxVariableRecurse is how far to recurse when evaluating nested types.
MaxVariableRecurse int
// MaxStringLen is the maximum number of bytes read from a string
MaxStringLen int
// MaxArrayValues is the maximum number of elements read from an array, a slice or a map.
MaxArrayValues int
// MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields.
MaxStructFields int
}
// Goroutine represents the information relevant to Delve from the runtime's
// internal G structure.
type Goroutine struct {
@ -201,6 +215,7 @@ type BreakpointInfo struct {
Goroutine *Goroutine `json:"goroutine,omitempty"`
Variables []Variable `json:"variables,omitempty"`
Arguments []Variable `json:"arguments,omitempty"`
Locals []Variable `json:"locals,omitempty"`
}
type EvalScope struct {

@ -56,9 +56,9 @@ type Client interface {
GetThread(id int) (*api.Thread, error)
// ListPackageVariables lists all package variables in the context of the current thread.
ListPackageVariables(filter string) ([]api.Variable, error)
ListPackageVariables(filter string, cfg api.LoadConfig) ([]api.Variable, error)
// EvalVariable returns a variable in the context of the current thread.
EvalVariable(scope api.EvalScope, symbol string) (*api.Variable, error)
EvalVariable(scope api.EvalScope, symbol string, cfg api.LoadConfig) (*api.Variable, error)
// SetVariable sets the value of a variable
SetVariable(scope api.EvalScope, symbol, value string) error
@ -70,9 +70,9 @@ type Client interface {
// ListTypes lists all types in the process matching filter.
ListTypes(filter string) ([]string, error)
// ListLocals lists all local variables in scope.
ListLocalVariables(scope api.EvalScope) ([]api.Variable, error)
ListLocalVariables(scope api.EvalScope, cfg api.LoadConfig) ([]api.Variable, error)
// ListFunctionArgs lists all arguments to the current function.
ListFunctionArgs(scope api.EvalScope) ([]api.Variable, error)
ListFunctionArgs(scope api.EvalScope, cfg api.LoadConfig) ([]api.Variable, error)
// ListRegisters lists registers and their values.
ListRegisters() (string, error)
@ -80,7 +80,7 @@ type Client interface {
ListGoroutines() ([]*api.Goroutine, error)
// Returns stacktrace
Stacktrace(int, int, bool) ([]api.Stackframe, error)
Stacktrace(int, int, *api.LoadConfig) ([]api.Stackframe, error)
// Returns whether we attached to a running process or not
AttachedToExistingProcess() bool

@ -249,6 +249,8 @@ func copyBreakpointInfo(bp *proc.Breakpoint, requested *api.Breakpoint) (err err
bp.Goroutine = requested.Goroutine
bp.Stacktrace = requested.Stacktrace
bp.Variables = requested.Variables
bp.LoadArgs = api.LoadConfigToProc(requested.LoadArgs)
bp.LoadLocals = api.LoadConfigToProc(requested.LoadLocals)
bp.Cond = nil
if requested.Cond != "" {
bp.Cond, err = parser.ParseExpr(requested.Cond)
@ -444,7 +446,7 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
if err != nil {
return err
}
bpi.Stacktrace, err = d.convertStacktrace(rawlocs, false)
bpi.Stacktrace, err = d.convertStacktrace(rawlocs, nil)
if err != nil {
return err
}
@ -459,15 +461,21 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
bpi.Variables = make([]api.Variable, len(bp.Variables))
}
for i := range bp.Variables {
v, err := s.EvalVariable(bp.Variables[i])
v, err := s.EvalVariable(bp.Variables[i], proc.LoadConfig{true, 1, 64, 64, -1})
if err != nil {
return err
}
bpi.Variables[i] = *api.ConvertVar(v)
}
vars, err := s.FunctionArguments()
if err == nil {
bpi.Arguments = convertVars(vars)
if bp.LoadArgs != nil {
if vars, err := s.FunctionArguments(*api.LoadConfigToProc(bp.LoadArgs)); err == nil {
bpi.Arguments = convertVars(vars)
}
}
if bp.LoadLocals != nil {
if locals, err := s.LocalVariables(*api.LoadConfigToProc(bp.LoadLocals)); err == nil {
bpi.Locals = convertVars(locals)
}
}
}
@ -542,7 +550,7 @@ func regexFilterFuncs(filter string, allFuncs []gosym.Func) ([]string, error) {
// PackageVariables returns a list of package variables for the thread,
// optionally regexp filtered using regexp described in 'filter'.
func (d *Debugger) PackageVariables(threadID int, filter string) ([]api.Variable, error) {
func (d *Debugger) PackageVariables(threadID int, filter string, cfg proc.LoadConfig) ([]api.Variable, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
@ -560,7 +568,7 @@ func (d *Debugger) PackageVariables(threadID int, filter string) ([]api.Variable
if err != nil {
return nil, err
}
pv, err := scope.PackageVariables()
pv, err := scope.PackageVariables(cfg)
if err != nil {
return nil, err
}
@ -597,7 +605,7 @@ func convertVars(pv []*proc.Variable) []api.Variable {
}
// LocalVariables returns a list of the local variables.
func (d *Debugger) LocalVariables(scope api.EvalScope) ([]api.Variable, error) {
func (d *Debugger) LocalVariables(scope api.EvalScope, cfg proc.LoadConfig) ([]api.Variable, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
@ -605,7 +613,7 @@ func (d *Debugger) LocalVariables(scope api.EvalScope) ([]api.Variable, error) {
if err != nil {
return nil, err
}
pv, err := s.LocalVariables()
pv, err := s.LocalVariables(cfg)
if err != nil {
return nil, err
}
@ -613,7 +621,7 @@ func (d *Debugger) LocalVariables(scope api.EvalScope) ([]api.Variable, error) {
}
// FunctionArguments returns the arguments to the current function.
func (d *Debugger) FunctionArguments(scope api.EvalScope) ([]api.Variable, error) {
func (d *Debugger) FunctionArguments(scope api.EvalScope, cfg proc.LoadConfig) ([]api.Variable, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
@ -621,7 +629,7 @@ func (d *Debugger) FunctionArguments(scope api.EvalScope) ([]api.Variable, error
if err != nil {
return nil, err
}
pv, err := s.FunctionArguments()
pv, err := s.FunctionArguments(cfg)
if err != nil {
return nil, err
}
@ -630,7 +638,7 @@ func (d *Debugger) FunctionArguments(scope api.EvalScope) ([]api.Variable, error
// EvalVariableInScope will attempt to evaluate the variable represented by 'symbol'
// in the scope provided.
func (d *Debugger) EvalVariableInScope(scope api.EvalScope, symbol string) (*api.Variable, error) {
func (d *Debugger) EvalVariableInScope(scope api.EvalScope, symbol string, cfg proc.LoadConfig) (*api.Variable, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
@ -638,7 +646,7 @@ func (d *Debugger) EvalVariableInScope(scope api.EvalScope, symbol string) (*api
if err != nil {
return nil, err
}
v, err := s.EvalVariable(symbol)
v, err := s.EvalVariable(symbol, cfg)
if err != nil {
return nil, err
}
@ -677,7 +685,7 @@ func (d *Debugger) Goroutines() ([]*api.Goroutine, error) {
// Stacktrace returns a list of Stackframes for the given goroutine. The
// length of the returned list will be min(stack_len, depth).
// If 'full' is true, then local vars, function args, etc will be returned as well.
func (d *Debugger) Stacktrace(goroutineID, depth int, full bool) ([]api.Stackframe, error) {
func (d *Debugger) Stacktrace(goroutineID, depth int, cfg *proc.LoadConfig) ([]api.Stackframe, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
@ -697,21 +705,21 @@ func (d *Debugger) Stacktrace(goroutineID, depth int, full bool) ([]api.Stackfra
return nil, err
}
return d.convertStacktrace(rawlocs, full)
return d.convertStacktrace(rawlocs, cfg)
}
func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, full bool) ([]api.Stackframe, error) {
func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, cfg *proc.LoadConfig) ([]api.Stackframe, error) {
locations := make([]api.Stackframe, 0, len(rawlocs))
for i := range rawlocs {
frame := api.Stackframe{Location: api.ConvertLocation(rawlocs[i].Call)}
if full {
if cfg != nil {
var err error
scope := rawlocs[i].Scope(d.process.CurrentThread)
locals, err := scope.LocalVariables()
locals, err := scope.LocalVariables(*cfg)
if err != nil {
return nil, err
}
arguments, err := scope.FunctionArguments()
arguments, err := scope.FunctionArguments(*cfg)
if err != nil {
return nil, err
}

@ -256,7 +256,7 @@ func (loc *AddrLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr str
}
return []api.Location{{PC: uint64(addr)}}, nil
} else {
v, err := scope.EvalExpression(loc.AddrExpr)
v, err := scope.EvalExpression(loc.AddrExpr, proc.LoadConfig{true, 0, 0, 0, 0})
if err != nil {
return nil, err
}

@ -6,7 +6,6 @@ import (
"net/rpc"
"net/rpc/jsonrpc"
"github.com/derekparker/delve/service"
"github.com/derekparker/delve/service/api"
)
@ -17,9 +16,6 @@ type RPCClient struct {
client *rpc.Client
}
// Ensure the implementation satisfies the interface.
var _ service.Client = &RPCClient{}
// NewClient creates a new RPCClient.
func NewClient(addr string) *RPCClient {
client, err := jsonrpc.Dial("tcp", addr)

@ -9,11 +9,14 @@ import (
grpc "net/rpc"
"net/rpc/jsonrpc"
"github.com/derekparker/delve/proc"
"github.com/derekparker/delve/service"
"github.com/derekparker/delve/service/api"
"github.com/derekparker/delve/service/debugger"
)
var defaultLoadConfig = proc.LoadConfig{ true, 1, 64, 64, -1 }
type ServerImpl struct {
s *RPCServer
}
@ -160,7 +163,11 @@ type StacktraceGoroutineArgs struct {
}
func (s *RPCServer) StacktraceGoroutine(args *StacktraceGoroutineArgs, locations *[]api.Stackframe) error {
locs, err := s.debugger.Stacktrace(args.Id, args.Depth, args.Full)
var loadcfg *proc.LoadConfig = nil
if args.Full {
loadcfg = &defaultLoadConfig
}
locs, err := s.debugger.Stacktrace(args.Id, args.Depth, loadcfg)
if err != nil {
return err
}
@ -241,7 +248,7 @@ func (s *RPCServer) ListPackageVars(filter string, variables *[]api.Variable) er
return fmt.Errorf("no current thread")
}
vars, err := s.debugger.PackageVariables(current.ID, filter)
vars, err := s.debugger.PackageVariables(current.ID, filter, defaultLoadConfig)
if err != nil {
return err
}
@ -263,7 +270,7 @@ func (s *RPCServer) ListThreadPackageVars(args *ThreadListArgs, variables *[]api
return fmt.Errorf("no thread with id %d", args.Id)
}
vars, err := s.debugger.PackageVariables(args.Id, args.Filter)
vars, err := s.debugger.PackageVariables(args.Id, args.Filter, defaultLoadConfig)
if err != nil {
return err
}
@ -286,7 +293,7 @@ func (s *RPCServer) ListRegisters(arg interface{}, registers *string) error {
}
func (s *RPCServer) ListLocalVars(scope api.EvalScope, variables *[]api.Variable) error {
vars, err := s.debugger.LocalVariables(scope)
vars, err := s.debugger.LocalVariables(scope, defaultLoadConfig)
if err != nil {
return err
}
@ -295,7 +302,7 @@ func (s *RPCServer) ListLocalVars(scope api.EvalScope, variables *[]api.Variable
}
func (s *RPCServer) ListFunctionArgs(scope api.EvalScope, variables *[]api.Variable) error {
vars, err := s.debugger.FunctionArguments(scope)
vars, err := s.debugger.FunctionArguments(scope, defaultLoadConfig)
if err != nil {
return err
}
@ -309,7 +316,7 @@ type EvalSymbolArgs struct {
}
func (s *RPCServer) EvalSymbol(args EvalSymbolArgs, variable *api.Variable) error {
v, err := s.debugger.EvalVariableInScope(args.Scope, args.Symbol)
v, err := s.debugger.EvalVariableInScope(args.Scope, args.Symbol, defaultLoadConfig)
if err != nil {
return err
}

@ -190,9 +190,9 @@ func (c *RPCClient) GetThread(id int) (*api.Thread, error) {
return out.Thread, err
}
func (c *RPCClient) EvalVariable(scope api.EvalScope, expr string) (*api.Variable, error) {
func (c *RPCClient) EvalVariable(scope api.EvalScope, expr string, cfg api.LoadConfig) (*api.Variable, error) {
var out EvalOut
err := c.call("Eval", EvalIn{scope, expr}, &out)
err := c.call("Eval", EvalIn{scope, expr, &cfg}, &out)
return out.Variable, err
}
@ -219,15 +219,15 @@ func (c *RPCClient) ListTypes(filter string) ([]string, error) {
return types.Types, err
}
func (c *RPCClient) ListPackageVariables(filter string) ([]api.Variable, error) {
func (c *RPCClient) ListPackageVariables(filter string, cfg api.LoadConfig) ([]api.Variable, error) {
var out ListPackageVarsOut
err := c.call("ListPackageVars", ListPackageVarsIn{filter}, &out)
err := c.call("ListPackageVars", ListPackageVarsIn{filter, cfg}, &out)
return out.Variables, err
}
func (c *RPCClient) ListLocalVariables(scope api.EvalScope) ([]api.Variable, error) {
func (c *RPCClient) ListLocalVariables(scope api.EvalScope, cfg api.LoadConfig) ([]api.Variable, error) {
var out ListLocalVarsOut
err := c.call("ListLocalVars", ListLocalVarsIn{scope}, &out)
err := c.call("ListLocalVars", ListLocalVarsIn{scope, cfg}, &out)
return out.Variables, err
}
@ -237,9 +237,9 @@ func (c *RPCClient) ListRegisters() (string, error) {
return out.Registers, err
}
func (c *RPCClient) ListFunctionArgs(scope api.EvalScope) ([]api.Variable, error) {
func (c *RPCClient) ListFunctionArgs(scope api.EvalScope, cfg api.LoadConfig) ([]api.Variable, error) {
var out ListFunctionArgsOut
err := c.call("ListFunctionArgs", ListFunctionArgsIn{scope}, &out)
err := c.call("ListFunctionArgs", ListFunctionArgsIn{scope, cfg}, &out)
return out.Args, err
}
@ -249,9 +249,9 @@ func (c *RPCClient) ListGoroutines() ([]*api.Goroutine, error) {
return out.Goroutines, err
}
func (c *RPCClient) Stacktrace(goroutineId, depth int, full bool) ([]api.Stackframe, error) {
func (c *RPCClient) Stacktrace(goroutineId, depth int, cfg *api.LoadConfig) ([]api.Stackframe, error) {
var out StacktraceOut
err := c.call("Stacktrace", StacktraceIn{goroutineId, depth, full}, &out)
err := c.call("Stacktrace", StacktraceIn{goroutineId, depth, false, cfg}, &out)
return out.Locations, err
}

@ -195,6 +195,7 @@ type StacktraceIn struct {
Id int
Depth int
Full bool
Cfg *api.LoadConfig
}
type StacktraceOut struct {
@ -202,7 +203,11 @@ type StacktraceOut struct {
}
func (s *RPCServer) Stacktrace(arg StacktraceIn, out *StacktraceOut) error {
locs, err := s.debugger.Stacktrace(arg.Id, arg.Depth, arg.Full)
cfg := arg.Cfg
if cfg == nil && arg.Full {
cfg = &api.LoadConfig{ true, 1, 64, 64, -1 }
}
locs, err := s.debugger.Stacktrace(arg.Id, arg.Depth, api.LoadConfigToProc(cfg))
if err != nil {
return err
}
@ -314,6 +319,7 @@ func (s *RPCServer) GetThread(arg GetThreadIn, out *GetThreadOut) error {
type ListPackageVarsIn struct {
Filter string
Cfg api.LoadConfig
}
type ListPackageVarsOut struct {
@ -331,7 +337,7 @@ func (s *RPCServer) ListPackageVars(arg ListPackageVarsIn, out *ListPackageVarsO
return fmt.Errorf("no current thread")
}
vars, err := s.debugger.PackageVariables(current.ID, arg.Filter)
vars, err := s.debugger.PackageVariables(current.ID, arg.Filter, *api.LoadConfigToProc(&arg.Cfg))
if err != nil {
return err
}
@ -362,6 +368,7 @@ func (s *RPCServer) ListRegisters(arg ListRegistersIn, out *ListRegistersOut) er
type ListLocalVarsIn struct {
Scope api.EvalScope
Cfg api.LoadConfig
}
type ListLocalVarsOut struct {
@ -369,7 +376,7 @@ type ListLocalVarsOut struct {
}
func (s *RPCServer) ListLocalVars(arg ListLocalVarsIn, out *ListLocalVarsOut) error {
vars, err := s.debugger.LocalVariables(arg.Scope)
vars, err := s.debugger.LocalVariables(arg.Scope, *api.LoadConfigToProc(&arg.Cfg))
if err != nil {
return err
}
@ -379,6 +386,7 @@ func (s *RPCServer) ListLocalVars(arg ListLocalVarsIn, out *ListLocalVarsOut) er
type ListFunctionArgsIn struct {
Scope api.EvalScope
Cfg api.LoadConfig
}
type ListFunctionArgsOut struct {
@ -386,7 +394,7 @@ type ListFunctionArgsOut struct {
}
func (s *RPCServer) ListFunctionArgs(arg ListFunctionArgsIn, out *ListFunctionArgsOut) error {
vars, err := s.debugger.FunctionArguments(arg.Scope)
vars, err := s.debugger.FunctionArguments(arg.Scope, *api.LoadConfigToProc(&arg.Cfg))
if err != nil {
return err
}
@ -397,6 +405,7 @@ func (s *RPCServer) ListFunctionArgs(arg ListFunctionArgsIn, out *ListFunctionAr
type EvalIn struct {
Scope api.EvalScope
Expr string
Cfg *api.LoadConfig
}
type EvalOut struct {
@ -404,7 +413,11 @@ type EvalOut struct {
}
func (s *RPCServer) Eval(arg EvalIn, out *EvalOut) error {
v, err := s.debugger.EvalVariableInScope(arg.Scope, arg.Expr)
cfg := arg.Cfg
if cfg == nil {
cfg = &api.LoadConfig{ true, 1, 64, 64, -1 }
}
v, err := s.debugger.EvalVariableInScope(arg.Scope, arg.Expr, *api.LoadConfigToProc(cfg))
if err != nil {
return err
}

@ -7,7 +7,6 @@ import (
"runtime"
"testing"
"github.com/derekparker/delve/service"
"github.com/derekparker/delve/service/api"
)
@ -35,18 +34,6 @@ type nextTest struct {
begin, end int
}
func countBreakpoints(t *testing.T, c service.Client) int {
bps, err := c.ListBreakpoints()
assertNoError(err, t, "ListBreakpoints()")
bpcount := 0
for _, bp := range bps {
if bp.ID >= 0 {
bpcount++
}
}
return bpcount
}
func testProgPath(t *testing.T, name string) string {
fp, err := filepath.Abs(fmt.Sprintf("_fixtures/%s.go", name))
if err != nil {
@ -61,7 +48,27 @@ func testProgPath(t *testing.T, name string) string {
return fp
}
func findLocationHelper(t *testing.T, c service.Client, loc string, shouldErr bool, count int, checkAddr uint64) []uint64 {
type BreakpointLister interface {
ListBreakpoints() ([]*api.Breakpoint, error)
}
func countBreakpoints(t *testing.T, c BreakpointLister) int {
bps, err := c.ListBreakpoints()
assertNoError(err, t, "ListBreakpoints()")
bpcount := 0
for _, bp := range bps {
if bp.ID >= 0 {
bpcount++
}
}
return bpcount
}
type LocationFinder interface {
FindLocation(api.EvalScope, string) ([]api.Location, error)
}
func findLocationHelper(t *testing.T, c LocationFinder, loc string, shouldErr bool, count int, checkAddr uint64) []uint64 {
locs, err := c.FindLocation(api.EvalScope{-1, 0}, loc)
t.Logf("FindLocation(\"%s\") → %v\n", loc, locs)

@ -19,7 +19,7 @@ import (
"github.com/derekparker/delve/service/rpc1"
)
func withTestClient1(name string, t *testing.T, fn func(c service.Client)) {
func withTestClient1(name string, t *testing.T, fn func(c *rpc1.RPCClient)) {
listener, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Fatalf("couldn't start listener: %s\n", err)
@ -56,7 +56,7 @@ func Test1RunWithInvalidPath(t *testing.T) {
}
func Test1Restart_afterExit(t *testing.T) {
withTestClient1("continuetestprog", t, func(c service.Client) {
withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) {
origPid := c.ProcessPid()
state := <-c.Continue()
if !state.Exited {
@ -76,7 +76,7 @@ func Test1Restart_afterExit(t *testing.T) {
}
func Test1Restart_breakpointPreservation(t *testing.T) {
withTestClient1("continuetestprog", t, func(c service.Client) {
withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1, Name: "firstbreakpoint", Tracepoint: true})
assertNoError(err, t, "CreateBreakpoint()")
stateCh := c.Continue()
@ -105,7 +105,7 @@ func Test1Restart_breakpointPreservation(t *testing.T) {
}
func Test1Restart_duringStop(t *testing.T) {
withTestClient1("continuetestprog", t, func(c service.Client) {
withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) {
origPid := c.ProcessPid()
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1})
if err != nil {
@ -144,7 +144,7 @@ func Test1Restart_attachPid(t *testing.T) {
}
func Test1ClientServer_exit(t *testing.T) {
withTestClient1("continuetestprog", t, func(c service.Client) {
withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) {
state, err := c.GetState()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
@ -167,7 +167,7 @@ func Test1ClientServer_exit(t *testing.T) {
}
func Test1ClientServer_step(t *testing.T) {
withTestClient1("testprog", t, func(c service.Client) {
withTestClient1("testprog", t, func(c *rpc1.RPCClient) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.helloworld", Line: 1})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
@ -190,7 +190,7 @@ func Test1ClientServer_step(t *testing.T) {
}
func testnext(testcases []nextTest, initialLocation string, t *testing.T) {
withTestClient1("testnextprog", t, func(c service.Client) {
withTestClient1("testnextprog", t, func(c *rpc1.RPCClient) {
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: initialLocation, Line: -1})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
@ -281,7 +281,7 @@ func Test1NextFunctionReturn(t *testing.T) {
}
func Test1ClientServer_breakpointInMainThread(t *testing.T) {
withTestClient1("testprog", t, func(c service.Client) {
withTestClient1("testprog", t, func(c *rpc1.RPCClient) {
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.helloworld", Line: 1})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
@ -302,7 +302,7 @@ func Test1ClientServer_breakpointInMainThread(t *testing.T) {
}
func Test1ClientServer_breakpointInSeparateGoroutine(t *testing.T) {
withTestClient1("testthreads", t, func(c service.Client) {
withTestClient1("testthreads", t, func(c *rpc1.RPCClient) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.anotherthread", Line: 1})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
@ -321,7 +321,7 @@ func Test1ClientServer_breakpointInSeparateGoroutine(t *testing.T) {
}
func Test1ClientServer_breakAtNonexistentPoint(t *testing.T) {
withTestClient1("testprog", t, func(c service.Client) {
withTestClient1("testprog", t, func(c *rpc1.RPCClient) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "nowhere", Line: 1})
if err == nil {
t.Fatal("Should not be able to break at non existent function")
@ -330,7 +330,7 @@ func Test1ClientServer_breakAtNonexistentPoint(t *testing.T) {
}
func Test1ClientServer_clearBreakpoint(t *testing.T) {
withTestClient1("testprog", t, func(c service.Client) {
withTestClient1("testprog", t, func(c *rpc1.RPCClient) {
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sleepytime", Line: 1})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
@ -356,7 +356,7 @@ func Test1ClientServer_clearBreakpoint(t *testing.T) {
}
func Test1ClientServer_switchThread(t *testing.T) {
withTestClient1("testnextprog", t, func(c service.Client) {
withTestClient1("testnextprog", t, func(c *rpc1.RPCClient) {
// With invalid thread id
_, err := c.SwitchThread(-1)
if err == nil {
@ -399,7 +399,7 @@ func Test1ClientServer_switchThread(t *testing.T) {
}
func Test1ClientServer_infoLocals(t *testing.T) {
withTestClient1("testnextprog", t, func(c service.Client) {
withTestClient1("testnextprog", t, func(c *rpc1.RPCClient) {
fp := testProgPath(t, "testnextprog")
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 23})
if err != nil {
@ -420,7 +420,7 @@ func Test1ClientServer_infoLocals(t *testing.T) {
}
func Test1ClientServer_infoArgs(t *testing.T) {
withTestClient1("testnextprog", t, func(c service.Client) {
withTestClient1("testnextprog", t, func(c *rpc1.RPCClient) {
fp := testProgPath(t, "testnextprog")
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 47})
if err != nil {
@ -448,7 +448,7 @@ func Test1ClientServer_infoArgs(t *testing.T) {
}
func Test1ClientServer_traceContinue(t *testing.T) {
withTestClient1("integrationprog", t, func(c service.Client) {
withTestClient1("integrationprog", t, func(c *rpc1.RPCClient) {
fp := testProgPath(t, "integrationprog")
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 15, Tracepoint: true, Goroutine: true, Stacktrace: 5, Variables: []string{"i"}})
if err != nil {
@ -505,7 +505,7 @@ func Test1ClientServer_traceContinue(t *testing.T) {
}
func Test1ClientServer_traceContinue2(t *testing.T) {
withTestClient1("integrationprog", t, func(c service.Client) {
withTestClient1("integrationprog", t, func(c *rpc1.RPCClient) {
bp1, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1, Tracepoint: true})
if err != nil {
t.Fatalf("Unexpected error: %v\n", err)
@ -548,7 +548,7 @@ func Test1ClientServer_traceContinue2(t *testing.T) {
}
func Test1ClientServer_FindLocations(t *testing.T) {
withTestClient1("locationsprog", t, func(c service.Client) {
withTestClient1("locationsprog", t, func(c *rpc1.RPCClient) {
someFunctionCallAddr := findLocationHelper(t, c, "locationsprog.go:26", false, 1, 0)[0]
someFunctionLine1 := findLocationHelper(t, c, "locationsprog.go:27", false, 1, 0)[0]
findLocationHelper(t, c, "anotherFunction:1", false, 1, someFunctionLine1)
@ -595,17 +595,17 @@ func Test1ClientServer_FindLocations(t *testing.T) {
findLocationHelper(t, c, "-1", false, 1, findLocationHelper(t, c, "locationsprog.go:32", false, 1, 0)[0])
})
withTestClient1("testnextdefer", t, func(c service.Client) {
withTestClient1("testnextdefer", t, func(c *rpc1.RPCClient) {
firstMainLine := findLocationHelper(t, c, "testnextdefer.go:5", false, 1, 0)[0]
findLocationHelper(t, c, "main.main", false, 1, firstMainLine)
})
withTestClient1("stacktraceprog", t, func(c service.Client) {
withTestClient1("stacktraceprog", t, func(c *rpc1.RPCClient) {
stacktracemeAddr := findLocationHelper(t, c, "stacktraceprog.go:4", false, 1, 0)[0]
findLocationHelper(t, c, "main.stacktraceme", false, 1, stacktracemeAddr)
})
withTestClient1("locationsUpperCase", t, func(c service.Client) {
withTestClient1("locationsUpperCase", t, func(c *rpc1.RPCClient) {
// Upper case
findLocationHelper(t, c, "locationsUpperCase.go:6", false, 1, 0)
@ -645,7 +645,7 @@ func Test1ClientServer_FindLocations(t *testing.T) {
}
func Test1ClientServer_FindLocationsAddr(t *testing.T) {
withTestClient1("locationsprog2", t, func(c service.Client) {
withTestClient1("locationsprog2", t, func(c *rpc1.RPCClient) {
<-c.Continue()
afunction := findLocationHelper(t, c, "main.afunction", false, 1, 0)[0]
@ -657,7 +657,7 @@ func Test1ClientServer_FindLocationsAddr(t *testing.T) {
}
func Test1ClientServer_EvalVariable(t *testing.T) {
withTestClient1("testvariables", t, func(c service.Client) {
withTestClient1("testvariables", t, func(c *rpc1.RPCClient) {
state := <-c.Continue()
if state.Err != nil {
@ -676,7 +676,7 @@ func Test1ClientServer_EvalVariable(t *testing.T) {
}
func Test1ClientServer_SetVariable(t *testing.T) {
withTestClient1("testvariables", t, func(c service.Client) {
withTestClient1("testvariables", t, func(c *rpc1.RPCClient) {
state := <-c.Continue()
if state.Err != nil {
@ -698,7 +698,7 @@ func Test1ClientServer_SetVariable(t *testing.T) {
}
func Test1ClientServer_FullStacktrace(t *testing.T) {
withTestClient1("goroutinestackprog", t, func(c service.Client) {
withTestClient1("goroutinestackprog", t, func(c *rpc1.RPCClient) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.stacktraceme", Line: -1})
assertNoError(err, t, "CreateBreakpoint()")
state := <-c.Continue()
@ -771,7 +771,7 @@ func Test1ClientServer_FullStacktrace(t *testing.T) {
func Test1Issue355(t *testing.T) {
// After the target process has terminated should return an error but not crash
withTestClient1("continuetestprog", t, func(c service.Client) {
withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) {
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: -1})
assertNoError(err, t, "CreateBreakpoint()")
ch := c.Continue()
@ -832,7 +832,7 @@ func Test1Disasm(t *testing.T) {
// Tests that disassembly by current PC will return a disassembly containing the instruction at PC
// Tests that stepping on a calculated CALL instruction will yield a disassembly that contains the
// effective destination of the CALL instruction
withTestClient1("locationsprog2", t, func(c service.Client) {
withTestClient1("locationsprog2", t, func(c *rpc1.RPCClient) {
ch := c.Continue()
state := <-ch
assertNoError(state.Err, t, "Continue()")
@ -934,7 +934,7 @@ func Test1Disasm(t *testing.T) {
func Test1NegativeStackDepthBug(t *testing.T) {
// After the target process has terminated should return an error but not crash
withTestClient1("continuetestprog", t, func(c service.Client) {
withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: -1})
assertNoError(err, t, "CreateBreakpoint()")
ch := c.Continue()
@ -946,7 +946,7 @@ func Test1NegativeStackDepthBug(t *testing.T) {
}
func Test1ClientServer_CondBreakpoint(t *testing.T) {
withTestClient1("parallel_next", t, func(c service.Client) {
withTestClient1("parallel_next", t, func(c *rpc1.RPCClient) {
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: 1})
assertNoError(err, t, "CreateBreakpoint()")
bp.Cond = "n == 7"
@ -976,7 +976,7 @@ func Test1ClientServer_CondBreakpoint(t *testing.T) {
}
func Test1SkipPrologue(t *testing.T) {
withTestClient1("locationsprog2", t, func(c service.Client) {
withTestClient1("locationsprog2", t, func(c *rpc1.RPCClient) {
<-c.Continue()
afunction := findLocationHelper(t, c, "main.afunction", false, 1, 0)[0]
@ -992,7 +992,7 @@ func Test1SkipPrologue(t *testing.T) {
}
func Test1SkipPrologue2(t *testing.T) {
withTestClient1("callme", t, func(c service.Client) {
withTestClient1("callme", t, func(c *rpc1.RPCClient) {
callme := findLocationHelper(t, c, "main.callme", false, 1, 0)[0]
callmeZ := findLocationHelper(t, c, "main.callme:0", false, 1, 0)[0]
findLocationHelper(t, c, "callme.go:5", false, 1, callme)
@ -1020,7 +1020,7 @@ func Test1SkipPrologue2(t *testing.T) {
func Test1Issue419(t *testing.T) {
// Calling service/rpc.(*Client).Halt could cause a crash because both Halt and Continue simultaneously
// try to read 'runtime.g' and debug/dwarf.Data.Type is not thread safe
withTestClient1("issue419", t, func(c service.Client) {
withTestClient1("issue419", t, func(c *rpc1.RPCClient) {
go func() {
rand.Seed(time.Now().Unix())
d := time.Duration(rand.Intn(4) + 1)
@ -1035,7 +1035,7 @@ func Test1Issue419(t *testing.T) {
}
func Test1TypesCommand(t *testing.T) {
withTestClient1("testvariables2", t, func(c service.Client) {
withTestClient1("testvariables2", t, func(c *rpc1.RPCClient) {
state := <-c.Continue()
assertNoError(state.Err, t, "Continue()")
types, err := c.ListTypes("")
@ -1061,7 +1061,7 @@ func Test1TypesCommand(t *testing.T) {
}
func Test1Issue406(t *testing.T) {
withTestClient1("issue406", t, func(c service.Client) {
withTestClient1("issue406", t, func(c *rpc1.RPCClient) {
locs, err := c.FindLocation(api.EvalScope{-1, 0}, "issue406.go:146")
assertNoError(err, t, "FindLocation()")
_, err = c.CreateBreakpoint(&api.Breakpoint{Addr: locs[0].PC})

@ -10,6 +10,7 @@ import (
"strings"
"testing"
"time"
"os"
protest "github.com/derekparker/delve/proc/test"
@ -19,6 +20,12 @@ import (
"github.com/derekparker/delve/service/rpc2"
)
var normalLoadConfig = api.LoadConfig{true, 1, 64, 64, -1}
func TestMain(m *testing.M) {
os.Exit(protest.RunTestsWithFixtures(m))
}
func withTestClient2(name string, t *testing.T, fn func(c service.Client)) {
listener, err := net.Listen("tcp", "localhost:0")
if err != nil {
@ -409,7 +416,7 @@ func TestClientServer_infoLocals(t *testing.T) {
if state.Err != nil {
t.Fatalf("Unexpected error: %v, state: %#v", state.Err, state)
}
locals, err := c.ListLocalVariables(api.EvalScope{-1, 0})
locals, err := c.ListLocalVariables(api.EvalScope{-1, 0}, normalLoadConfig)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
@ -437,7 +444,7 @@ func TestClientServer_infoArgs(t *testing.T) {
if regs == "" {
t.Fatal("Expected string showing registers values, got empty string")
}
locals, err := c.ListFunctionArgs(api.EvalScope{-1, 0})
locals, err := c.ListFunctionArgs(api.EvalScope{-1, 0}, normalLoadConfig)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
@ -664,7 +671,7 @@ func TestClientServer_EvalVariable(t *testing.T) {
t.Fatalf("Continue(): %v\n", state.Err)
}
var1, err := c.EvalVariable(api.EvalScope{-1, 0}, "a1")
var1, err := c.EvalVariable(api.EvalScope{-1, 0}, "a1", normalLoadConfig)
assertNoError(err, t, "EvalVariable")
t.Logf("var1: %s", var1.SinglelineString())
@ -685,7 +692,7 @@ func TestClientServer_SetVariable(t *testing.T) {
assertNoError(c.SetVariable(api.EvalScope{-1, 0}, "a2", "8"), t, "SetVariable()")
a2, err := c.EvalVariable(api.EvalScope{-1, 0}, "a2")
a2, err := c.EvalVariable(api.EvalScope{-1, 0}, "a2", normalLoadConfig)
t.Logf("a2: %v", a2)
@ -710,7 +717,7 @@ func TestClientServer_FullStacktrace(t *testing.T) {
assertNoError(err, t, "GoroutinesInfo()")
found := make([]bool, 10)
for _, g := range gs {
frames, err := c.Stacktrace(g.ID, 10, true)
frames, err := c.Stacktrace(g.ID, 10, &normalLoadConfig)
assertNoError(err, t, fmt.Sprintf("Stacktrace(%d)", g.ID))
for i, frame := range frames {
if frame.Function == nil {
@ -744,7 +751,7 @@ func TestClientServer_FullStacktrace(t *testing.T) {
t.Fatalf("Continue(): %v\n", state.Err)
}
frames, err := c.Stacktrace(-1, 10, true)
frames, err := c.Stacktrace(-1, 10, &normalLoadConfig)
assertNoError(err, t, "Stacktrace")
cur := 3
@ -810,15 +817,15 @@ func TestIssue355(t *testing.T) {
_, err = c.GetThread(tid)
assertError(err, t, "GetThread()")
assertError(c.SetVariable(api.EvalScope{gid, 0}, "a", "10"), t, "SetVariable()")
_, err = c.ListLocalVariables(api.EvalScope{gid, 0})
_, err = c.ListLocalVariables(api.EvalScope{gid, 0}, normalLoadConfig)
assertError(err, t, "ListLocalVariables()")
_, err = c.ListFunctionArgs(api.EvalScope{gid, 0})
_, err = c.ListFunctionArgs(api.EvalScope{gid, 0}, normalLoadConfig)
assertError(err, t, "ListFunctionArgs()")
_, err = c.ListRegisters()
assertError(err, t, "ListRegisters()")
_, err = c.ListGoroutines()
assertError(err, t, "ListGoroutines()")
_, err = c.Stacktrace(gid, 10, false)
_, err = c.Stacktrace(gid, 10, &normalLoadConfig)
assertError(err, t, "Stacktrace()")
_, err = c.FindLocation(api.EvalScope{gid, 0}, "+1")
assertError(err, t, "FindLocation()")
@ -940,7 +947,7 @@ func TestNegativeStackDepthBug(t *testing.T) {
ch := c.Continue()
state := <-ch
assertNoError(state.Err, t, "Continue()")
_, err = c.Stacktrace(-1, -2, true)
_, err = c.Stacktrace(-1, -2, &normalLoadConfig)
assertError(err, t, "Stacktrace()")
})
}
@ -966,7 +973,7 @@ func TestClientServer_CondBreakpoint(t *testing.T) {
state := <-c.Continue()
assertNoError(state.Err, t, "Continue()")
nvar, err := c.EvalVariable(api.EvalScope{-1, 0}, "n")
nvar, err := c.EvalVariable(api.EvalScope{-1, 0}, "n", normalLoadConfig)
assertNoError(err, t, "EvalVariable()")
if nvar.SinglelineString() != "7" {
@ -1069,9 +1076,27 @@ func TestIssue406(t *testing.T) {
ch := c.Continue()
state := <-ch
assertNoError(state.Err, t, "Continue()")
v, err := c.EvalVariable(api.EvalScope{-1, 0}, "cfgtree")
v, err := c.EvalVariable(api.EvalScope{-1, 0}, "cfgtree", normalLoadConfig)
assertNoError(err, t, "EvalVariable()")
vs := v.MultilineString("")
t.Logf("cfgtree formats to: %s\n", vs)
})
}
func TestEvalExprName(t *testing.T) {
withTestClient2("testvariables2", t, func(c service.Client) {
state := <-c.Continue()
assertNoError(state.Err, t, "Continue()")
var1, err := c.EvalVariable(api.EvalScope{-1, 0}, "i1+1", normalLoadConfig)
assertNoError(err, t, "EvalVariable")
const name = "i1+1"
t.Logf("i1+1 → %#v", var1)
if var1.Name != name {
t.Fatalf("Wrong variable name %q, expected %q", var1.Name, name)
}
})
}

@ -12,11 +12,14 @@ import (
protest "github.com/derekparker/delve/proc/test"
)
var pnormalLoadConfig = proc.LoadConfig{true, 1, 64, 64, -1}
var pshortLoadConfig = proc.LoadConfig{false, 0, 64, 0, 3}
type varTest struct {
name string
preserveName bool
value string
setTo string
alternate string
varType string
err error
}
@ -49,21 +52,17 @@ func assertVariable(t *testing.T, variable *proc.Variable, expected varTest) {
}
}
func evalVariable(p *proc.Process, symbol string) (*proc.Variable, error) {
func evalVariable(p *proc.Process, symbol string, cfg proc.LoadConfig) (*proc.Variable, error) {
scope, err := p.CurrentThread.Scope()
if err != nil {
return nil, err
}
return scope.EvalVariable(symbol)
return scope.EvalVariable(symbol, cfg)
}
func (tc *varTest) settable() bool {
return tc.setTo != ""
}
func (tc *varTest) afterSet() varTest {
func (tc *varTest) alternateVarTest() varTest {
r := *tc
r.value = r.setTo
r.value = r.alternate
return r
}
@ -142,7 +141,7 @@ func TestVariableEvaluation(t *testing.T) {
assertNoError(err, t, "Continue() returned an error")
for _, tc := range testcases {
variable, err := evalVariable(p, tc.name)
variable, err := evalVariable(p, tc.name, pnormalLoadConfig)
if tc.err == nil {
assertNoError(err, t, "EvalVariable() returned an error")
assertVariable(t, variable, tc)
@ -155,14 +154,14 @@ func TestVariableEvaluation(t *testing.T) {
}
}
if tc.settable() {
assertNoError(setVariable(p, tc.name, tc.setTo), t, "SetVariable()")
variable, err = evalVariable(p, tc.name)
if tc.alternate != "" {
assertNoError(setVariable(p, tc.name, tc.alternate), t, "SetVariable()")
variable, err = evalVariable(p, tc.name, pnormalLoadConfig)
assertNoError(err, t, "EvalVariable()")
assertVariable(t, variable, tc.afterSet())
assertVariable(t, variable, tc.alternateVarTest())
assertNoError(setVariable(p, tc.name, tc.value), t, "SetVariable()")
variable, err := evalVariable(p, tc.name)
variable, err := evalVariable(p, tc.name, pnormalLoadConfig)
assertNoError(err, t, "EvalVariable()")
assertVariable(t, variable, tc)
}
@ -170,6 +169,72 @@ func TestVariableEvaluation(t *testing.T) {
})
}
func TestVariableEvaluationShort(t *testing.T) {
testcases := []varTest{
{"a1", true, "\"foofoofoofoofoofoo\"", "", "string", nil},
{"a11", true, "[3]main.FooBar [...]", "", "[3]main.FooBar", nil},
{"a12", true, "[]main.FooBar len: 2, cap: 2, [...]", "", "[]main.FooBar", nil},
{"a13", true, "[]*main.FooBar len: 3, cap: 3, [...]", "", "[]*main.FooBar", nil},
{"a2", true, "6", "", "int", nil},
{"a3", true, "7.23", "", "float64", nil},
{"a4", true, "[2]int [...]", "", "[2]int", nil},
{"a5", true, "[]int len: 5, cap: 5, [...]", "", "[]int", nil},
{"a6", true, "main.FooBar {Baz: 8, Bur: \"word\"}", "", "main.FooBar", nil},
{"a7", true, "(*main.FooBar)(0x…", "", "*main.FooBar", nil},
{"a8", true, "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil},
{"a9", true, "*main.FooBar nil", "", "*main.FooBar", nil},
{"baz", true, "\"bazburzum\"", "", "string", nil},
{"neg", true, "-1", "", "int", nil},
{"f32", true, "1.2", "", "float32", nil},
{"c64", true, "(1 + 2i)", "", "complex64", nil},
{"c128", true, "(2 + 3i)", "", "complex128", nil},
{"a6.Baz", true, "8", "", "int", nil},
{"a7.Baz", true, "5", "", "int", nil},
{"a8.Baz", true, "\"feh\"", "", "string", nil},
{"a9.Baz", true, "nil", "", "int", fmt.Errorf("a9 is nil")},
{"a9.NonExistent", true, "nil", "", "int", fmt.Errorf("a9 has no member NonExistent")},
{"a8", true, "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil}, // reread variable after member
{"i32", true, "[2]int32 [...]", "", "[2]int32", nil},
{"b1", true, "true", "false", "bool", nil},
{"b2", true, "false", "true", "bool", nil},
{"i8", true, "1", "2", "int8", nil},
{"u16", true, "65535", "0", "uint16", nil},
{"u32", true, "4294967295", "1", "uint32", nil},
{"u64", true, "18446744073709551615", "2", "uint64", nil},
{"u8", true, "255", "3", "uint8", nil},
{"up", true, "5", "4", "uintptr", nil},
{"f", true, "main.barfoo", "", "func()", nil},
{"ba", true, "[]int len: 200, cap: 200, [...]", "", "[]int", nil},
{"ms", true, "main.Nest {Level: 0, Nest: (*main.Nest)(0x…", "", "main.Nest", nil},
{"ms.Nest.Nest", true, "(*main.Nest)(0x…", "", "*main.Nest", nil},
{"ms.Nest.Nest.Nest.Nest.Nest", true, "*main.Nest nil", "", "*main.Nest", nil},
{"ms.Nest.Nest.Nest.Nest.Nest.Nest", true, "", "", "*main.Nest", fmt.Errorf("ms.Nest.Nest.Nest.Nest.Nest is nil")},
{"main.p1", true, "10", "", "int", nil},
{"p1", true, "10", "", "int", nil},
{"NonExistent", true, "", "", "", 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, pshortLoadConfig)
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())
}
}
}
})
}
func TestMultilineVariableEvaluation(t *testing.T) {
testcases := []varTest{
{"a1", true, "\"foofoofoofoofoofoo\"", "", "string", nil},
@ -209,7 +274,7 @@ func TestMultilineVariableEvaluation(t *testing.T) {
assertNoError(err, t, "Continue() returned an error")
for _, tc := range testcases {
variable, err := evalVariable(p, tc.name)
variable, err := evalVariable(p, tc.name, pnormalLoadConfig)
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)
@ -237,7 +302,7 @@ func (s varArray) Less(i, j int) bool {
func TestLocalVariables(t *testing.T) {
testcases := []struct {
fn func(*proc.EvalScope) ([]*proc.Variable, error)
fn func(*proc.EvalScope, proc.LoadConfig) ([]*proc.Variable, error)
output []varTest
}{
{(*proc.EvalScope).LocalVariables,
@ -284,7 +349,7 @@ func TestLocalVariables(t *testing.T) {
for _, tc := range testcases {
scope, err := p.CurrentThread.Scope()
assertNoError(err, t, "AsScope()")
vars, err := tc.fn(scope)
vars, err := tc.fn(scope, pnormalLoadConfig)
assertNoError(err, t, "LocalVariables() returned an error")
sort.Sort(varArray(vars))
@ -303,21 +368,24 @@ func TestLocalVariables(t *testing.T) {
func TestEmbeddedStruct(t *testing.T) {
withTestProcess("testvariables2", t, func(p *proc.Process, fixture protest.Fixture) {
testcases := []varTest{
{"b.val", true, "-314", "", "int", nil},
{"b.A.val", true, "-314", "", "int", nil},
{"b.a.val", true, "42", "", "int", nil},
{"b.ptr.val", true, "1337", "", "int", nil},
{"b.C.s", true, "\"hello\"", "", "string", nil},
{"b.s", true, "\"hello\"", "", "string", nil},
{"b2", true, "main.B {main.A: struct main.A {val: 42}, *main.C: *struct main.C nil, a: main.A {val: 47}, ptr: *main.A nil}", "", "main.B", nil},
{"b.val", true, "-314", "-314", "int", nil},
{"b.A.val", true, "-314", "-314", "int", nil},
{"b.a.val", true, "42", "42", "int", nil},
{"b.ptr.val", true, "1337", "1337", "int", nil},
{"b.C.s", true, "\"hello\"", "\"hello\"", "string", nil},
{"b.s", true, "\"hello\"", "\"hello\"", "string", nil},
{"b2", true, "main.B {main.A: struct main.A {val: 42}, *main.C: *struct main.C nil, a: main.A {val: 47}, ptr: *main.A nil}", "main.B {main.A: (*struct main.A)(0x…", "main.B", nil},
}
assertNoError(p.Continue(), t, "Continue()")
for _, tc := range testcases {
variable, err := evalVariable(p, tc.name)
variable, err := evalVariable(p, tc.name, pnormalLoadConfig)
if tc.err == nil {
assertNoError(err, t, "EvalVariable() returned an error")
assertNoError(err, t, fmt.Sprintf("EvalVariable(%s) returned an error", tc.name))
assertVariable(t, variable, tc)
variable, err = evalVariable(p, tc.name, pshortLoadConfig)
assertNoError(err, t, fmt.Sprintf("EvalVariable(%s, pshortLoadConfig) returned an error", tc.name))
assertVariable(t, variable, tc.alternateVarTest())
} else {
if tc.err.Error() != err.Error() {
t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
@ -334,7 +402,7 @@ func TestComplexSetting(t *testing.T) {
h := func(setExpr, value string) {
assertNoError(setVariable(p, "c128", setExpr), t, "SetVariable()")
variable, err := evalVariable(p, "c128")
variable, err := evalVariable(p, "c128", pnormalLoadConfig)
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)
@ -351,171 +419,171 @@ func TestComplexSetting(t *testing.T) {
func TestEvalExpression(t *testing.T) {
testcases := []varTest{
// slice/array/string subscript
{"s1[0]", false, "\"one\"", "", "string", nil},
{"s1[1]", false, "\"two\"", "", "string", nil},
{"s1[2]", false, "\"three\"", "", "string", nil},
{"s1[3]", false, "\"four\"", "", "string", nil},
{"s1[4]", false, "\"five\"", "", "string", nil},
{"s1[0]", false, "\"one\"", "\"one\"", "string", nil},
{"s1[1]", false, "\"two\"", "\"two\"", "string", nil},
{"s1[2]", false, "\"three\"", "\"three\"", "string", nil},
{"s1[3]", false, "\"four\"", "\"four\"", "string", nil},
{"s1[4]", false, "\"five\"", "\"five\"", "string", nil},
{"s1[5]", false, "", "", "string", fmt.Errorf("index out of bounds")},
{"a1[0]", false, "\"one\"", "", "string", nil},
{"a1[1]", false, "\"two\"", "", "string", nil},
{"a1[2]", false, "\"three\"", "", "string", nil},
{"a1[3]", false, "\"four\"", "", "string", nil},
{"a1[4]", false, "\"five\"", "", "string", nil},
{"a1[0]", false, "\"one\"", "\"one\"", "string", nil},
{"a1[1]", false, "\"two\"", "\"two\"", "string", nil},
{"a1[2]", false, "\"three\"", "\"three\"", "string", nil},
{"a1[3]", false, "\"four\"", "\"four\"", "string", nil},
{"a1[4]", false, "\"five\"", "\"five\"", "string", nil},
{"a1[5]", false, "", "", "string", fmt.Errorf("index out of bounds")},
{"str1[0]", false, "48", "", "byte", nil},
{"str1[1]", false, "49", "", "byte", nil},
{"str1[2]", false, "50", "", "byte", nil},
{"str1[10]", false, "48", "", "byte", nil},
{"str1[0]", false, "48", "48", "byte", nil},
{"str1[1]", false, "49", "49", "byte", nil},
{"str1[2]", false, "50", "50", "byte", nil},
{"str1[10]", false, "48", "48", "byte", nil},
{"str1[11]", false, "", "", "byte", fmt.Errorf("index out of bounds")},
// slice/array/string reslicing
{"a1[2:4]", false, "[]string len: 2, cap: 2, [\"three\",\"four\"]", "", "[]string", nil},
{"s1[2:4]", false, "[]string len: 2, cap: 2, [\"three\",\"four\"]", "", "[]string", nil},
{"str1[2:4]", false, "\"23\"", "", "string", nil},
{"str1[0:11]", false, "\"01234567890\"", "", "string", nil},
{"str1[:3]", false, "\"012\"", "", "string", nil},
{"str1[3:]", false, "\"34567890\"", "", "string", nil},
{"a1[2:4]", false, "[]string len: 2, cap: 2, [\"three\",\"four\"]", "[]string len: 2, cap: 2, [...]", "[]string", nil},
{"s1[2:4]", false, "[]string len: 2, cap: 2, [\"three\",\"four\"]", "[]string len: 2, cap: 2, [...]", "[]string", nil},
{"str1[2:4]", false, "\"23\"", "\"23\"", "string", nil},
{"str1[0:11]", false, "\"01234567890\"", "\"01234567890\"", "string", nil},
{"str1[:3]", false, "\"012\"", "\"012\"", "string", nil},
{"str1[3:]", false, "\"34567890\"", "\"34567890\"", "string", nil},
{"str1[0:12]", false, "", "", "string", fmt.Errorf("index out of bounds")},
{"str1[5:3]", false, "", "", "string", fmt.Errorf("index out of bounds")},
// pointers
{"*p2", false, "5", "", "int", nil},
{"p2", true, "*5", "", "*int", nil},
{"p3", true, "*int nil", "", "*int", nil},
{"*p2", false, "5", "5", "int", nil},
{"p2", true, "*5", "(*int)(0x…", "*int", nil},
{"p3", true, "*int nil", "*int nil", "*int", nil},
{"*p3", false, "", "", "int", fmt.Errorf("nil pointer dereference")},
// channels
{"ch1", true, "chan int 0/2", "", "chan int", nil},
{"chnil", true, "chan int nil", "", "chan int", nil},
{"ch1", true, "chan int 0/2", "chan int 0/2", "chan int", nil},
{"chnil", true, "chan int nil", "chan int nil", "chan int", nil},
{"ch1+1", false, "", "", "", fmt.Errorf("can not convert 1 constant to chan int")},
// maps
{"m1[\"Malone\"]", false, "struct main.astruct {A: 2, B: 3}", "", "struct main.astruct", nil},
{"m2[1].B", false, "11", "", "int", nil},
{"m2[c1.sa[2].B-4].A", false, "10", "", "int", nil},
{"m2[*p1].B", false, "11", "", "int", nil},
{"m3[as1]", false, "42", "", "int", nil},
{"m1[\"Malone\"]", false, "struct main.astruct {A: 2, B: 3}", "struct main.astruct {A: 2, B: 3}", "struct main.astruct", nil},
{"m2[1].B", false, "11", "11", "int", nil},
{"m2[c1.sa[2].B-4].A", false, "10", "10", "int", nil},
{"m2[*p1].B", false, "11", "11", "int", nil},
{"m3[as1]", false, "42", "42", "int", nil},
{"mnil[\"Malone\"]", false, "", "", "", fmt.Errorf("key not found")},
{"m1[80:]", false, "", "", "", fmt.Errorf("map index out of bounds")},
// interfaces
{"err1", true, "error(*struct main.astruct) *{A: 1, B: 2}", "", "error", nil},
{"err2", true, "error(*struct main.bstruct) *{a: main.astruct {A: 1, B: 2}}", "", "error", nil},
{"errnil", true, "error nil", "", "error", nil},
{"iface1", true, "interface {}(*struct main.astruct) *{A: 1, B: 2}", "", "interface {}", nil},
{"iface2", true, "interface {}(*string) *\"test\"", "", "interface {}", nil},
{"iface3", true, "interface {}(*map[string]go/constant.Value) *[]", "", "interface {}", nil},
{"iface4", true, "interface {}(*[]go/constant.Value) *[*4]", "", "interface {}", nil},
{"ifacenil", true, "interface {} nil", "", "interface {}", nil},
{"err1 == err2", false, "false", "", "", nil},
{"err1", true, "error(*struct main.astruct) *{A: 1, B: 2}", "error(*struct main.astruct) 0x…", "error", nil},
{"err2", true, "error(*struct main.bstruct) *{a: main.astruct {A: 1, B: 2}}", "error(*struct main.bstruct) 0x…", "error", nil},
{"errnil", true, "error nil", "error nil", "error", nil},
{"iface1", true, "interface {}(*struct main.astruct) *{A: 1, B: 2}", "interface {}(*struct main.astruct) 0x…", "interface {}", nil},
{"iface2", true, "interface {}(*string) *\"test\"", "interface {}(*string) 0x…", "interface {}", nil},
{"iface3", true, "interface {}(*map[string]go/constant.Value) *[]", "interface {}(*map[string]go/constant.Value) 0x…", "interface {}", nil},
{"iface4", true, "interface {}(*[]go/constant.Value) *[*4]", "interface {}(*[]go/constant.Value) 0x…", "interface {}", nil},
{"ifacenil", true, "interface {} nil", "interface {} nil", "interface {}", nil},
{"err1 == err2", false, "false", "false", "", nil},
{"err1 == iface1", false, "", "", "", fmt.Errorf("mismatched types \"error\" and \"interface {}\"")},
{"errnil == nil", false, "false", "", "", nil},
{"nil == errnil", false, "false", "", "", nil},
{"err1.(*main.astruct)", false, "*struct main.astruct {A: 1, B: 2}", "", "*struct main.astruct", nil},
{"errnil == nil", false, "false", "false", "", nil},
{"nil == errnil", false, "false", "false", "", nil},
{"err1.(*main.astruct)", false, "*struct main.astruct {A: 1, B: 2}", "(*struct main.astruct)(0x…", "*struct main.astruct", nil},
{"err1.(*main.bstruct)", false, "", "", "", fmt.Errorf("interface conversion: error is *struct main.astruct, not *struct main.bstruct")},
{"errnil.(*main.astruct)", false, "", "", "", fmt.Errorf("interface conversion: error is nil, not *main.astruct")},
{"const1", true, "go/constant.Value(*go/constant.int64Val) *3", "", "go/constant.Value", nil},
{"const1", true, "go/constant.Value(*go/constant.int64Val) *3", "go/constant.Value(*go/constant.int64Val) 0x…", "go/constant.Value", nil},
// combined expressions
{"c1.pb.a.A", true, "1", "", "int", nil},
{"c1.sa[1].B", false, "3", "", "int", nil},
{"s2[5].B", false, "12", "", "int", nil},
{"s2[c1.sa[2].B].A", false, "11", "", "int", nil},
{"s2[*p2].B", false, "12", "", "int", nil},
{"c1.pb.a.A", true, "1", "1", "int", nil},
{"c1.sa[1].B", false, "3", "3", "int", nil},
{"s2[5].B", false, "12", "12", "int", nil},
{"s2[c1.sa[2].B].A", false, "11", "11", "int", nil},
{"s2[*p2].B", false, "12", "12", "int", nil},
// constants
{"1.1", false, "1.1", "", "", nil},
{"10", false, "10", "", "", nil},
{"1 + 2i", false, "(1 + 2i)", "", "", nil},
{"true", false, "true", "", "", nil},
{"\"test\"", false, "\"test\"", "", "", nil},
{"1.1", false, "1.1", "1.1", "", nil},
{"10", false, "10", "10", "", nil},
{"1 + 2i", false, "(1 + 2i)", "(1 + 2i)", "", nil},
{"true", false, "true", "true", "", nil},
{"\"test\"", false, "\"test\"", "\"test\"", "", nil},
// binary operators
{"i2 + i3", false, "5", "", "int", nil},
{"i2 - i3", false, "-1", "", "int", nil},
{"i3 - i2", false, "1", "", "int", nil},
{"i2 * i3", false, "6", "", "int", nil},
{"i2/i3", false, "0", "", "int", nil},
{"f1/2.0", false, "1.5", "", "float64", nil},
{"i2 << 2", false, "8", "", "int", nil},
{"i2 + i3", false, "5", "5", "int", nil},
{"i2 - i3", false, "-1", "-1", "int", nil},
{"i3 - i2", false, "1", "1", "int", nil},
{"i2 * i3", false, "6", "6", "int", nil},
{"i2/i3", false, "0", "0", "int", nil},
{"f1/2.0", false, "1.5", "1.5", "float64", nil},
{"i2 << 2", false, "8", "8", "int", nil},
// unary operators
{"-i2", false, "-2", "", "int", nil},
{"+i2", false, "2", "", "int", nil},
{"^i2", false, "-3", "", "int", nil},
{"-i2", false, "-2", "-2", "int", nil},
{"+i2", false, "2", "2", "int", nil},
{"^i2", false, "-3", "-3", "int", nil},
// comparison operators
{"i2 == i3", false, "false", "", "", nil},
{"i2 == 2", false, "true", "", "", nil},
{"i2 == 2", false, "true", "", "", nil},
{"i2 == 3", false, "false", "", "", nil},
{"i2 != i3", false, "true", "", "", nil},
{"i2 < i3", false, "true", "", "", nil},
{"i2 <= i3", false, "true", "", "", nil},
{"i2 > i3", false, "false", "", "", nil},
{"i2 >= i3", false, "false", "", "", nil},
{"i2 >= 2", false, "true", "", "", nil},
{"str1 == \"01234567890\"", false, "true", "", "", nil},
{"str1 < \"01234567890\"", false, "false", "", "", nil},
{"str1 < \"11234567890\"", false, "true", "", "", nil},
{"str1 > \"00234567890\"", false, "true", "", "", nil},
{"str1 == str1", false, "true", "", "", nil},
{"c1.pb.a == *(c1.sa[0])", false, "true", "", "", nil},
{"c1.pb.a != *(c1.sa[0])", false, "false", "", "", nil},
{"c1.pb.a == *(c1.sa[1])", false, "false", "", "", nil},
{"c1.pb.a != *(c1.sa[1])", false, "true", "", "", nil},
{"i2 == i3", false, "false", "false", "", nil},
{"i2 == 2", false, "true", "true", "", nil},
{"i2 == 2", false, "true", "true", "", nil},
{"i2 == 3", false, "false", "false", "", nil},
{"i2 != i3", false, "true", "true", "", nil},
{"i2 < i3", false, "true", "true", "", nil},
{"i2 <= i3", false, "true", "true", "", nil},
{"i2 > i3", false, "false", "false", "", nil},
{"i2 >= i3", false, "false", "false", "", nil},
{"i2 >= 2", false, "true", "true", "", nil},
{"str1 == \"01234567890\"", false, "true", "true", "", nil},
{"str1 < \"01234567890\"", false, "false", "false", "", nil},
{"str1 < \"11234567890\"", false, "true", "true", "", nil},
{"str1 > \"00234567890\"", false, "true", "true", "", nil},
{"str1 == str1", false, "true", "true", "", nil},
{"c1.pb.a == *(c1.sa[0])", false, "true", "true", "", nil},
{"c1.pb.a != *(c1.sa[0])", false, "false", "false", "", nil},
{"c1.pb.a == *(c1.sa[1])", false, "false", "false", "", nil},
{"c1.pb.a != *(c1.sa[1])", false, "true", "true", "", nil},
// builtins
{"cap(parr)", false, "4", "", "", nil},
{"len(parr)", false, "4", "", "", nil},
{"cap(parr)", false, "4", "4", "", nil},
{"len(parr)", false, "4", "4", "", nil},
{"cap(p1)", false, "", "", "", fmt.Errorf("invalid argument p1 (type *int) for cap")},
{"len(p1)", false, "", "", "", fmt.Errorf("invalid argument p1 (type *int) for len")},
{"cap(a1)", false, "5", "", "", nil},
{"len(a1)", false, "5", "", "", nil},
{"cap(s3)", false, "6", "", "", nil},
{"len(s3)", false, "0", "", "", nil},
{"cap(nilslice)", false, "0", "", "", nil},
{"len(nilslice)", false, "0", "", "", nil},
{"cap(ch1)", false, "2", "", "", nil},
{"len(ch1)", false, "0", "", "", nil},
{"cap(chnil)", false, "0", "", "", nil},
{"len(chnil)", false, "0", "", "", nil},
{"len(m1)", false, "41", "", "", nil},
{"len(mnil)", false, "0", "", "", nil},
{"imag(cpx1)", false, "2", "", "", nil},
{"real(cpx1)", false, "1", "", "", nil},
{"imag(3i)", false, "3", "", "", nil},
{"real(4)", false, "4", "", "", nil},
{"cap(a1)", false, "5", "5", "", nil},
{"len(a1)", false, "5", "5", "", nil},
{"cap(s3)", false, "6", "6", "", nil},
{"len(s3)", false, "0", "0", "", nil},
{"cap(nilslice)", false, "0", "0", "", nil},
{"len(nilslice)", false, "0", "0", "", nil},
{"cap(ch1)", false, "2", "2", "", nil},
{"len(ch1)", false, "0", "0", "", nil},
{"cap(chnil)", false, "0", "0", "", nil},
{"len(chnil)", false, "0", "0", "", nil},
{"len(m1)", false, "41", "41", "", nil},
{"len(mnil)", false, "0", "0", "", nil},
{"imag(cpx1)", false, "2", "2", "", nil},
{"real(cpx1)", false, "1", "1", "", nil},
{"imag(3i)", false, "3", "3", "", nil},
{"real(4)", false, "4", "4", "", nil},
// nil
{"nil", false, "nil", "", "", nil},
{"nil", false, "nil", "nil", "", nil},
{"nil+1", false, "", "", "", fmt.Errorf("operator + can not be applied to \"nil\"")},
{"fn1", false, "main.afunc", "", "main.functype", nil},
{"fn2", false, "nil", "", "main.functype", nil},
{"nilslice", false, "[]int len: 0, cap: 0, []", "", "[]int", nil},
{"fn1", false, "main.afunc", "main.afunc", "main.functype", nil},
{"fn2", false, "nil", "nil", "main.functype", nil},
{"nilslice", false, "[]int len: 0, cap: 0, []", "[]int len: 0, cap: 0, []", "[]int", nil},
{"fn1 == fn2", false, "", "", "", fmt.Errorf("can not compare func variables")},
{"fn1 == nil", false, "false", "", "", nil},
{"fn1 != nil", false, "true", "", "", nil},
{"fn2 == nil", false, "true", "", "", nil},
{"fn2 != nil", false, "false", "", "", nil},
{"c1.sa == nil", false, "false", "", "", nil},
{"c1.sa != nil", false, "true", "", "", nil},
{"c1.sa[0] == nil", false, "false", "", "", nil},
{"c1.sa[0] != nil", false, "true", "", "", nil},
{"nilslice == nil", false, "true", "", "", nil},
{"nil == nilslice", false, "true", "", "", nil},
{"nilslice != nil", false, "false", "", "", nil},
{"nilptr == nil", false, "true", "", "", nil},
{"nilptr != nil", false, "false", "", "", nil},
{"p1 == nil", false, "false", "", "", nil},
{"p1 != nil", false, "true", "", "", nil},
{"ch1 == nil", false, "false", "", "", nil},
{"chnil == nil", false, "true", "", "", nil},
{"fn1 == nil", false, "false", "false", "", nil},
{"fn1 != nil", false, "true", "true", "", nil},
{"fn2 == nil", false, "true", "true", "", nil},
{"fn2 != nil", false, "false", "false", "", nil},
{"c1.sa == nil", false, "false", "false", "", nil},
{"c1.sa != nil", false, "true", "true", "", nil},
{"c1.sa[0] == nil", false, "false", "false", "", nil},
{"c1.sa[0] != nil", false, "true", "true", "", nil},
{"nilslice == nil", false, "true", "true", "", nil},
{"nil == nilslice", false, "true", "true", "", nil},
{"nilslice != nil", false, "false", "false", "", nil},
{"nilptr == nil", false, "true", "true", "", nil},
{"nilptr != nil", false, "false", "false", "", nil},
{"p1 == nil", false, "false", "false", "", nil},
{"p1 != nil", false, "true", "true", "", nil},
{"ch1 == nil", false, "false", "false", "", nil},
{"chnil == nil", false, "true", "true", "", nil},
{"ch1 == chnil", false, "", "", "", fmt.Errorf("can not compare chan variables")},
{"m1 == nil", false, "false", "", "", nil},
{"m1 == nil", false, "false", "false", "", nil},
{"mnil == m1", false, "", "", "", fmt.Errorf("can not compare map variables")},
{"mnil == nil", false, "true", "", "", nil},
{"mnil == nil", false, "true", "true", "", nil},
{"nil == 2", false, "", "", "", fmt.Errorf("can not compare int to nil")},
{"2 == nil", false, "", "", "", fmt.Errorf("can not compare int to nil")},
@ -536,32 +604,36 @@ func TestEvalExpression(t *testing.T) {
{"&nil", false, "", "", "", fmt.Errorf("can not take address of \"nil\"")},
{"nil[0]", false, "", "", "", fmt.Errorf("expression \"nil\" (nil) does not support indexing")},
{"nil[2:10]", false, "", "", "", fmt.Errorf("can not slice \"nil\" (type nil)")},
{"nil.member", false, "", "", "", fmt.Errorf("type nil is not a struct")},
{"nil.member", false, "", "", "", fmt.Errorf("nil (type nil) is not a struct")},
{"(map[string]main.astruct)(0x4000)", false, "", "", "", fmt.Errorf("can not convert \"0x4000\" to map[string]main.astruct")},
// typecasts
{"uint(i2)", false, "2", "", "uint", nil},
{"int8(i2)", false, "2", "", "int8", nil},
{"int(f1)", false, "3", "", "int", nil},
{"complex128(f1)", false, "(3 + 0i)", "", "complex128", nil},
{"uint8(i4)", false, "32", "", "uint8", nil},
{"uint8(i5)", false, "253", "", "uint8", nil},
{"int8(i5)", false, "-3", "", "int8", nil},
{"int8(i6)", false, "12", "", "int8", nil},
{"uint(i2)", false, "2", "2", "uint", nil},
{"int8(i2)", false, "2", "2", "int8", nil},
{"int(f1)", false, "3", "3", "int", nil},
{"complex128(f1)", false, "(3 + 0i)", "(3 + 0i)", "complex128", nil},
{"uint8(i4)", false, "32", "32", "uint8", nil},
{"uint8(i5)", false, "253", "253", "uint8", nil},
{"int8(i5)", false, "-3", "-3", "int8", nil},
{"int8(i6)", false, "12", "12", "int8", nil},
// misc
{"i1", true, "1", "", "int", nil},
{"mainMenu", true, `main.Menu len: 3, cap: 3, [{Name: "home", Route: "/", Active: 1},{Name: "About", Route: "/about", Active: 1},{Name: "Login", Route: "/login", Active: 1}]`, "", "main.Menu", nil},
{"mainMenu[0]", false, `main.Item {Name: "home", Route: "/", Active: 1}`, "", "main.Item", nil},
{"i1", true, "1", "1", "int", nil},
{"mainMenu", true, `main.Menu len: 3, cap: 3, [{Name: "home", Route: "/", Active: 1},{Name: "About", Route: "/about", Active: 1},{Name: "Login", Route: "/login", Active: 1}]`, `main.Menu len: 3, cap: 3, [...]`, "main.Menu", nil},
{"mainMenu[0]", false, `main.Item {Name: "home", Route: "/", Active: 1}`, `main.Item {Name: "home", Route: "/", Active: 1}`, "main.Item", nil},
{"sd", false, "main.D {u1: 0, u2: 0, u3: 0, u4: 0, u5: 0, u6: 0}", "main.D {u1: 0, u2: 0, u3: 0,...+3 more}", "main.D", nil},
}
withTestProcess("testvariables2", t, func(p *proc.Process, fixture protest.Fixture) {
assertNoError(p.Continue(), t, "Continue() returned an error")
for _, tc := range testcases {
variable, err := evalVariable(p, tc.name)
variable, err := evalVariable(p, tc.name, pnormalLoadConfig)
if tc.err == nil {
assertNoError(err, t, fmt.Sprintf("EvalExpression(%s) returned an error", tc.name))
assertVariable(t, variable, tc)
variable, err := evalVariable(p, tc.name, pshortLoadConfig)
assertNoError(err, t, fmt.Sprintf("EvalExpression(%s, pshortLoadConfig) returned an error", tc.name))
assertVariable(t, variable, tc.alternateVarTest())
} else {
if err == nil {
t.Fatalf("Expected error %s, got no error (%s)", tc.err.Error(), tc.name)
@ -570,6 +642,7 @@ func TestEvalExpression(t *testing.T) {
t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
}
}
}
})
}
@ -577,7 +650,7 @@ func TestEvalExpression(t *testing.T) {
func TestEvalAddrAndCast(t *testing.T) {
withTestProcess("testvariables2", t, func(p *proc.Process, fixture protest.Fixture) {
assertNoError(p.Continue(), t, "Continue() returned an error")
c1addr, err := evalVariable(p, "&c1")
c1addr, err := evalVariable(p, "&c1", pnormalLoadConfig)
assertNoError(err, t, "EvalExpression(&c1)")
c1addrstr := api.ConvertVar(c1addr).SinglelineString()
t.Logf("&c1 → %s", c1addrstr)
@ -585,7 +658,7 @@ func TestEvalAddrAndCast(t *testing.T) {
t.Fatalf("Invalid value of EvalExpression(&c1) \"%s\"", c1addrstr)
}
aaddr, err := evalVariable(p, "&(c1.pb.a)")
aaddr, err := evalVariable(p, "&(c1.pb.a)", pnormalLoadConfig)
assertNoError(err, t, "EvalExpression(&(c1.pb.a))")
aaddrstr := api.ConvertVar(aaddr).SinglelineString()
t.Logf("&(c1.pb.a) → %s", aaddrstr)
@ -593,7 +666,7 @@ func TestEvalAddrAndCast(t *testing.T) {
t.Fatalf("invalid value of EvalExpression(&(c1.pb.a)) \"%s\"", aaddrstr)
}
a, err := evalVariable(p, "*"+aaddrstr)
a, err := evalVariable(p, "*"+aaddrstr, pnormalLoadConfig)
assertNoError(err, t, fmt.Sprintf("EvalExpression(*%s)", aaddrstr))
t.Logf("*%s → %s", aaddrstr, api.ConvertVar(a).SinglelineString())
assertVariable(t, a, varTest{aaddrstr, false, "struct main.astruct {A: 1, B: 2}", "", "struct main.astruct", nil})
@ -603,7 +676,7 @@ func TestEvalAddrAndCast(t *testing.T) {
func TestMapEvaluation(t *testing.T) {
withTestProcess("testvariables2", t, func(p *proc.Process, fixture protest.Fixture) {
assertNoError(p.Continue(), t, "Continue() returned an error")
m1v, err := evalVariable(p, "m1")
m1v, err := evalVariable(p, "m1", pnormalLoadConfig)
assertNoError(err, t, "EvalVariable()")
m1 := api.ConvertVar(m1v)
t.Logf("m1 = %v", m1.MultilineString(""))
@ -626,7 +699,7 @@ func TestMapEvaluation(t *testing.T) {
t.Fatalf("Could not find Malone")
}
m1sliced, err := evalVariable(p, "m1[10:]")
m1sliced, err := evalVariable(p, "m1[10:]", pnormalLoadConfig)
assertNoError(err, t, "EvalVariable(m1[10:])")
if len(m1sliced.Children)/2 != int(m1.Len-10) {
t.Fatalf("Wrong number of children (after slicing): %d", len(m1sliced.Children)/2)
@ -637,7 +710,7 @@ func TestMapEvaluation(t *testing.T) {
func TestUnsafePointer(t *testing.T) {
withTestProcess("testvariables2", t, func(p *proc.Process, fixture protest.Fixture) {
assertNoError(p.Continue(), t, "Continue() returned an error")
up1v, err := evalVariable(p, "up1")
up1v, err := evalVariable(p, "up1", pnormalLoadConfig)
assertNoError(err, t, "EvalVariable(up1)")
up1 := api.ConvertVar(up1v)
if ss := up1.SinglelineString(); !strings.HasPrefix(ss, "unsafe.Pointer(") {

@ -37,7 +37,6 @@ type callContext struct {
}
type cmdfunc func(t *Term, ctx callContext, args string) error
type filteringFunc func(t *Term, ctx callContext, args string) ([]string, error)
type command struct {
aliases []string
@ -63,6 +62,11 @@ type Commands struct {
client service.Client
}
var (
LongLoadConfig = api.LoadConfig{true, 1, 64, 64, -1}
ShortLoadConfig = api.LoadConfig{false, 0, 64, 0, 3}
)
// DebugCommands returns a Commands struct with default commands defined.
func DebugCommands(client service.Client) *Commands {
c := &Commands{client: client}
@ -85,12 +89,12 @@ func DebugCommands(client service.Client) *Commands {
{aliases: []string{"breakpoints", "bp"}, cmdFn: breakpoints, helpMsg: "Print out info for active breakpoints."},
{aliases: []string{"print", "p"}, allowedPrefixes: onPrefix | scopePrefix, cmdFn: printVar, helpMsg: "Evaluate a variable."},
{aliases: []string{"set"}, allowedPrefixes: scopePrefix, cmdFn: setVar, helpMsg: "Changes the value of a variable."},
{aliases: []string{"sources"}, cmdFn: filterSortAndOutput(sources), helpMsg: "Print list of source files, optionally filtered by a regexp."},
{aliases: []string{"funcs"}, cmdFn: filterSortAndOutput(funcs), helpMsg: "Print list of functions, optionally filtered by a regexp."},
{aliases: []string{"types"}, cmdFn: filterSortAndOutput(types), helpMsg: "Print list of types, optionally filtered by a regexp."},
{aliases: []string{"args"}, allowedPrefixes: scopePrefix, cmdFn: filterSortAndOutput(args), helpMsg: "Print function arguments, optionally filtered by a regexp."},
{aliases: []string{"locals"}, allowedPrefixes: scopePrefix, cmdFn: filterSortAndOutput(locals), helpMsg: "Print function locals, optionally filtered by a regexp."},
{aliases: []string{"vars"}, cmdFn: filterSortAndOutput(vars), helpMsg: "Print package variables, optionally filtered by a regexp."},
{aliases: []string{"sources"}, cmdFn: sources, helpMsg: "Print list of source files, optionally filtered by a regexp."},
{aliases: []string{"funcs"}, cmdFn: funcs, helpMsg: "Print list of functions, optionally filtered by a regexp."},
{aliases: []string{"types"}, cmdFn: types, helpMsg: "Print list of types, optionally filtered by a regexp."},
{aliases: []string{"args"}, allowedPrefixes: scopePrefix | onPrefix, cmdFn: args, helpMsg: "args [-v] <filter>. Print function arguments, optionally filtered by a regexp."},
{aliases: []string{"locals"}, allowedPrefixes: scopePrefix | onPrefix, cmdFn: locals, helpMsg: "locals [-v] <filter>. Print function locals, optionally filtered by a regexp."},
{aliases: []string{"vars"}, cmdFn: vars, helpMsg: "vars [-v] <filter>. Print package variables, optionally filtered by a regexp."},
{aliases: []string{"regs"}, cmdFn: regs, helpMsg: "Print contents of CPU registers."},
{aliases: []string{"exit", "quit", "q"}, cmdFn: exitCommand, helpMsg: "Exit the debugger."},
{aliases: []string{"list", "ls"}, allowedPrefixes: scopePrefix, cmdFn: listCommand, helpMsg: "list <linespec>. Show source around current point or provided linespec."},
@ -552,6 +556,20 @@ func breakpoints(t *Term, ctx callContext, args string) error {
if bp.Goroutine {
attrs = append(attrs, "\tgoroutine")
}
if bp.LoadArgs != nil {
if *(bp.LoadArgs) == LongLoadConfig {
attrs = append(attrs, "\targs -v")
} else {
attrs = append(attrs, "\targs")
}
}
if bp.LoadLocals != nil {
if *(bp.LoadLocals) == LongLoadConfig {
attrs = append(attrs, "\tlocals -v")
} else {
attrs = append(attrs, "\tlocals")
}
}
for i := range bp.Variables {
attrs = append(attrs, fmt.Sprintf("\tprint %s", bp.Variables[i]))
}
@ -624,7 +642,7 @@ func printVar(t *Term, ctx callContext, args string) error {
ctx.Breakpoint.Variables = append(ctx.Breakpoint.Variables, args)
return nil
}
val, err := t.client.EvalVariable(ctx.Scope, args)
val, err := t.client.EvalVariable(ctx.Scope, args, LongLoadConfig)
if err != nil {
return err
}
@ -650,55 +668,101 @@ func setVar(t *Term, ctx callContext, args string) error {
return t.client.SetVariable(ctx.Scope, lexpr, rexpr)
}
func filterVariables(vars []api.Variable, filter string) []string {
func printFilteredVariables(varType string, vars []api.Variable, filter string, cfg api.LoadConfig) error {
reg, err := regexp.Compile(filter)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
return nil
return err
}
data := make([]string, 0, len(vars))
match := false
for _, v := range vars {
if reg == nil || reg.Match([]byte(v.Name)) {
data = append(data, fmt.Sprintf("%s = %s", v.Name, v.SinglelineString()))
match = true
if cfg == ShortLoadConfig {
fmt.Printf("%s = %s\n", v.Name, v.SinglelineString())
} else {
fmt.Printf("%s = %s\n", v.Name, v.MultilineString(""))
}
}
}
return data
}
func sources(t *Term, ctx callContext, filter string) ([]string, error) {
return t.client.ListSources(filter)
}
func funcs(t *Term, ctx callContext, filter string) ([]string, error) {
return t.client.ListFunctions(filter)
}
func types(t *Term, ctx callContext, filter string) ([]string, error) {
return t.client.ListTypes(filter)
}
func args(t *Term, ctx callContext, filter string) ([]string, error) {
vars, err := t.client.ListFunctionArgs(ctx.Scope)
if err != nil {
return nil, err
if !match {
fmt.Printf("(no %s)\n", varType)
}
return describeNoVars("args", filterVariables(vars, filter)), nil
return nil
}
func locals(t *Term, ctx callContext, filter string) ([]string, error) {
locals, err := t.client.ListLocalVariables(ctx.Scope)
func printSortedStrings(v []string, err error) error {
if err != nil {
return nil, err
return err
}
return describeNoVars("locals", filterVariables(locals, filter)), nil
sort.Strings(v)
for _, d := range v {
fmt.Println(d)
}
return nil
}
func vars(t *Term, ctx callContext, filter string) ([]string, error) {
vars, err := t.client.ListPackageVariables(filter)
if err != nil {
return nil, err
func sources(t *Term, ctx callContext, args string) error {
return printSortedStrings(t.client.ListSources(args))
}
func funcs(t *Term, ctx callContext, args string) error {
return printSortedStrings(t.client.ListFunctions(args))
}
func types(t *Term, ctx callContext, args string) error {
return printSortedStrings(t.client.ListTypes(args))
}
func parseVarArguments(args string) (filter string, cfg api.LoadConfig) {
if v := strings.SplitN(args, " ", 2); len(v) >= 1 && v[0] == "-v" {
if len(v) == 2 {
return v[1], LongLoadConfig
} else {
return "", LongLoadConfig
}
}
return describeNoVars("vars", filterVariables(vars, filter)), nil
return args, ShortLoadConfig
}
func args(t *Term, ctx callContext, args string) error {
filter, cfg := parseVarArguments(args)
if ctx.Prefix == onPrefix {
if filter != "" {
return fmt.Errorf("filter not supported on breakpoint")
}
ctx.Breakpoint.LoadArgs = &cfg
return nil
}
vars, err := t.client.ListFunctionArgs(ctx.Scope, cfg)
if err != nil {
return err
}
return printFilteredVariables("args", vars, filter, cfg)
}
func locals(t *Term, ctx callContext, args string) error {
filter, cfg := parseVarArguments(args)
if ctx.Prefix == onPrefix {
if filter != "" {
return fmt.Errorf("filter not supported on breakpoint")
}
ctx.Breakpoint.LoadLocals = &cfg
return nil
}
locals, err := t.client.ListLocalVariables(ctx.Scope, cfg)
if err != nil {
return err
}
return printFilteredVariables("locals", locals, filter, cfg)
}
func vars(t *Term, ctx callContext, args string) error {
filter, cfg := parseVarArguments(args)
vars, err := t.client.ListPackageVariables(filter, cfg)
if err != nil {
return err
}
return printFilteredVariables("vars", vars, filter, cfg)
}
func regs(t *Term, ctx callContext, args string) error {
@ -710,27 +774,6 @@ func regs(t *Term, ctx callContext, args string) error {
return nil
}
func filterSortAndOutput(fn filteringFunc) cmdfunc {
return func(t *Term, ctx callContext, args string) error {
var filter string
if len(args) > 0 {
if _, err := regexp.Compile(args); err != nil {
return fmt.Errorf("invalid filter argument: %s", err.Error())
}
filter = args
}
data, err := fn(t, ctx, filter)
if err != nil {
return err
}
sort.Sort(sort.StringSlice(data))
for _, d := range data {
fmt.Println(d)
}
return nil
}
}
func stackCommand(t *Term, ctx callContext, args string) error {
depth, full, err := parseStackArgs(args)
if err != nil {
@ -740,7 +783,11 @@ func stackCommand(t *Term, ctx callContext, args string) error {
ctx.Breakpoint.Stacktrace = depth
return nil
}
stack, err := t.client.Stacktrace(ctx.Scope.GoroutineID, depth, full)
var cfg *api.LoadConfig
if full {
cfg = &ShortLoadConfig
}
stack, err := t.client.Stacktrace(ctx.Scope.GoroutineID, depth, cfg)
if err != nil {
return err
}
@ -772,7 +819,7 @@ func parseStackArgs(argstr string) (int, bool, error) {
func listCommand(t *Term, ctx callContext, args string) error {
if ctx.Prefix == scopePrefix {
locs, err := t.client.Stacktrace(ctx.Scope.GoroutineID, ctx.Scope.Frame, false)
locs, err := t.client.Stacktrace(ctx.Scope.GoroutineID, ctx.Scope.Frame, nil)
if err != nil {
return err
}
@ -885,7 +932,7 @@ func printStack(stack []api.Stackframe, ind string) {
}
d := digits(len(stack) - 1)
fmtstr := "%s%" + strconv.Itoa(d) + "d 0x%016x in %s\n"
s := strings.Repeat(" ", d+2+len(ind))
s := ind + strings.Repeat(" ", d+2+len(ind))
for i := range stack {
name := "(nil)"
@ -941,7 +988,7 @@ func printcontextThread(t *Term, th *api.Thread) {
}
args := ""
if th.Breakpoint.Tracepoint && th.BreakpointInfo != nil {
if th.BreakpointInfo != nil && th.Breakpoint.LoadArgs != nil && *th.Breakpoint.LoadArgs == ShortLoadConfig {
var arg []string
for _, ar := range th.BreakpointInfo.Arguments {
arg = append(arg, ar.SinglelineString())
@ -977,18 +1024,29 @@ func printcontextThread(t *Term, th *api.Thread) {
}
if th.BreakpointInfo != nil {
bp := th.Breakpoint
bpi := th.BreakpointInfo
if bpi.Goroutine != nil {
writeGoroutineLong(os.Stdout, bpi.Goroutine, "\t")
}
if len(bpi.Variables) > 0 {
ss := make([]string, len(bpi.Variables))
for i, v := range bpi.Variables {
ss[i] = fmt.Sprintf("%s: %s", v.Name, v.MultilineString(""))
for _, v := range bpi.Variables {
fmt.Printf("\t%s: %s\n", v.Name, v.MultilineString("\t"))
}
for _, v := range bpi.Locals {
if *bp.LoadLocals == LongLoadConfig {
fmt.Printf("\t%s: %s\n", v.Name, v.MultilineString("\t"))
} else {
fmt.Printf("\t%s: %s\n", v.Name, v.SinglelineString())
}
}
if bp.LoadArgs != nil && *bp.LoadArgs == LongLoadConfig {
for _, v := range bpi.Arguments {
fmt.Printf("\t%s: %s\n", v.Name, v.MultilineString("\t"))
}
fmt.Printf("\t%s\n", strings.Join(ss, ", "))
}
if bpi.Stacktrace != nil {
@ -1153,10 +1211,3 @@ func formatBreakpointLocation(bp *api.Breakpoint) string {
}
return fmt.Sprintf("%#v for %s:%d", bp.Addr, p, bp.Line)
}
func describeNoVars(varType string, data []string) []string {
if len(data) == 0 {
return []string{fmt.Sprintf("(no %s)", varType)}
}
return data
}

@ -357,3 +357,44 @@ func TestNoVars(t *testing.T) {
term.AssertExec("vars filterThatMatchesNothing", "(no vars)\n")
})
}
func TestOnPrefixLocals(t *testing.T) {
const prefix = "\ti: "
withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
term.MustExec("b agobp main.agoroutine")
term.MustExec("on agobp args -v")
seen := make([]bool, 10)
for {
outstr, err := term.Exec("continue")
if err != nil {
if strings.Index(err.Error(), "exited") < 0 {
t.Fatalf("Unexpected error executing 'continue': %v", err)
}
break
}
out := strings.Split(outstr, "\n")
for i := range out {
if !strings.HasPrefix(out[i], "\ti: ") {
continue
}
id, err := strconv.Atoi(out[i][len(prefix):])
if err != nil {
continue
}
if seen[id] {
t.Fatalf("Goroutine %d seen twice\n", id)
}
seen[id] = true
}
}
for i := range seen {
if !seen[i] {
t.Fatalf("Goroutine %d not seen\n", i)
}
}
})
}