diff --git a/_fixtures/testvariables3.go b/_fixtures/testvariables3.go index 72df030da9..e9329d5e6d 100644 --- a/_fixtures/testvariables3.go +++ b/_fixtures/testvariables3.go @@ -5,10 +5,49 @@ import ( "runtime" ) +type astruct struct { + A int + B int +} + +type bstruct struct { + a astruct +} + +type cstruct struct { + pb *bstruct + sa []*astruct +} + +func afunc(x int) int { + return x + 2 +} + +type functype func(int) int + func main() { i1 := 1 i2 := 2 + f1 := 3.0 + i3 := 3 p1 := &i1 + s1 := []string{"one", "two", "three", "four", "five"} + a1 := [5]string{"one", "two", "three", "four", "five"} + c1 := cstruct{&bstruct{astruct{1, 2}}, []*astruct{&astruct{1, 2}, &astruct{2, 3}, &astruct{4, 5}}} + s2 := []astruct{{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10}, {11, 12}, {13, 14}, {15, 16}} + p2 := &(c1.sa[2].B) + as1 := astruct{1, 1} + var p3 *int + str1 := "01234567890" + var fn1 functype = afunc + var fn2 functype = nil + var nilslice []int = nil + var nilptr *int = nil + + var amb1 = 1 runtime.Breakpoint() - fmt.Println(i1, i2, p1) + for amb1 := 0; amb1 < 10; amb1++ { + fmt.Println(amb1) + } + fmt.Println(i1, i2, i3, p1, amb1, s1, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr) } diff --git a/_fixtures/testvariables4.go b/_fixtures/testvariables4.go index 3492cdfe92..687de6ed19 100644 --- a/_fixtures/testvariables4.go +++ b/_fixtures/testvariables4.go @@ -22,8 +22,9 @@ type B struct { func main() { b := B{A: A{-314}, C: &C{"hello"}, a: A{42}, ptr: &A{1337}} + b2 := B{A: A{42}, a: A{47}} runtime.Breakpoint() - fmt.Println(b) + fmt.Println(b, b2) fmt.Println(b.val) fmt.Println(b.A.val) fmt.Println(b.a.val) diff --git a/dwarf/reader/reader.go b/dwarf/reader/reader.go index 5c00a96f22..f25f401621 100755 --- a/dwarf/reader/reader.go +++ b/dwarf/reader/reader.go @@ -151,6 +151,13 @@ func (reader *Reader) SeekToTypeNamed(name string) (*dwarf.Entry, error) { return nil, err } + switch entry.Tag { + case dwarf.TagArrayType, dwarf.TagBaseType, dwarf.TagClassType, dwarf.TagStructType, dwarf.TagUnionType, dwarf.TagConstType, dwarf.TagVolatileType, dwarf.TagRestrictType, dwarf.TagEnumerationType, dwarf.TagPointerType, dwarf.TagSubroutineType, dwarf.TagTypedef, dwarf.TagUnspecifiedType: + //ok + default: + continue + } + n, ok := entry.Val(dwarf.AttrName).(string) if !ok { continue diff --git a/proc/eval.go b/proc/eval.go new file mode 100644 index 0000000000..5f8d0d8ad8 --- /dev/null +++ b/proc/eval.go @@ -0,0 +1,759 @@ +package proc + +import ( + "bytes" + "debug/dwarf" + "fmt" + "go/ast" + "go/constant" + "go/parser" + "go/printer" + "go/token" + "reflect" +) + +// Returns the value of the given expression +func (scope *EvalScope) EvalExpression(expr string) (*Variable, error) { + t, err := parser.ParseExpr(expr) + if err != nil { + return nil, err + } + + ev, err := scope.evalAST(t) + if err != nil { + return nil, err + } + ev.loadValue() + return ev, nil +} + +func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) { + switch node := t.(type) { + case *ast.CallExpr: + if fnnode, ok := node.Fun.(*ast.Ident); ok && len(node.Args) == 2 && (fnnode.Name == "complex64" || fnnode.Name == "complex128") { + // implement the special case type casts complex64(f1, f2) and complex128(f1, f2) + return scope.evalComplexCast(fnnode.Name, node) + } + // this must be a type cast because we do not support function calls + return scope.evalTypeCast(node) + + case *ast.Ident: + return scope.evalIdent(node) + + case *ast.ParenExpr: + // otherwise just eval recursively + return scope.evalAST(node.X) + + case *ast.SelectorExpr: // . + // try to interpret the selector as a package variable + if maybePkg, ok := node.X.(*ast.Ident); ok { + if v, err := scope.packageVarAddr(maybePkg.Name + "." + node.Sel.Name); err == nil { + return v, nil + } + } + // if it's not a package variable then it must be a struct member access + return scope.evalStructSelector(node) + + case *ast.IndexExpr: + return scope.evalIndex(node) + + case *ast.SliceExpr: + if node.Slice3 { + return nil, fmt.Errorf("3-index slice expressions not supported") + } + return scope.evalReslice(node) + + case *ast.StarExpr: + // pointer dereferencing * + return scope.evalPointerDeref(node) + + case *ast.UnaryExpr: + // The unary operators we support are +, - and & (note that unary * is parsed as ast.StarExpr) + switch node.Op { + case token.AND: + return scope.evalAddrOf(node) + + default: + return scope.evalUnary(node) + } + + case *ast.BinaryExpr: + return scope.evalBinary(node) + + case *ast.BasicLit: + return newConstant(constant.MakeFromLiteral(node.Value, node.Kind, 0), scope.Thread), nil + + default: + return nil, fmt.Errorf("expression %T not implemented", t) + + } +} + +func exprToString(t ast.Expr) string { + var buf bytes.Buffer + printer.Fprint(&buf, token.NewFileSet(), t) + return buf.String() +} + +// Eval expressions: complex64(, ) and complex128(, ) +func (scope *EvalScope) evalComplexCast(typename string, node *ast.CallExpr) (*Variable, error) { + realev, err := scope.evalAST(node.Args[0]) + if err != nil { + return nil, err + } + imagev, err := scope.evalAST(node.Args[1]) + if err != nil { + return nil, err + } + + sz := 128 + ftypename := "float64" + if typename == "complex64" { + sz = 64 + ftypename = "float32" + } + + realev.loadValue() + imagev.loadValue() + + if realev.Unreadable != nil { + return nil, realev.Unreadable + } + + if imagev.Unreadable != nil { + return nil, imagev.Unreadable + } + + if realev.Value == nil || ((realev.Value.Kind() != constant.Int) && (realev.Value.Kind() != constant.Float)) { + return nil, fmt.Errorf("can not convert \"%s\" to %s", exprToString(node.Args[0]), ftypename) + } + + if imagev.Value == nil || ((imagev.Value.Kind() != constant.Int) && (imagev.Value.Kind() != constant.Float)) { + return nil, fmt.Errorf("can not convert \"%s\" to %s", exprToString(node.Args[1]), ftypename) + } + + typ := &dwarf.ComplexType{dwarf.BasicType{dwarf.CommonType{ByteSize: int64(sz / 8), Name: typename}, int64(sz), 0}} + + r := newVariable("", 0, typ, scope.Thread) + r.Value = constant.BinaryOp(realev.Value, token.ADD, constant.MakeImag(imagev.Value)) + return r, nil +} + +// Eval type cast expressions +func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { + if len(node.Args) != 1 { + return nil, fmt.Errorf("wrong number of arguments for a type cast") + } + + argv, err := scope.evalAST(node.Args[0]) + if err != nil { + return nil, err + } + argv.loadValue() + if argv.Unreadable != nil { + return nil, argv.Unreadable + } + + fnnode := node.Fun + + // remove all enclosing parenthesis from the type name + for { + p, ok := fnnode.(*ast.ParenExpr) + if !ok { + break + } + fnnode = p.X + } + + var typ dwarf.Type + + if snode, ok := fnnode.(*ast.StarExpr); ok { + // Pointer types only appear in the dwarf informations when + // a pointer to the type is used in the target program, here + // we create a pointer type on the fly so that the user can + // specify a pointer to any variable used in the target program + ptyp, err := scope.findType(exprToString(snode.X)) + if err != nil { + return nil, err + } + typ = &dwarf.PtrType{dwarf.CommonType{int64(scope.Thread.dbp.arch.PtrSize()), exprToString(fnnode)}, ptyp} + } else { + typ, err = scope.findType(exprToString(fnnode)) + if err != nil { + return nil, err + } + } + + // only supports cast of integer constants into pointers + ptyp, isptrtyp := typ.(*dwarf.PtrType) + if !isptrtyp { + return nil, fmt.Errorf("can not convert \"%s\" to %s", exprToString(node.Args[0]), typ.String()) + } + + switch argv.Kind { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + // ok + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + // ok + default: + return nil, fmt.Errorf("can not convert \"%s\" to %s", exprToString(node.Args[0]), typ.String()) + } + + n, _ := constant.Int64Val(argv.Value) + + v := newVariable("", 0, ptyp, scope.Thread) + v.Children = []Variable{*newVariable("", uintptr(n), ptyp.Type, scope.Thread)} + return v, nil +} + +// Evaluates identifier expressions +func (scope *EvalScope) evalIdent(node *ast.Ident) (*Variable, error) { + switch node.Name { + case "true", "false": + return newConstant(constant.MakeBool(node.Name == "true"), scope.Thread), nil + case "nil": + return nilVariable, nil + } + + // try to interpret this as a local variable + v, err := scope.extractVarInfo(node.Name) + if err != nil { + // if it's not a local variable then it could be a package variable w/o explicit package name + origErr := err + _, _, fn := scope.Thread.dbp.PCToLine(scope.PC) + if fn != nil { + if v, err := scope.packageVarAddr(fn.PackageName() + "." + node.Name); err == nil { + v.Name = node.Name + return v, nil + } + } + return nil, origErr + } + return v, nil +} + +// Evaluates expressions . where subexpr is not a package name +func (scope *EvalScope) evalStructSelector(node *ast.SelectorExpr) (*Variable, error) { + xv, err := scope.evalAST(node.X) + if err != nil { + return nil, err + } + return xv.structMember(node.Sel.Name) +} + +// Evaluates expressions [] (subscript access to arrays, slices and maps) +func (scope *EvalScope) evalIndex(node *ast.IndexExpr) (*Variable, error) { + xev, err := scope.evalAST(node.X) + if err != nil { + return nil, err + } + if xev.Unreadable != nil { + return nil, xev.Unreadable + } + if xev.base == 0 { + return nil, fmt.Errorf("can not index \"%s\"", exprToString(node.X)) + } + + idxev, err := scope.evalAST(node.Index) + if err != nil { + return nil, err + } + + switch xev.Kind { + case reflect.Slice, reflect.Array, reflect.String: + n, err := idxev.asInt() + if err != nil { + return nil, err + } + return xev.sliceAccess(int(n)) + + case reflect.Map: + return nil, fmt.Errorf("map access not implemented") + default: + return nil, fmt.Errorf("invalid expression \"%s\" (type %s does not support indexing)", exprToString(node.X), xev.DwarfType.String()) + + } +} + +// Evaluates expressions [:] +// HACK: slicing a map expression with [0:0] will return the whole map +func (scope *EvalScope) evalReslice(node *ast.SliceExpr) (*Variable, error) { + xev, err := scope.evalAST(node.X) + if err != nil { + return nil, err + } + if xev.Unreadable != nil { + return nil, xev.Unreadable + } + if xev.base == 0 { + return nil, fmt.Errorf("can not slice \"%s\"", exprToString(node.X)) + } + + switch xev.Kind { + case reflect.Slice, reflect.Array, reflect.String: + //ok + default: + return nil, fmt.Errorf("cannot slice \"%s\" (type %s)", exprToString(node.X), xev.DwarfType.String()) + } + + var low, high int64 + + if node.Low != nil { + lowv, err := scope.evalAST(node.Low) + if err != nil { + return nil, err + } + low, err = lowv.asInt() + if err != nil { + return nil, fmt.Errorf("can not convert \"%s\" to int: %v", exprToString(node.Low), err) + } + } + + if node.High == nil { + high = xev.Len + } else { + highv, err := scope.evalAST(node.High) + if err != nil { + return nil, err + } + high, err = highv.asInt() + if err != nil { + return nil, fmt.Errorf("can not convert \"%s\" to int: %v", exprToString(node.High), err) + } + } + + r, err := xev.reslice(low, high) + return r, err +} + +// Evaluates a pointer dereference expression: * +func (scope *EvalScope) evalPointerDeref(node *ast.StarExpr) (*Variable, error) { + xev, err := scope.evalAST(node.X) + if err != nil { + return nil, err + } + + if xev.DwarfType == nil { + return nil, fmt.Errorf("expression \"%s\" can not be dereferenced", exprToString(node.X)) + } + + if xev.Kind != reflect.Ptr { + return nil, fmt.Errorf("expression \"%s\" (%s) can not be dereferenced", exprToString(node.X), xev.DwarfType.String()) + } + + if len(xev.Children) == 1 { + // this branch is here to support pointers constructed with typecasts from ints + return &(xev.Children[0]), nil + } else { + rv := xev.maybeDereference() + if rv.Addr == 0 { + return nil, fmt.Errorf("nil pointer dereference") + } + return rv, nil + } +} + +// Evaluates expressions & +func (scope *EvalScope) evalAddrOf(node *ast.UnaryExpr) (*Variable, error) { + xev, err := scope.evalAST(node.X) + if err != nil { + return nil, err + } + if xev.Addr == 0 { + return nil, fmt.Errorf("can not take address of \"%s\"", exprToString(node.X)) + } + + xev.OnlyAddr = true + + typename := "*" + xev.DwarfType.String() + rv := newVariable("", 0, &dwarf.PtrType{dwarf.CommonType{ByteSize: int64(scope.Thread.dbp.arch.PtrSize()), Name: typename}, xev.DwarfType}, scope.Thread) + rv.Children = []Variable{*xev} + rv.loaded = true + + return rv, nil +} + +func constantUnaryOp(op token.Token, y constant.Value) (r constant.Value, err error) { + defer func() { + if ierr := recover(); ierr != nil { + err = fmt.Errorf("%v", ierr) + } + }() + r = constant.UnaryOp(op, y, 0) + return +} + +func constantBinaryOp(op token.Token, x, y constant.Value) (r constant.Value, err error) { + defer func() { + if ierr := recover(); ierr != nil { + err = fmt.Errorf("%v", ierr) + } + }() + switch op { + case token.SHL, token.SHR: + n, _ := constant.Uint64Val(y) + r = constant.Shift(x, op, uint(n)) + default: + r = constant.BinaryOp(x, op, y) + } + return +} + +func constantCompare(op token.Token, x, y constant.Value) (r bool, err error) { + defer func() { + if ierr := recover(); ierr != nil { + err = fmt.Errorf("%v", ierr) + } + }() + r = constant.Compare(x, op, y) + return +} + +// Evaluates expressions: - and + +func (scope *EvalScope) evalUnary(node *ast.UnaryExpr) (*Variable, error) { + xv, err := scope.evalAST(node.X) + if err != nil { + return nil, err + } + + xv.loadValue() + if xv.Unreadable != nil { + return nil, xv.Unreadable + } + if xv.Value == nil { + return nil, fmt.Errorf("operator %s can not be applied to \"%s\"", node.Op.String(), exprToString(node.X)) + } + rc, err := constantUnaryOp(node.Op, xv.Value) + if err != nil { + return nil, err + } + if xv.DwarfType != nil { + r := newVariable("", 0, xv.DwarfType, xv.thread) + r.Value = rc + return r, nil + } else { + return newConstant(rc, xv.thread), nil + } +} + +func negotiateType(op token.Token, xv, yv *Variable) (dwarf.Type, error) { + if op == token.SHR || op == token.SHL { + if xv.Value == nil || xv.Value.Kind() != constant.Int { + return nil, fmt.Errorf("shift of type %s", xv.Kind) + } + + switch yv.Kind { + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + // ok + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if yv.DwarfType != nil || constant.Sign(yv.Value) < 0 { + return nil, fmt.Errorf("shift count type %s, must be unsigned integer", yv.Kind.String()) + } + default: + return nil, fmt.Errorf("shift count type %s, must be unsigned integer", yv.Kind.String()) + } + + return xv.DwarfType, nil + } + + if xv.DwarfType == nil && yv.DwarfType == nil { + return nil, nil + } + + if xv.DwarfType != nil && yv.DwarfType != nil { + if xv.DwarfType.String() != yv.DwarfType.String() { + return nil, fmt.Errorf("mismatched types \"%s\" and \"%s\"", xv.DwarfType.String(), yv.DwarfType.String()) + } + return xv.DwarfType, nil + } else if xv.DwarfType != nil && yv.DwarfType == nil { + if err := yv.isType(xv.DwarfType, xv.Kind); err != nil { + return nil, err + } + return xv.DwarfType, nil + } else if xv.DwarfType == nil && yv.DwarfType != nil { + if err := xv.isType(yv.DwarfType, yv.Kind); err != nil { + return nil, err + } + return yv.DwarfType, nil + } + + panic("unreachable") +} + +func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) { + switch node.Op { + case token.INC, token.DEC, token.ARROW: + return nil, fmt.Errorf("operator %s not supported", node.Op.String()) + } + + xv, err := scope.evalAST(node.X) + if err != nil { + return nil, err + } + + yv, err := scope.evalAST(node.Y) + if err != nil { + return nil, err + } + + xv.loadValue() + yv.loadValue() + + if xv.Unreadable != nil { + return nil, xv.Unreadable + } + + if yv.Unreadable != nil { + return nil, yv.Unreadable + } + + typ, err := negotiateType(node.Op, xv, yv) + if err != nil { + return nil, err + } + + op := node.Op + if typ != nil && (op == token.QUO) { + _, isint := typ.(*dwarf.IntType) + _, isuint := typ.(*dwarf.UintType) + if isint || isuint { + // forces integer division if the result type is integer + op = token.QUO_ASSIGN + } + } + + switch op { + case token.EQL, token.LSS, token.GTR, token.NEQ, token.LEQ, token.GEQ: + v, err := compareOp(op, xv, yv) + if err != nil { + return nil, err + } + return newConstant(constant.MakeBool(v), xv.thread), nil + + default: + if xv.Value == nil { + return nil, fmt.Errorf("operator %s can not be applied to \"%s\"", node.Op.String(), exprToString(node.X)) + } + + if yv.Value == nil { + return nil, fmt.Errorf("operator %s can not be applied to \"%s\"", node.Op.String(), exprToString(node.Y)) + } + + rc, err := constantBinaryOp(op, xv.Value, yv.Value) + if err != nil { + return nil, err + } + + if typ == nil { + return newConstant(rc, xv.thread), nil + } else { + r := newVariable("", 0, typ, xv.thread) + r.Value = rc + return r, nil + } + } +} + +// Comapres xv to yv using operator op +// Both xv and yv must be loaded and have a compatible type (as determined by negotiateType) +func compareOp(op token.Token, xv *Variable, yv *Variable) (bool, error) { + switch xv.Kind { + case reflect.Bool: + fallthrough + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + fallthrough + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + fallthrough + case reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: + return constantCompare(op, xv.Value, yv.Value) + case reflect.String: + if int64(len(constant.StringVal(xv.Value))) != xv.Len || int64(len(constant.StringVal(yv.Value))) != yv.Len { + return false, fmt.Errorf("string too long for comparison") + } + return constantCompare(op, xv.Value, yv.Value) + } + + if op != token.EQL && op != token.NEQ { + return false, fmt.Errorf("operator %s not defined on %s", op.String(), xv.Kind.String()) + } + + var eql bool + var err error + + switch xv.Kind { + case reflect.Ptr: + eql = xv.Children[0].Addr == yv.Children[0].Addr + case reflect.Array: + if int64(len(xv.Children)) != xv.Len || int64(len(yv.Children)) != yv.Len { + return false, fmt.Errorf("array too long for comparison") + } + eql, err = equalChildren(xv, yv, true) + case reflect.Struct: + if len(xv.Children) != len(yv.Children) { + return false, nil + } + if int64(len(xv.Children)) != xv.Len || int64(len(yv.Children)) != yv.Len { + return false, fmt.Errorf("sturcture too deep for comparison") + } + eql, err = equalChildren(xv, yv, false) + case reflect.Slice, reflect.Map, reflect.Func: + if xv != nilVariable && yv != nilVariable { + return false, fmt.Errorf("can not compare %s variables", xv.Kind.String()) + } + + eql = xv.base == yv.base + default: + return false, fmt.Errorf("unimplemented comparison of %s variables", xv.Kind.String()) + } + + if op == token.NEQ { + return !eql, err + } + return eql, err +} + +func equalChildren(xv, yv *Variable, shortcircuit bool) (bool, error) { + r := true + for i := range xv.Children { + eql, err := compareOp(token.EQL, &xv.Children[i], &yv.Children[i]) + if err != nil { + return false, err + } + r = r && eql + if !r && shortcircuit { + return false, nil + } + } + return r, nil +} + +func (scope *EvalScope) findType(name string) (dwarf.Type, error) { + reader := scope.DwarfReader() + typentry, err := reader.SeekToTypeNamed(name) + if err != nil { + return nil, err + } + return scope.Thread.dbp.dwarf.Type(typentry.Offset) +} + +func (v *Variable) asInt() (int64, error) { + if v.DwarfType == nil { + if v.Value.Kind() != constant.Int { + return 0, fmt.Errorf("can not convert constant %s to int", v.Value) + } + } else { + v.loadValue() + if v.Unreadable != nil { + return 0, v.Unreadable + } + if _, ok := v.DwarfType.(*dwarf.IntType); !ok { + return 0, fmt.Errorf("can not convert value of type %s to int", v.DwarfType.String()) + } + } + n, _ := constant.Int64Val(v.Value) + return n, nil +} + +func (v *Variable) isType(typ dwarf.Type, kind reflect.Kind) error { + if v.DwarfType != nil { + if typ != nil && typ.String() != v.RealType.String() { + return fmt.Errorf("can not convert value of type %s to %s", v.DwarfType.String(), typ.String()) + } + return nil + } + + if typ == nil { + return nil + } + + if v == nilVariable { + switch kind { + case reflect.Slice, reflect.Map, reflect.Func, reflect.Ptr, reflect.Chan, reflect.Interface: + return nil + default: + return fmt.Errorf("mismatched types nil and %s", typ.String()) + } + } + + converr := fmt.Errorf("can not convert %s constant to %s", v.Value, typ.String()) + + if v.Value == nil { + return converr + } + + switch t := typ.(type) { + case *dwarf.IntType: + if v.Value.Kind() != constant.Int { + return converr + } + case *dwarf.UintType: + if v.Value.Kind() != constant.Int { + return converr + } + case *dwarf.FloatType: + if (v.Value.Kind() != constant.Int) && (v.Value.Kind() != constant.Float) { + return converr + } + case *dwarf.BoolType: + if v.Value.Kind() != constant.Bool { + return converr + } + case *dwarf.StructType: + if t.StructName != "string" { + return converr + } + if v.Value.Kind() != constant.String { + return converr + } + case *dwarf.ComplexType: + if v.Value.Kind() != constant.Complex && v.Value.Kind() != constant.Float && v.Value.Kind() != constant.Int { + return converr + } + default: + return converr + } + + return nil +} + +func (v *Variable) sliceAccess(idx int) (*Variable, error) { + if idx < 0 || int64(idx) >= v.Len { + return nil, fmt.Errorf("index out of bounds") + } + return newVariable("", v.base+uintptr(int64(idx)*v.stride), v.fieldType, v.thread), nil +} + +func (v *Variable) reslice(low int64, high int64) (*Variable, error) { + if low < 0 || low >= v.Len || high < 0 || high > v.Len { + return nil, fmt.Errorf("index out of bounds") + } + + base := v.base + uintptr(int64(low)*v.stride) + len := high - low + + if high-low < 0 { + return nil, fmt.Errorf("index out of bounds") + } + + typ := v.DwarfType + if _, isarr := v.DwarfType.(*dwarf.ArrayType); isarr { + typ = &dwarf.StructType{ + CommonType: dwarf.CommonType{ + ByteSize: 24, + Name: "", + }, + StructName: fmt.Sprintf("[]%s", v.fieldType), + Kind: "struct", + Field: nil, + } + } + + r := newVariable("", 0, typ, v.thread) + r.Cap = len + r.Len = len + r.base = base + r.stride = v.stride + r.fieldType = v.fieldType + + return r, nil +} diff --git a/proc/proc_test.go b/proc/proc_test.go index e7ae5ef4b1..7b2b84731b 100644 --- a/proc/proc_test.go +++ b/proc/proc_test.go @@ -10,7 +10,6 @@ import ( "path/filepath" "reflect" "runtime" - "strconv" "strings" "testing" "time" @@ -854,8 +853,8 @@ func TestVariableEvaluation(t *testing.T) { {"baz", reflect.String, "bazburzum", 9, 0, 0}, {"neg", reflect.Int, int64(-1), 0, 0, 0}, {"f32", reflect.Float32, float64(float32(1.2)), 0, 0, 0}, - {"c64", reflect.Complex64, nil, 2, 0, 2}, - {"c128", reflect.Complex128, nil, 2, 0, 2}, + {"c64", reflect.Complex64, complex128(complex64(1 + 2i)), 0, 0, 0}, + {"c128", reflect.Complex128, complex128(2 + 3i), 0, 0, 0}, {"a6.Baz", reflect.Int, int64(8), 0, 0, 0}, {"a7.Baz", reflect.Int, int64(5), 0, 0, 0}, {"a8.Baz", reflect.String, "feh", 3, 0, 0}, @@ -891,6 +890,12 @@ func TestVariableEvaluation(t *testing.T) { if y, ok := tc.value.(float64); !ok || x != y { t.Fatalf("%s value: expected: %v got: %v", tc.name, tc.value, v.Value) } + case reflect.Complex64, reflect.Complex128: + xr, _ := constant.Float64Val(constant.Real(v.Value)) + xi, _ := constant.Float64Val(constant.Imag(v.Value)) + if y, ok := tc.value.(complex128); !ok || complex(xr, xi) != y { + t.Fatalf("%s value: expected: %v got: %v", tc.name, tc.value, v.Value) + } case reflect.String: if y, ok := tc.value.(string); !ok || constant.StringVal(v.Value) != y { t.Fatalf("%s value: expected: %v got: %v", tc.name, tc.value, v.Value) @@ -992,9 +997,9 @@ func TestPointerSetting(t *testing.T) { // change p1 to point to i2 scope, err := p.CurrentThread.Scope() assertNoError(err, t, "Scope()") - i2addr, err := scope.ExtractVariableInfo("i2") - assertNoError(err, t, "EvalVariableAddr()") - assertNoError(setVariable(p, "p1", strconv.Itoa(int(i2addr.Addr))), t, "SetVariable()") + i2addr, err := scope.EvalExpression("i2") + assertNoError(err, t, "EvalExpression()") + assertNoError(setVariable(p, "p1", fmt.Sprintf("(*int)(0x%x)", i2addr.Addr)), t, "SetVariable()") pval(2) // change the value of i2 check that p1 also changes diff --git a/proc/variables.go b/proc/variables.go index e17bf349f8..0703081ef1 100644 --- a/proc/variables.go +++ b/proc/variables.go @@ -5,12 +5,10 @@ import ( "debug/dwarf" "encoding/binary" "fmt" - "go/ast" "go/constant" "go/parser" "go/token" "reflect" - "strconv" "strings" "unsafe" @@ -30,6 +28,7 @@ const ( // Represents a variable. type Variable struct { Addr uintptr + OnlyAddr bool Name string DwarfType dwarf.Type RealType dwarf.Type @@ -41,6 +40,9 @@ type Variable struct { Len int64 Cap int64 + // base address of arrays, base address of the backing array for slices (0 for nil slices) + // base address of the backing byte array for strings + // address of the function entry point for function variables (0 for nil function pointers) base uintptr stride int64 fieldType dwarf.Type @@ -125,6 +127,11 @@ func newVariable(name string, addr uintptr, dwarfType dwarf.Type, thread *Thread switch { case t.StructName == "string": v.Kind = reflect.String + v.stride = 1 + v.fieldType = &dwarf.UintType{dwarf.BasicType{dwarf.CommonType{1, "byte"}, 8, 0}} + if v.Addr != 0 { + v.base, v.Len, v.Unreadable = v.thread.readStringInfo(v.Addr) + } case strings.HasPrefix(t.StructName, "[]"): v.Kind = reflect.Slice if v.Addr != 0 { @@ -177,6 +184,31 @@ func newVariable(name string, addr uintptr, dwarfType dwarf.Type, thread *Thread return v } +func newConstant(val constant.Value, thread *Thread) *Variable { + v := &Variable{Value: val, thread: thread, loaded: true} + switch val.Kind() { + case constant.Int: + v.Kind = reflect.Int + case constant.Float: + v.Kind = reflect.Float64 + case constant.Bool: + v.Kind = reflect.Bool + case constant.Complex: + v.Kind = reflect.Complex128 + case constant.String: + v.Kind = reflect.String + v.Len = int64(len(constant.StringVal(val))) + } + return v +} + +var nilVariable = &Variable{ + Addr: 0, + base: 0, + Kind: reflect.Ptr, + Children: []Variable{{Addr: 0, OnlyAddr: true}}, +} + func (v *Variable) clone() *Variable { r := *v return &r @@ -395,62 +427,52 @@ func (g *G) Go() Location { return Location{PC: g.GoPC, File: f, Line: l, Fn: fn} } -// Returns information for the named variable. -func (scope *EvalScope) ExtractVariableInfo(name string) (*Variable, error) { - parts := strings.Split(name, ".") - varName := parts[0] - memberNames := parts[1:] +// Returns the value of the given expression (backwards compatibility). +func (scope *EvalScope) EvalVariable(name string) (*Variable, error) { + return scope.EvalExpression(name) +} - v, err := scope.extractVarInfo(varName) +// Sets the value of the named variable +func (scope *EvalScope) SetVariable(name, value string) error { + t, err := parser.ParseExpr(name) if err != nil { - origErr := err - // Attempt to evaluate name as a package variable. - if len(memberNames) > 0 { - v, err = scope.packageVarAddr(name) - } else { - _, _, fn := scope.Thread.dbp.PCToLine(scope.PC) - if fn != nil { - v, err = scope.packageVarAddr(fn.PackageName() + "." + name) - } - } - if err != nil { - return nil, origErr - } - v.Name = name - return v, nil - } else { - if len(memberNames) > 0 { - for i := range memberNames { - v, err = v.structMember(memberNames[i]) - if err != nil { - return nil, err - } - } - } + return err } - return v, nil -} -// Returns the value of the named variable. -func (scope *EvalScope) EvalVariable(name string) (*Variable, error) { - v, err := scope.ExtractVariableInfo(name) + xv, err := scope.evalAST(t) if err != nil { - return nil, err + return err } - v.loadValue() - return v, nil -} -// Sets the value of the named variable -func (scope *EvalScope) SetVariable(name, value string) error { - v, err := scope.ExtractVariableInfo(name) + if xv.Addr == 0 { + return fmt.Errorf("Can not assign to \"%s\"", name) + } + + if xv.Unreadable != nil { + return fmt.Errorf("Expression \"%s\" is unreadable: %v", name, xv.Unreadable) + } + + t, err = parser.ParseExpr(value) if err != nil { return err } - if v.Unreadable != nil { - return fmt.Errorf("Variable \"%s\" is unreadable: %v\n", name, v.Unreadable) + + yv, err := scope.evalAST(t) + if err != nil { + return err + } + + yv.loadValue() + + if err := yv.isType(xv.RealType, xv.Kind); err != nil { + return err } - return v.setValue(value) + + if yv.Unreadable != nil { + return fmt.Errorf("Expression \"%s\" is unreadable: %v", value, yv.Unreadable) + } + + return xv.setValue(yv) } func (scope *EvalScope) extractVariableFromEntry(entry *dwarf.Entry) (*Variable, error) { @@ -601,7 +623,7 @@ func (v *Variable) structMember(memberName string) (*Variable, error) { } return nil, fmt.Errorf("%s has no member %s", v.Name, memberName) default: - return nil, fmt.Errorf("%s type %s is not a struct", v.Name, structVar.DwarfType) + return nil, fmt.Errorf("%s (type %s) is not a struct", v.Name, structVar.DwarfType) } } @@ -670,7 +692,7 @@ func (v *Variable) loadValue() { } func (v *Variable) loadValueInternal(recurseLevel int) { - if v.Unreadable != nil || v.loaded || v.Addr == 0 { + if v.Unreadable != nil || v.loaded || (v.Addr == 0 && v.base == 0) { return } v.loaded = true @@ -683,7 +705,7 @@ func (v *Variable) loadValueInternal(recurseLevel int) { case reflect.String: var val string - val, v.Len, v.Unreadable = v.thread.readString(uintptr(v.Addr)) + val, v.Unreadable = v.thread.readStringValue(v.base, v.Len) v.Value = constant.MakeString(val) case reflect.Slice, reflect.Array: @@ -734,62 +756,87 @@ func (v *Variable) loadValueInternal(recurseLevel int) { } } -func (v *Variable) setValue(value string) error { - switch t := v.RealType.(type) { - case *dwarf.PtrType: - return v.writeUint(false, value, int64(v.thread.dbp.arch.PtrSize())) - case *dwarf.ComplexType: - return v.writeComplex(value, t.ByteSize) - case *dwarf.IntType: - return v.writeUint(true, value, t.ByteSize) - case *dwarf.UintType: - return v.writeUint(false, value, t.ByteSize) - case *dwarf.FloatType: - return v.writeFloat(value, t.ByteSize) - case *dwarf.BoolType: - return v.writeBool(value) +func (v *Variable) setValue(y *Variable) error { + var err error + switch v.Kind { + case reflect.Float32, reflect.Float64: + f, _ := constant.Float64Val(y.Value) + err = v.writeFloatRaw(f, v.RealType.Size()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + n, _ := constant.Int64Val(y.Value) + err = v.writeUint(uint64(n), v.RealType.Size()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + n, _ := constant.Uint64Val(y.Value) + err = v.writeUint(n, v.RealType.Size()) + case reflect.Bool: + err = v.writeBool(constant.BoolVal(y.Value)) + case reflect.Complex64, reflect.Complex128: + real, _ := constant.Float64Val(constant.Real(y.Value)) + imag, _ := constant.Float64Val(constant.Imag(y.Value)) + err = v.writeComplex(real, imag, v.RealType.Size()) default: - return fmt.Errorf("Can not set value of variables of kind: %s\n", v.RealType.String()) + fmt.Printf("default\n") + if _, isptr := v.RealType.(*dwarf.PtrType); isptr { + err = v.writeUint(uint64(y.Children[0].Addr), int64(v.thread.dbp.arch.PtrSize())) + } else { + return fmt.Errorf("can not set variables of type %s (not implemented)", v.Kind.String()) + } } + + return err } -func (thread *Thread) readString(addr uintptr) (string, int64, error) { +func (thread *Thread) readStringInfo(addr uintptr) (uintptr, int64, error) { // string data structure is always two ptrs in size. Addr, followed by len // http://research.swtch.com/godata // read len val, err := thread.readMemory(addr+uintptr(thread.dbp.arch.PtrSize()), thread.dbp.arch.PtrSize()) if err != nil { - return "", 0, fmt.Errorf("could not read string len %s", err) + return 0, 0, fmt.Errorf("could not read string len %s", err) } strlen := int64(binary.LittleEndian.Uint64(val)) if strlen < 0 { - return "", 0, fmt.Errorf("invalid length: %d", strlen) - } - - count := strlen - if count > maxArrayValues { - count = maxArrayValues + return 0, 0, fmt.Errorf("invalid length: %d", strlen) } // read addr val, err = thread.readMemory(addr, thread.dbp.arch.PtrSize()) if err != nil { - return "", 0, fmt.Errorf("could not read string pointer %s", err) + return 0, 0, fmt.Errorf("could not read string pointer %s", err) } addr = uintptr(binary.LittleEndian.Uint64(val)) if addr == 0 { - return "", 0, nil + return 0, 0, nil } - val, err = thread.readMemory(addr, int(count)) + return addr, strlen, nil +} + +func (thread *Thread) readStringValue(addr uintptr, strlen int64) (string, error) { + count := strlen + if count > maxArrayValues { + count = maxArrayValues + } + + val, err := thread.readMemory(addr, int(count)) if err != nil { - return "", 0, fmt.Errorf("could not read string at %#v due to %s", addr, err) + return "", fmt.Errorf("could not read string at %#v due to %s", addr, err) } retstr := *(*string)(unsafe.Pointer(&val)) - return retstr, strlen, nil + return retstr, nil +} + +func (thread *Thread) readString(addr uintptr) (string, int64, error) { + addr, strlen, err := thread.readStringInfo(addr) + if err != nil { + return "", 0, err + } + + retstr, err := thread.readStringValue(addr, strlen) + return retstr, strlen, err } func (v *Variable) loadSliceInfo(t *dwarf.StructType) { @@ -883,78 +930,11 @@ func (v *Variable) readComplex(size int64) { imagvar := newVariable("imaginary", v.Addr+uintptr(fs), ftyp, v.thread) realvar.loadValue() imagvar.loadValue() - v.Len = 2 - v.Children = []Variable{*realvar, *imagvar} - v.Value = constant.BinaryOp(realvar.Value, token.ADD, imagvar.Value) + v.Value = constant.BinaryOp(realvar.Value, token.ADD, constant.MakeImag(imagvar.Value)) } -func (v *Variable) writeComplex(value string, size int64) error { - var real, imag float64 - - expr, err := parser.ParseExpr(value) - if err != nil { - return err - } - - var lits []*ast.BasicLit - - if e, ok := expr.(*ast.ParenExpr); ok { - expr = e.X - } - - switch e := expr.(type) { - case *ast.BinaryExpr: // " + i" or "i + " - x, xok := e.X.(*ast.BasicLit) - y, yok := e.Y.(*ast.BasicLit) - if e.Op != token.ADD || !xok || !yok { - return fmt.Errorf("Not a complex constant: %s", value) - } - lits = []*ast.BasicLit{x, y} - case *ast.CallExpr: // "complex(, )" - tname, ok := e.Fun.(*ast.Ident) - if !ok { - return fmt.Errorf("Not a complex constant: %s", value) - } - if (tname.Name != "complex64") && (tname.Name != "complex128") { - return fmt.Errorf("Not a complex constant: %s", value) - } - if len(e.Args) != 2 { - return fmt.Errorf("Not a complex constant: %s", value) - } - for i := range e.Args { - lit, ok := e.Args[i].(*ast.BasicLit) - if !ok { - return fmt.Errorf("Not a complex constant: %s", value) - } - lits = append(lits, lit) - } - lits[1].Kind = token.IMAG - lits[1].Value = lits[1].Value + "i" - case *ast.BasicLit: // "" or "i" - lits = []*ast.BasicLit{e} - default: - return fmt.Errorf("Not a complex constant: %s", value) - } - - for _, lit := range lits { - var err error - var v float64 - switch lit.Kind { - case token.FLOAT, token.INT: - v, err = strconv.ParseFloat(lit.Value, int(size/2)) - real += v - case token.IMAG: - v, err = strconv.ParseFloat(lit.Value[:len(lit.Value)-1], int(size/2)) - imag += v - default: - return fmt.Errorf("Not a complex constant: %s", value) - } - if err != nil { - return err - } - } - - err = v.writeFloatRaw(real, int64(size/2)) +func (v *Variable) writeComplex(real, imag float64, size int64) error { + err := v.writeFloatRaw(real, int64(size/2)) if err != nil { return err } @@ -985,36 +965,21 @@ func (thread *Thread) readIntRaw(addr uintptr, size int64) (int64, error) { return n, nil } -func (v *Variable) writeUint(signed bool, value string, size int64) error { - var ( - n uint64 - err error - ) - if signed { - var m int64 - m, err = strconv.ParseInt(value, 0, int(size*8)) - n = uint64(m) - } else { - n, err = strconv.ParseUint(value, 0, int(size*8)) - } - if err != nil { - return err - } - +func (v *Variable) writeUint(value uint64, size int64) error { val := make([]byte, size) switch size { case 1: - val[0] = byte(n) + val[0] = byte(value) case 2: - binary.LittleEndian.PutUint16(val, uint16(n)) + binary.LittleEndian.PutUint16(val, uint16(value)) case 4: - binary.LittleEndian.PutUint32(val, uint32(n)) + binary.LittleEndian.PutUint32(val, uint32(value)) case 8: - binary.LittleEndian.PutUint64(val, uint64(n)) + binary.LittleEndian.PutUint64(val, uint64(value)) } - _, err = v.thread.writeMemory(v.Addr, val) + _, err := v.thread.writeMemory(v.Addr, val) return err } @@ -1061,14 +1026,6 @@ func (v *Variable) readFloatRaw(size int64) (float64, error) { return 0.0, fmt.Errorf("could not read float") } -func (v *Variable) writeFloat(value string, size int64) error { - f, err := strconv.ParseFloat(value, int(size*8)) - if err != nil { - return err - } - return v.writeFloatRaw(f, size) -} - func (v *Variable) writeFloatRaw(f float64, size int64) error { buf := bytes.NewBuffer(make([]byte, 0, size)) @@ -1085,16 +1042,10 @@ func (v *Variable) writeFloatRaw(f float64, size int64) error { return err } -func (v *Variable) writeBool(value string) error { - b, err := strconv.ParseBool(value) - if err != nil { - return err - } +func (v *Variable) writeBool(value bool) error { val := []byte{0} - if b { - val[0] = *(*byte)(unsafe.Pointer(&b)) - } - _, err = v.thread.writeMemory(v.Addr, val) + val[0] = *(*byte)(unsafe.Pointer(&value)) + _, err := v.thread.writeMemory(v.Addr, val) return err } @@ -1108,7 +1059,8 @@ func (v *Variable) readFunctionPtr() { // dereference pointer to find function pc fnaddr := uintptr(binary.LittleEndian.Uint64(val)) if fnaddr == 0 { - v.Unreadable = err + v.base = 0 + v.Value = constant.MakeString("") return } @@ -1118,10 +1070,10 @@ func (v *Variable) readFunctionPtr() { return } - funcAddr := binary.LittleEndian.Uint64(val) - fn := v.thread.dbp.goSymTable.PCToFunc(uint64(funcAddr)) + v.base = uintptr(binary.LittleEndian.Uint64(val)) + fn := v.thread.dbp.goSymTable.PCToFunc(uint64(v.base)) if fn == nil { - v.Unreadable = fmt.Errorf("could not find function for %#v", funcAddr) + v.Unreadable = fmt.Errorf("could not find function for %#v", v.base) return } diff --git a/service/api/conversions.go b/service/api/conversions.go index 18784f219f..c0843ecde5 100644 --- a/service/api/conversions.go +++ b/service/api/conversions.go @@ -58,11 +58,12 @@ func ConvertThread(th *proc.Thread) *Thread { func ConvertVar(v *proc.Variable) *Variable { r := Variable{ - Addr: v.Addr, - Name: v.Name, - Kind: v.Kind, - Len: v.Len, - Cap: v.Cap, + Addr: v.Addr, + OnlyAddr: v.OnlyAddr, + Name: v.Name, + Kind: v.Kind, + Len: v.Len, + Cap: v.Cap, } if v.DwarfType != nil { @@ -92,10 +93,42 @@ func ConvertVar(v *proc.Variable) *Variable { } } - r.Children = make([]Variable, len(v.Children)) + switch v.Kind { + case reflect.Complex64: + r.Children = make([]Variable, 2) + r.Len = 2 - for i := range v.Children { - r.Children[i] = *ConvertVar(&v.Children[i]) + real, _ := constant.Float64Val(constant.Real(v.Value)) + imag, _ := constant.Float64Val(constant.Imag(v.Value)) + + r.Children[0].Name = "real" + r.Children[0].Kind = reflect.Float32 + r.Children[0].Value = strconv.FormatFloat(real, 'f', -1, 32) + + r.Children[1].Name = "imaginary" + r.Children[1].Kind = reflect.Float32 + r.Children[1].Value = strconv.FormatFloat(imag, 'f', -1, 32) + case reflect.Complex128: + r.Children = make([]Variable, 2) + r.Len = 2 + + real, _ := constant.Float64Val(constant.Real(v.Value)) + imag, _ := constant.Float64Val(constant.Imag(v.Value)) + + r.Children[0].Name = "real" + r.Children[0].Kind = reflect.Float64 + r.Children[0].Value = strconv.FormatFloat(real, 'f', -1, 64) + + r.Children[1].Name = "imaginary" + r.Children[1].Kind = reflect.Float64 + r.Children[1].Value = strconv.FormatFloat(imag, 'f', -1, 64) + + default: + r.Children = make([]Variable, len(v.Children)) + + for i := range v.Children { + r.Children[i] = *ConvertVar(&v.Children[i]) + } } return &r diff --git a/service/api/prettyprint.go b/service/api/prettyprint.go index df1d118ed3..3546e0c5ef 100644 --- a/service/api/prettyprint.go +++ b/service/api/prettyprint.go @@ -16,7 +16,7 @@ const ( // Returns a representation of v on a single line func (v *Variable) SinglelineString() string { var buf bytes.Buffer - v.writeTo(&buf, false, true, "") + v.writeTo(&buf, true, false, true, "") return buf.String() } @@ -24,17 +24,17 @@ func (v *Variable) SinglelineString() string { func (v *Variable) MultilineString(indent string) string { var buf bytes.Buffer buf.WriteString(indent) - v.writeTo(&buf, true, true, indent) + v.writeTo(&buf, true, true, true, indent) return buf.String() } -func (v *Variable) writeTo(buf *bytes.Buffer, newlines, includeType bool, indent string) { +func (v *Variable) writeTo(buf *bytes.Buffer, top, newlines, includeType bool, indent string) { if v.Unreadable != "" { fmt.Fprintf(buf, "(unreadable %s)", v.Unreadable) return } - if v.Addr == 0 { + if !top && v.Addr == 0 { fmt.Fprintf(buf, "%s nil", v.Type) return } @@ -45,8 +45,14 @@ func (v *Variable) writeTo(buf *bytes.Buffer, newlines, includeType bool, indent case reflect.Array: v.writeArrayTo(buf, newlines, includeType, indent) case reflect.Ptr: - fmt.Fprintf(buf, "*") - v.Children[0].writeTo(buf, newlines, includeType, indent) + if v.Type == "" { + fmt.Fprintf(buf, "nil") + } else if v.Children[0].OnlyAddr { + fmt.Fprintf(buf, "(%s)(0x%x)", v.Type, v.Children[0].Addr) + } else { + fmt.Fprintf(buf, "*") + v.Children[0].writeTo(buf, false, newlines, includeType, indent) + } case reflect.String: v.writeStringTo(buf) case reflect.Struct: @@ -54,14 +60,13 @@ func (v *Variable) writeTo(buf *bytes.Buffer, newlines, includeType bool, indent case reflect.Map: v.writeMapTo(buf, newlines, includeType, indent) case reflect.Func: - fmt.Fprintf(buf, "%s", v.Value) - case reflect.Complex64, reflect.Complex128: - switch v.RealType { - case "complex64": - fmt.Fprintf(buf, "(%s + %si)", v.Children[0].Value, v.Children[1].Value) - case "complex128": - fmt.Fprintf(buf, "(%s + %si)", v.Children[0].Value, v.Children[1].Value) + if v.Value == "" { + fmt.Fprintf(buf, "nil") + } else { + fmt.Fprintf(buf, "%s", v.Value) } + case reflect.Complex64, reflect.Complex128: + fmt.Fprintf(buf, "(%s + %si)", v.Children[0].Value, v.Children[1].Value) default: if v.Value != "" { buf.Write([]byte(v.Value)) @@ -112,7 +117,7 @@ func (v *Variable) writeStructTo(buf *bytes.Buffer, newlines, includeType bool, fmt.Fprintf(buf, "\n%s%s", indent, indentString) } fmt.Fprintf(buf, "%s: ", v.Children[i].Name) - v.Children[i].writeTo(buf, nl, true, indent+indentString) + v.Children[i].writeTo(buf, false, nl, true, indent+indentString) if i != len(v.Children)-1 || nl { fmt.Fprintf(buf, ",") if !nl { @@ -144,9 +149,9 @@ func (v *Variable) writeMapTo(buf *bytes.Buffer, newlines, includeType bool, ind fmt.Fprintf(buf, "\n%s%s", indent, indentString) } - key.writeTo(buf, false, false, indent+indentString) + key.writeTo(buf, false, false, false, indent+indentString) fmt.Fprintf(buf, ": ") - value.writeTo(buf, nl, false, indent+indentString) + value.writeTo(buf, false, nl, false, indent+indentString) if i != len(v.Children)-1 || nl { fmt.Fprintf(buf, ", ") } @@ -239,7 +244,7 @@ func (v *Variable) writeSliceOrArrayTo(buf *bytes.Buffer, newlines bool, indent if nl { fmt.Fprintf(buf, "\n%s%s", indent, indentString) } - v.Children[i].writeTo(buf, nl, false, indent+indentString) + v.Children[i].writeTo(buf, false, nl, false, indent+indentString) if i != len(v.Children)-1 || nl { fmt.Fprintf(buf, ",") } diff --git a/service/api/types.go b/service/api/types.go index c74449173a..12c635df33 100644 --- a/service/api/types.go +++ b/service/api/types.go @@ -110,6 +110,8 @@ type Variable struct { Name string `json:"name"` // Address of the variable or struct member Addr uintptr `json:"addr"` + // Only the address field is filled (result of evaluating expressions like &) + OnlyAddr bool `json:"onlyAddr"` // Go type of the variable Type string `json:"type"` // Type of the variable after resolving any typedefs diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 4d8e0c7733..4df3ff21b2 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -13,11 +13,12 @@ import ( ) type varTest struct { - name string - value string - setTo string - varType string - err error + name string + preserveName bool + value string + setTo string + varType string + err error } func matchStringOrPrefix(output, target string) bool { @@ -28,12 +29,13 @@ func matchStringOrPrefix(output, target string) bool { } else { return output == target } - } func assertVariable(t *testing.T, variable *proc.Variable, expected varTest) { - if variable.Name != expected.name { - t.Fatalf("Expected %s got %s\n", expected.name, variable.Name) + if expected.preserveName { + if variable.Name != expected.name { + t.Fatalf("Expected %s got %s\n", expected.name, variable.Name) + } } cv := api.ConvertVar(variable) @@ -92,47 +94,47 @@ func withTestProcess(name string, t *testing.T, fn func(p *proc.Process, fixture func TestVariableEvaluation(t *testing.T) { testcases := []varTest{ - {"a1", "\"foofoofoofoofoofoo\"", "", "struct string", nil}, - {"a11", "[3]main.FooBar [{Baz: 1, Bur: \"a\"},{Baz: 2, Bur: \"b\"},{Baz: 3, Bur: \"c\"}]", "", "[3]main.FooBar", nil}, - {"a12", "[]main.FooBar len: 2, cap: 2, [{Baz: 4, Bur: \"d\"},{Baz: 5, Bur: \"e\"}]", "", "struct []main.FooBar", nil}, - {"a13", "[]*main.FooBar len: 3, cap: 3, [*{Baz: 6, Bur: \"f\"},*{Baz: 7, Bur: \"g\"},*{Baz: 8, Bur: \"h\"}]", "", "struct []*main.FooBar", nil}, - {"a2", "6", "10", "int", nil}, - {"a3", "7.23", "3.1", "float64", nil}, - {"a4", "[2]int [1,2]", "", "[2]int", nil}, - {"a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "", "struct []int", nil}, - {"a6", "main.FooBar {Baz: 8, Bur: \"word\"}", "", "main.FooBar", nil}, - {"a7", "*main.FooBar {Baz: 5, Bur: \"strum\"}", "", "*main.FooBar", nil}, - {"a8", "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil}, - {"a9", "*main.FooBar nil", "", "*main.FooBar", nil}, - {"baz", "\"bazburzum\"", "", "struct string", nil}, - {"neg", "-1", "-20", "int", nil}, - {"f32", "1.2", "1.1", "float32", nil}, - {"c64", "(1 + 2i)", "(4 + 5i)", "complex64", nil}, - {"c128", "(2 + 3i)", "(6.3 + 7i)", "complex128", nil}, - {"a6.Baz", "8", "20", "int", nil}, - {"a7.Baz", "5", "25", "int", nil}, - {"a8.Baz", "\"feh\"", "", "struct string", nil}, - {"a9.Baz", "nil", "", "int", fmt.Errorf("a9 is nil")}, - {"a9.NonExistent", "nil", "", "int", fmt.Errorf("a9 has no member NonExistent")}, - {"a8", "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil}, // reread variable after member - {"i32", "[2]int32 [1,2]", "", "[2]int32", nil}, - {"b1", "true", "false", "bool", nil}, - {"b2", "false", "true", "bool", nil}, - {"i8", "1", "2", "int8", nil}, - {"u16", "65535", "0", "uint16", nil}, - {"u32", "4294967295", "1", "uint32", nil}, - {"u64", "18446744073709551615", "2", "uint64", nil}, - {"u8", "255", "3", "uint8", nil}, - {"up", "5", "4", "uintptr", nil}, - {"f", "main.barfoo", "", "func()", nil}, - {"ba", "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "", "struct []int", nil}, - {"ms", "main.Nest {Level: 0, Nest: *main.Nest {Level: 1, Nest: *(*main.Nest)(…", "", "main.Nest", nil}, - {"ms.Nest.Nest", "*main.Nest {Level: 2, Nest: *main.Nest {Level: 3, Nest: *(*main.Nest)(…", "", "*main.Nest", nil}, - {"ms.Nest.Nest.Nest.Nest.Nest", "*main.Nest nil", "", "*main.Nest", nil}, - {"ms.Nest.Nest.Nest.Nest.Nest.Nest", "", "", "*main.Nest", fmt.Errorf("ms.Nest.Nest.Nest.Nest.Nest is nil")}, - {"main.p1", "10", "12", "int", nil}, - {"p1", "10", "13", "int", nil}, - {"NonExistent", "", "", "", fmt.Errorf("could not find symbol value for NonExistent")}, + {"a1", true, "\"foofoofoofoofoofoo\"", "", "struct string", nil}, + {"a11", true, "[3]main.FooBar [{Baz: 1, Bur: \"a\"},{Baz: 2, Bur: \"b\"},{Baz: 3, Bur: \"c\"}]", "", "[3]main.FooBar", nil}, + {"a12", true, "[]main.FooBar len: 2, cap: 2, [{Baz: 4, Bur: \"d\"},{Baz: 5, Bur: \"e\"}]", "", "struct []main.FooBar", nil}, + {"a13", true, "[]*main.FooBar len: 3, cap: 3, [*{Baz: 6, Bur: \"f\"},*{Baz: 7, Bur: \"g\"},*{Baz: 8, Bur: \"h\"}]", "", "struct []*main.FooBar", nil}, + {"a2", true, "6", "10", "int", nil}, + {"a3", true, "7.23", "3.1", "float64", nil}, + {"a4", true, "[2]int [1,2]", "", "[2]int", nil}, + {"a5", true, "[]int len: 5, cap: 5, [1,2,3,4,5]", "", "struct []int", nil}, + {"a6", true, "main.FooBar {Baz: 8, Bur: \"word\"}", "", "main.FooBar", nil}, + {"a7", true, "*main.FooBar {Baz: 5, Bur: \"strum\"}", "", "*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\"", "", "struct string", nil}, + {"neg", true, "-1", "-20", "int", nil}, + {"f32", true, "1.2", "1.1", "float32", nil}, + {"c64", true, "(1 + 2i)", "(4 + 5i)", "complex64", nil}, + {"c128", true, "(2 + 3i)", "(6.3 + 7i)", "complex128", nil}, + {"a6.Baz", true, "8", "20", "int", nil}, + {"a7.Baz", true, "5", "25", "int", nil}, + {"a8.Baz", true, "\"feh\"", "", "struct 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 [1,2]", "", "[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, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "", "struct []int", nil}, + {"ms", true, "main.Nest {Level: 0, Nest: *main.Nest {Level: 1, Nest: *(*main.Nest)(…", "", "main.Nest", nil}, + {"ms.Nest.Nest", true, "*main.Nest {Level: 2, Nest: *main.Nest {Level: 3, Nest: *(*main.Nest)(…", "", "*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", "12", "int", nil}, + {"p1", true, "10", "13", "int", nil}, + {"NonExistent", true, "", "", "", fmt.Errorf("could not find symbol value for NonExistent")}, } withTestProcess("testvariables", t, func(p *proc.Process, fixture protest.Fixture) { @@ -170,32 +172,32 @@ func TestVariableEvaluation(t *testing.T) { func TestMultilineVariableEvaluation(t *testing.T) { testcases := []varTest{ - {"a1", "\"foofoofoofoofoofoo\"", "", "struct string", nil}, - {"a11", `[3]main.FooBar [ + {"a1", true, "\"foofoofoofoofoofoo\"", "", "struct string", nil}, + {"a11", true, `[3]main.FooBar [ {Baz: 1, Bur: "a"}, {Baz: 2, Bur: "b"}, {Baz: 3, Bur: "c"}, ]`, "", "[3]main.FooBar", nil}, - {"a12", `[]main.FooBar len: 2, cap: 2, [ + {"a12", true, `[]main.FooBar len: 2, cap: 2, [ {Baz: 4, Bur: "d"}, {Baz: 5, Bur: "e"}, ]`, "", "struct []main.FooBar", nil}, - {"a13", `[]*main.FooBar len: 3, cap: 3, [ + {"a13", true, `[]*main.FooBar len: 3, cap: 3, [ *{Baz: 6, Bur: "f"}, *{Baz: 7, Bur: "g"}, *{Baz: 8, Bur: "h"}, ]`, "", "struct []*main.FooBar", nil}, - {"a2", "6", "10", "int", nil}, - {"a4", "[2]int [1,2]", "", "[2]int", nil}, - {"a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "", "struct []int", nil}, - {"a6", "main.FooBar {Baz: 8, Bur: \"word\"}", "", "main.FooBar", nil}, - {"a7", "*main.FooBar {Baz: 5, Bur: \"strum\"}", "", "*main.FooBar", nil}, - {"a8", "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil}, - {"a9", "*main.FooBar nil", "", "*main.FooBar", nil}, - {"a8", "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil}, // reread variable after member - {"i32", "[2]int32 [1,2]", "", "[2]int32", nil}, - {"ba", "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "", "struct []int", nil}, - {"ms", `main.Nest { + {"a2", true, "6", "10", "int", nil}, + {"a4", true, "[2]int [1,2]", "", "[2]int", nil}, + {"a5", true, "[]int len: 5, cap: 5, [1,2,3,4,5]", "", "struct []int", nil}, + {"a6", true, "main.FooBar {Baz: 8, Bur: \"word\"}", "", "main.FooBar", nil}, + {"a7", true, "*main.FooBar {Baz: 5, Bur: \"strum\"}", "", "*main.FooBar", nil}, + {"a8", true, "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil}, + {"a9", true, "*main.FooBar nil", "", "*main.FooBar", nil}, + {"a8", true, "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil}, // reread variable after member + {"i32", true, "[2]int32 [1,2]", "", "[2]int32", nil}, + {"ba", true, "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "", "struct []int", nil}, + {"ms", true, `main.Nest { Level: 0, Nest: *main.Nest { Level: 1, @@ -240,39 +242,39 @@ func TestLocalVariables(t *testing.T) { }{ {(*proc.EvalScope).LocalVariables, []varTest{ - {"a1", "\"foofoofoofoofoofoo\"", "", "struct string", nil}, - {"a10", "\"ofo\"", "", "struct string", nil}, - {"a11", "[3]main.FooBar [{Baz: 1, Bur: \"a\"},{Baz: 2, Bur: \"b\"},{Baz: 3, Bur: \"c\"}]", "", "[3]main.FooBar", nil}, - {"a12", "[]main.FooBar len: 2, cap: 2, [{Baz: 4, Bur: \"d\"},{Baz: 5, Bur: \"e\"}]", "", "struct []main.FooBar", nil}, - {"a13", "[]*main.FooBar len: 3, cap: 3, [*{Baz: 6, Bur: \"f\"},*{Baz: 7, Bur: \"g\"},*{Baz: 8, Bur: \"h\"}]", "", "struct []*main.FooBar", nil}, - {"a2", "6", "", "int", nil}, - {"a3", "7.23", "", "float64", nil}, - {"a4", "[2]int [1,2]", "", "[2]int", nil}, - {"a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "", "struct []int", nil}, - {"a6", "main.FooBar {Baz: 8, Bur: \"word\"}", "", "main.FooBar", nil}, - {"a7", "*main.FooBar {Baz: 5, Bur: \"strum\"}", "", "*main.FooBar", nil}, - {"a8", "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil}, - {"a9", "*main.FooBar nil", "", "*main.FooBar", nil}, - {"b1", "true", "", "bool", nil}, - {"b2", "false", "", "bool", nil}, - {"ba", "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "", "struct []int", nil}, - {"c128", "(2 + 3i)", "", "complex128", nil}, - {"c64", "(1 + 2i)", "", "complex64", nil}, - {"f", "main.barfoo", "", "func()", nil}, - {"f32", "1.2", "", "float32", nil}, - {"i32", "[2]int32 [1,2]", "", "[2]int32", nil}, - {"i8", "1", "", "int8", nil}, - {"ms", "main.Nest {Level: 0, Nest: *main.Nest {Level: 1, Nest: *(*main.Nest)…", "", "main.Nest", nil}, - {"neg", "-1", "", "int", nil}, - {"u16", "65535", "", "uint16", nil}, - {"u32", "4294967295", "", "uint32", nil}, - {"u64", "18446744073709551615", "", "uint64", nil}, - {"u8", "255", "", "uint8", nil}, - {"up", "5", "", "uintptr", nil}}}, + {"a1", true, "\"foofoofoofoofoofoo\"", "", "struct string", nil}, + {"a10", true, "\"ofo\"", "", "struct string", nil}, + {"a11", true, "[3]main.FooBar [{Baz: 1, Bur: \"a\"},{Baz: 2, Bur: \"b\"},{Baz: 3, Bur: \"c\"}]", "", "[3]main.FooBar", nil}, + {"a12", true, "[]main.FooBar len: 2, cap: 2, [{Baz: 4, Bur: \"d\"},{Baz: 5, Bur: \"e\"}]", "", "struct []main.FooBar", nil}, + {"a13", true, "[]*main.FooBar len: 3, cap: 3, [*{Baz: 6, Bur: \"f\"},*{Baz: 7, Bur: \"g\"},*{Baz: 8, Bur: \"h\"}]", "", "struct []*main.FooBar", nil}, + {"a2", true, "6", "", "int", nil}, + {"a3", true, "7.23", "", "float64", nil}, + {"a4", true, "[2]int [1,2]", "", "[2]int", nil}, + {"a5", true, "[]int len: 5, cap: 5, [1,2,3,4,5]", "", "struct []int", nil}, + {"a6", true, "main.FooBar {Baz: 8, Bur: \"word\"}", "", "main.FooBar", nil}, + {"a7", true, "*main.FooBar {Baz: 5, Bur: \"strum\"}", "", "*main.FooBar", nil}, + {"a8", true, "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil}, + {"a9", true, "*main.FooBar nil", "", "*main.FooBar", nil}, + {"b1", true, "true", "", "bool", nil}, + {"b2", true, "false", "", "bool", nil}, + {"ba", true, "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "", "struct []int", nil}, + {"c128", true, "(2 + 3i)", "", "complex128", nil}, + {"c64", true, "(1 + 2i)", "", "complex64", nil}, + {"f", true, "main.barfoo", "", "func()", nil}, + {"f32", true, "1.2", "", "float32", nil}, + {"i32", true, "[2]int32 [1,2]", "", "[2]int32", nil}, + {"i8", true, "1", "", "int8", nil}, + {"ms", true, "main.Nest {Level: 0, Nest: *main.Nest {Level: 1, Nest: *(*main.Nest)…", "", "main.Nest", nil}, + {"neg", true, "-1", "", "int", nil}, + {"u16", true, "65535", "", "uint16", nil}, + {"u32", true, "4294967295", "", "uint32", nil}, + {"u64", true, "18446744073709551615", "", "uint64", nil}, + {"u8", true, "255", "", "uint8", nil}, + {"up", true, "5", "", "uintptr", nil}}}, {(*proc.EvalScope).FunctionArguments, []varTest{ - {"bar", "main.FooBar {Baz: 10, Bur: \"lorem\"}", "", "main.FooBar", nil}, - {"baz", "\"bazburzum\"", "", "struct string", nil}}}, + {"bar", true, "main.FooBar {Baz: 10, Bur: \"lorem\"}", "", "main.FooBar", nil}, + {"baz", true, "\"bazburzum\"", "", "struct string", nil}}}, } withTestProcess("testvariables", t, func(p *proc.Process, fixture protest.Fixture) { @@ -301,12 +303,13 @@ func TestLocalVariables(t *testing.T) { func TestEmbeddedStruct(t *testing.T) { withTestProcess("testvariables4", t, func(p *proc.Process, fixture protest.Fixture) { testcases := []varTest{ - {"b.val", "-314", "", "int", nil}, - {"b.A.val", "-314", "", "int", nil}, - {"b.a.val", "42", "", "int", nil}, - {"b.ptr.val", "1337", "", "int", nil}, - {"b.C.s", "\"hello\"", "", "struct string", nil}, - {"b.s", "\"hello\"", "", "struct string", nil}, + {"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\"", "", "struct string", nil}, + {"b.s", true, "\"hello\"", "", "struct 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}, } assertNoError(p.Continue(), t, "Continue()") @@ -344,3 +347,170 @@ func TestComplexSetting(t *testing.T) { h("complex128(1.2, 3.4)", "(1.2 + 3.4i)") }) } + +func TestEvalExpression(t *testing.T) { + testcases := []varTest{ + // slice/array/string subscript + {"s1[0]", false, "\"one\"", "", "struct string", nil}, + {"s1[1]", false, "\"two\"", "", "struct string", nil}, + {"s1[2]", false, "\"three\"", "", "struct string", nil}, + {"s1[3]", false, "\"four\"", "", "struct string", nil}, + {"s1[4]", false, "\"five\"", "", "struct string", nil}, + {"s1[5]", false, "", "", "struct string", fmt.Errorf("index out of bounds")}, + {"a1[0]", false, "\"one\"", "", "struct string", nil}, + {"a1[1]", false, "\"two\"", "", "struct string", nil}, + {"a1[2]", false, "\"three\"", "", "struct string", nil}, + {"a1[3]", false, "\"four\"", "", "struct string", nil}, + {"a1[4]", false, "\"five\"", "", "struct string", nil}, + {"a1[5]", false, "", "", "struct 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[11]", false, "", "", "byte", fmt.Errorf("index out of bounds")}, + + // slice/array/string reslicing + {"a1[2:4]", false, "[]struct string len: 2, cap: 2, [\"three\",\"four\"]", "", "struct []struct string", nil}, + {"s1[2:4]", false, "[]string len: 2, cap: 2, [\"three\",\"four\"]", "", "struct []string", nil}, + {"str1[2:4]", false, "\"23\"", "", "struct string", nil}, + {"str1[0:11]", false, "\"01234567890\"", "", "struct string", nil}, + {"str1[:3]", false, "\"012\"", "", "struct string", nil}, + {"str1[3:]", false, "\"34567890\"", "", "struct string", nil}, + {"str1[0:12]", false, "", "", "struct string", fmt.Errorf("index out of bounds")}, + {"str1[5:3]", false, "", "", "struct string", fmt.Errorf("index out of bounds")}, + + // pointers + {"*p2", false, "5", "", "int", nil}, + {"p2", true, "*5", "", "*int", nil}, + {"p3", true, "*int nil", "", "*int", nil}, + {"*p3", false, "", "", "int", fmt.Errorf("nil pointer dereference")}, + + // 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}, + + // 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}, + + // 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}, + + // unary operators + {"-i2", false, "-2", "", "int", nil}, + {"+i2", false, "2", "", "int", nil}, + {"^i2", false, "-3", "", "int", nil}, + + // comparison operators + {"i2 == i3", false, "false", "", "", nil}, + {"i2 == 2", false, "true", "", "", nil}, + {"i2 == 2.0", 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}, + + // nil + {"nil", false, "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, []", "", "struct []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}, + {"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}, + + // errors + {"&3", false, "", "", "", fmt.Errorf("can not take address of \"3\"")}, + {"*3", false, "", "", "", fmt.Errorf("expression \"3\" can not be dereferenced")}, + {"&(i2 + i3)", false, "", "", "", fmt.Errorf("can not take address of \"(i2 + i3)\"")}, + {"i2 + p1", false, "", "", "", fmt.Errorf("mismatched types \"int\" and \"*int\"")}, + {"i2 + f1", false, "", "", "", fmt.Errorf("mismatched types \"int\" and \"float64\"")}, + {"i2 << f1", false, "", "", "", fmt.Errorf("shift count type float64, must be unsigned integer")}, + {"i2 << -1", false, "", "", "", fmt.Errorf("shift count type int, must be unsigned integer")}, + {"i2 << i3", false, "", "", "int", fmt.Errorf("shift count type int, must be unsigned integer")}, + {"*(i2 + i3)", false, "", "", "", fmt.Errorf("expression \"(i2 + i3)\" (int) can not be dereferenced")}, + {"i2.member", false, "", "", "", fmt.Errorf("i2 (type int) is not a struct")}, + {"fmt.Println(\"hello\")", false, "", "", "", fmt.Errorf("no type entry found")}, + } + + withTestProcess("testvariables3", 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) + if tc.err == nil { + assertNoError(err, t, fmt.Sprintf("EvalExpression(%s) returned an error", tc.name)) + assertVariable(t, variable, tc) + } else { + if err == nil { + t.Fatalf("Expected error %s, got non (%s)", tc.err.Error(), tc.name) + } + if tc.err.Error() != err.Error() { + t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error()) + } + } + } + }) +} + +func TestEvalAddrAndCast(t *testing.T) { + withTestProcess("testvariables3", t, func(p *proc.Process, fixture protest.Fixture) { + assertNoError(p.Continue(), t, "Continue() returned an error") + c1addr, err := evalVariable(p, "&c1") + assertNoError(err, t, "EvalExpression(&c1)") + c1addrstr := api.ConvertVar(c1addr).SinglelineString() + t.Logf("&c1 → %s", c1addrstr) + if !strings.HasPrefix(c1addrstr, "(*main.cstruct)(0x") { + t.Fatalf("Invalid value of EvalExpression(&c1) \"%s\"", c1addrstr) + } + + aaddr, err := evalVariable(p, "&(c1.pb.a)") + assertNoError(err, t, "EvalExpression(&(c1.pb.a))") + aaddrstr := api.ConvertVar(aaddr).SinglelineString() + t.Logf("&(c1.pb.a) → %s", aaddrstr) + if !strings.HasPrefix(aaddrstr, "(*main.astruct)(0x") { + t.Fatalf("invalid value of EvalExpression(&(c1.pb.a)) \"%s\"", aaddrstr) + } + + a, err := evalVariable(p, "*"+aaddrstr) + 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}) + }) +}