x/debug: and support for index and slice expressions.
Slicing an array may yield a type which is not in the DWARF information
of the debugged program, so we add a type sliceOf to handle this.
Change-Id: I79200b036cf183bee7e24cabc5b145e1db522256
Reviewed-on: https://go-review.googlesource.com/16039
Reviewed-by: Dave Day <djd@golang.org>
diff --git a/ogle/demo/ogler/ogler_test.go b/ogle/demo/ogler/ogler_test.go
index ba2530d..522fab0 100644
--- a/ogle/demo/ogler/ogler_test.go
+++ b/ogle/demo/ogler/ogler_test.go
@@ -187,6 +187,41 @@
`&local_int16`: program.Pointer{42, 42},
`*&local_int16`: int16(-32321),
`*&*&*&*&local_int16`: int16(-32321),
+ `local_array[2]`: int8(3),
+ `local_slice[1]`: uint8(108),
+ `local_slice_2[1]`: int8(121),
+ `&local_array[1]`: program.Pointer{42, 42},
+ `&local_slice[1]`: program.Pointer{42, 42},
+ `local_map[-21]`: float32(3.54321),
+ `local_map[+21]`: float32(0),
+ `local_map_3[1024]`: int8(1),
+ `local_map_3[512]`: int8(-1),
+ `local_map_empty[21]`: float32(0),
+ `local_map_nil[32]`: float32(0),
+ `local_string[2]`: uint8('m'),
+ `"hello"[2]`: uint8('l'),
+ `local_array[1:3][1]`: int8(3),
+ `local_array[0:4][2:3][0]`: int8(3),
+ `local_array[:]`: program.Slice{program.Array{42, 42, 5, 8}, 5},
+ `local_array[:2]`: program.Slice{program.Array{42, 42, 2, 8}, 5},
+ `local_array[2:]`: program.Slice{program.Array{42, 42, 3, 8}, 3},
+ `local_array[1:3]`: program.Slice{program.Array{42, 42, 2, 8}, 4},
+ `local_array[:3:4]`: program.Slice{program.Array{42, 42, 3, 8}, 4},
+ `local_array[1:3:4]`: program.Slice{program.Array{42, 42, 2, 8}, 3},
+ `local_array[1:][1:][1:]`: program.Slice{program.Array{42, 42, 2, 8}, 2},
+ `(&local_array)[:]`: program.Slice{program.Array{42, 42, 5, 8}, 5},
+ `(&local_array)[:2]`: program.Slice{program.Array{42, 42, 2, 8}, 5},
+ `(&local_array)[2:]`: program.Slice{program.Array{42, 42, 3, 8}, 3},
+ `(&local_array)[1:3]`: program.Slice{program.Array{42, 42, 2, 8}, 4},
+ `(&local_array)[:3:4]`: program.Slice{program.Array{42, 42, 3, 8}, 4},
+ `(&local_array)[1:3:4]`: program.Slice{program.Array{42, 42, 2, 8}, 3},
+ `local_slice[1:5][0:3][1]`: uint8('i'),
+ `local_slice[:]`: program.Slice{program.Array{42, 42, 5, 8}, 5},
+ `local_slice[:2]`: program.Slice{program.Array{42, 42, 2, 8}, 5},
+ `local_slice[2:]`: program.Slice{program.Array{42, 42, 3, 8}, 3},
+ `local_slice[1:3]`: program.Slice{program.Array{42, 42, 2, 8}, 4},
+ `local_slice[:3:4]`: program.Slice{program.Array{42, 42, 3, 8}, 4},
+ `local_slice[1:3:4]`: program.Slice{program.Array{42, 42, 2, 8}, 3},
`5 + false`: nil,
``: nil,
`x + ""`: nil,
@@ -198,6 +233,14 @@
`x % 0`: nil,
`0 % 0`: nil,
`'a' % ('a'-'a')`: nil,
+ `local_array[-2] + 1`: nil,
+ `local_array[22] + 1`: nil,
+ `local_slice[-2] + 1`: nil,
+ `local_slice[22] + 1`: nil,
+ `local_string[-2]`: nil,
+ `local_string[22]`: nil,
+ `"hello"[-2]`: nil,
+ `"hello"[22]`: nil,
}
func isHex(r uint8) bool {
diff --git a/ogle/program/server/eval.go b/ogle/program/server/eval.go
index 2199d1a..475331a 100644
--- a/ogle/program/server/eval.go
+++ b/ogle/program/server/eval.go
@@ -14,6 +14,7 @@
import (
"errors"
+ "fmt"
"go/ast"
"go/parser"
"go/token"
@@ -28,10 +29,11 @@
var (
// Some big.Ints to use in overflow checks.
- bigIntMaxInt32 = new(big.Int).SetInt64(math.MaxInt32)
- bigIntMinInt32 = new(big.Int).SetInt64(math.MinInt32)
- bigIntMaxInt64 = new(big.Int).SetInt64(math.MaxInt64)
- bigIntMinInt64 = new(big.Int).SetInt64(math.MinInt64)
+ bigIntMaxInt32 = big.NewInt(math.MaxInt32)
+ bigIntMinInt32 = big.NewInt(math.MinInt32)
+ bigIntMaxInt64 = big.NewInt(math.MaxInt64)
+ bigIntMinInt64 = big.NewInt(math.MinInt64)
+ bigIntMaxUint64 = new(big.Int).SetUint64(math.MaxUint64)
)
// result stores an intermediate value produced during evaluation of an expression.
@@ -95,6 +97,11 @@
a uint64
}
+// A sliceOf is a slice created by slicing an array.
+// Unlike program.Slice, the DWARF type stored alongside a value of this type is
+// the type of the slice's elements, not the type of the slice.
+type sliceOf program.Slice
+
// evalExpression evaluates a Go expression.
// If the program counter and stack pointer are nonzero, they are used to determine
// what local variables are available and where in memory they are.
@@ -144,6 +151,8 @@
return program.String{Length: uint64(len(v)), String: string(v)}, nil
case pointerToValue:
return program.Pointer{TypeID: uint64(val.d.Common().Offset), Address: v.a}, nil
+ case sliceOf:
+ return program.Slice(v), nil
case nil, addressableValue:
// This case should not be reachable.
return nil, errors.New("unknown error")
@@ -276,7 +285,7 @@
if pt, ok := t.(*dwarf.PtrType); ok {
return e.resultFrom(v.Address, pt.Type, getAddress)
} else {
- e.err("invalid pointer type")
+ return e.err("invalid DWARF type for pointer")
}
case pointerToValue:
return e.resultFrom(v.a, x.d, getAddress)
@@ -285,6 +294,272 @@
}
return e.err("invalid indirect")
+ case *ast.IndexExpr:
+ x, index := e.evalNode(n.X, false), e.evalNode(n.Index, false)
+ if x.v == nil || index.v == nil {
+ return result{}
+ }
+ // The expression is x[index]
+ if m, ok := x.v.(program.Map); ok {
+ if getAddress {
+ return e.err("can't take address of map value")
+ }
+ mt, ok := followTypedefs(x.d).(*dwarf.MapType)
+ if !ok {
+ return e.err("invalid DWARF type for map")
+ }
+ var (
+ found bool // true if the key was found
+ value result // the map value for the key
+ abort bool // true if an error occurred while searching
+ // fn is a function that checks if one (key, value) pair corresponds
+ // to the index in the expression.
+ fn = func(keyAddr, valAddr uint64, keyType, valType dwarf.Type) bool {
+ key := e.resultFrom(keyAddr, keyType, false)
+ if key.v == nil {
+ abort = true
+ return false // stop searching map
+ }
+ equal, ok := e.evalBinaryOp(token.EQL, index, key).v.(bool)
+ if !ok {
+ abort = true
+ return false // stop searching map
+ }
+ if equal {
+ found = true
+ value = e.resultFrom(valAddr, valType, false)
+ return false // stop searching map
+ }
+ return true // continue searching map
+ }
+ )
+ if err := e.server.peekMapValues(mt, m.Address, fn); err != nil {
+ return e.err(err.Error())
+ }
+ if abort {
+ // Some operation on individual map keys failed.
+ return result{}
+ }
+ if found {
+ return value
+ }
+ // The key wasn't in the map; return the zero value.
+ return e.zero(mt.ElemType)
+ }
+
+ // The index should be a non-negative integer for the remaining cases.
+ u, err := uint64FromResult(index)
+ if err != nil {
+ return e.err("invalid index: " + err.Error())
+ }
+ switch v := x.v.(type) {
+ case program.Array:
+ if u >= v.Length {
+ return e.err("array index out of bounds")
+ }
+ elemType, err := e.server.dwarfData.Type(dwarf.Offset(v.ElementTypeID))
+ if err != nil {
+ return e.err(err.Error())
+ }
+ return e.resultFrom(v.Element(u).Address, elemType, getAddress)
+ case program.Slice:
+ if u >= v.Length {
+ return e.err("slice index out of bounds")
+ }
+ elemType, err := e.server.dwarfData.Type(dwarf.Offset(v.ElementTypeID))
+ if err != nil {
+ return e.err(err.Error())
+ }
+ return e.resultFrom(v.Element(u).Address, elemType, getAddress)
+ case sliceOf:
+ if u >= v.Length {
+ return e.err("slice index out of bounds")
+ }
+ return e.resultFrom(v.Element(u).Address, x.d, getAddress)
+ case program.String:
+ if getAddress {
+ return e.err("can't take address of string element")
+ }
+ if u >= v.Length {
+ return e.err("string index out of bounds")
+ }
+ if u >= uint64(len(v.String)) {
+ return e.err("string element unavailable")
+ }
+ return e.uint8Result(v.String[u])
+ case untString:
+ if getAddress {
+ return e.err("can't take address of string element")
+ }
+ if u >= uint64(len(v)) {
+ return e.err("string index out of bounds")
+ }
+ return e.uint8Result(v[u])
+ }
+ return e.err("invalid index expression")
+
+ case *ast.SliceExpr:
+ if n.Slice3 && n.High == nil {
+ return e.err("middle index required in full slice")
+ }
+ if n.Slice3 && n.Max == nil {
+ return e.err("final index required in full slice")
+ }
+ var (
+ low, high, max uint64
+ err error
+ )
+ if n.Low != nil {
+ low, err = uint64FromResult(e.evalNode(n.Low, false))
+ if err != nil {
+ return e.err("invalid slice lower bound: " + err.Error())
+ }
+ }
+ if n.High != nil {
+ high, err = uint64FromResult(e.evalNode(n.High, false))
+ if err != nil {
+ return e.err("invalid slice upper bound: " + err.Error())
+ }
+ }
+ if n.Max != nil {
+ max, err = uint64FromResult(e.evalNode(n.Max, false))
+ if err != nil {
+ return e.err("invalid slice capacity: " + err.Error())
+ }
+ }
+ x := e.evalNode(n.X, false)
+ switch v := x.v.(type) {
+ case program.Array, program.Pointer, pointerToValue:
+ // This case handles the slicing of arrays and pointers to arrays.
+ var arr program.Array
+ switch v := x.v.(type) {
+ case program.Array:
+ arr = v
+ case program.Pointer:
+ pt, ok := followTypedefs(x.d).(*dwarf.PtrType)
+ if !ok {
+ return e.err("invalid DWARF type for pointer")
+ }
+ a := e.resultFrom(v.Address, pt.Type, false)
+ arr, ok = a.v.(program.Array)
+ if !ok {
+ // v is a pointer to something other than an array.
+ return e.err("cannot slice pointer")
+ }
+ case pointerToValue:
+ a := e.resultFrom(v.a, x.d, false)
+ var ok bool
+ arr, ok = a.v.(program.Array)
+ if !ok {
+ // v is a pointer to something other than an array.
+ return e.err("cannot slice pointer")
+ }
+ }
+ elemType, err := e.server.dwarfData.Type(dwarf.Offset(arr.ElementTypeID))
+ if err != nil {
+ return e.err(err.Error())
+ }
+ if n.High == nil {
+ high = arr.Length
+ } else if high > arr.Length {
+ return e.err("slice upper bound is too large")
+ }
+ if n.Max == nil {
+ max = arr.Length
+ } else if max > arr.Length {
+ return e.err("slice capacity is too large")
+ }
+ if low > high || high > max {
+ return e.err("invalid slice index")
+ }
+ return result{
+ d: elemType,
+ v: sliceOf{
+ Array: program.Array{
+ ElementTypeID: arr.ElementTypeID,
+ Address: arr.Element(low).Address,
+ Length: high - low,
+ StrideBits: uint64(elemType.Common().ByteSize) * 8,
+ },
+ Capacity: max - low,
+ },
+ }
+ case program.Slice:
+ if n.High == nil {
+ high = v.Length
+ } else if high > v.Capacity {
+ return e.err("slice upper bound is too large")
+ }
+ if n.Max == nil {
+ max = v.Capacity
+ } else if max > v.Capacity {
+ return e.err("slice capacity is too large")
+ }
+ if low > high || high > max {
+ return e.err("invalid slice index")
+ }
+ v.Address += low * (v.StrideBits / 8)
+ v.Length = high - low
+ v.Capacity = max - low
+ return result{x.d, v}
+ case sliceOf:
+ if n.High == nil {
+ high = v.Length
+ } else if high > v.Capacity {
+ return e.err("slice upper bound is too large")
+ }
+ if n.Max == nil {
+ max = v.Capacity
+ } else if max > v.Capacity {
+ return e.err("slice capacity is too large")
+ }
+ if low > high || high > max {
+ return e.err("invalid slice index")
+ }
+ v.Address += low * (v.StrideBits / 8)
+ v.Length = high - low
+ v.Capacity = max - low
+ return result{x.d, v}
+ case program.String:
+ if n.Max != nil {
+ return e.err("full slice of string")
+ }
+ if n.High == nil {
+ high = v.Length
+ }
+ if low > high || high > v.Length {
+ return e.err("invalid slice index")
+ }
+ v.Length = high - low
+ if low > uint64(len(v.String)) {
+ // v.String was truncated before the point where this slice starts.
+ v.String = ""
+ } else {
+ if high > uint64(len(v.String)) {
+ // v.String was truncated before the point where this slice ends.
+ high = uint64(len(v.String))
+ }
+ v.String = v.String[low:high]
+ }
+ return result{x.d, v}
+ case untString:
+ if n.Max != nil {
+ return e.err("full slice of string")
+ }
+ if n.High == nil {
+ high = uint64(len(v))
+ }
+ if low > high {
+ return e.err("invalid slice expression")
+ }
+ if high > uint64(len(v)) {
+ return e.err("slice upper bound is too large")
+ }
+ return e.stringResult(string(v[low:high]))
+ default:
+ return e.err("invalid slice expression")
+ }
+
case *ast.UnaryExpr:
if n.Op == token.AND {
x := e.evalNode(n.X, true)
@@ -1333,6 +1608,15 @@
return result{t, uint8(v)}
}
+// stringResult constructs a result for a string value.
+func (e *evaluator) stringResult(s string) result {
+ t, ok := e.getBaseType("string")
+ if !ok {
+ e.err("couldn't construct string")
+ }
+ return result{t, program.String{Length: uint64(len(s)), String: s}}
+}
+
// getBaseType returns the *dwarf.Type with a given name.
// TODO: cache this.
func (e *evaluator) getBaseType(name string) (dwarf.Type, bool) {
@@ -1365,6 +1649,85 @@
return result{t, v}
}
+// zero returns the zero value of type t.
+// TODO: implement for array and struct.
+func (e *evaluator) zero(t dwarf.Type) result {
+ var v interface{}
+ switch typ := followTypedefs(t).(type) {
+ case *dwarf.CharType, *dwarf.IntType, *dwarf.EnumType:
+ switch typ.Common().ByteSize {
+ case 1:
+ v = int8(0)
+ case 2:
+ v = int16(0)
+ case 4:
+ v = int32(0)
+ case 8:
+ v = int64(0)
+ default:
+ return e.err("invalid integer size " + fmt.Sprint(typ.Common().ByteSize))
+ }
+ case *dwarf.UcharType, *dwarf.UintType:
+ switch typ.Common().ByteSize {
+ case 1:
+ v = uint8(0)
+ case 2:
+ v = uint16(0)
+ case 4:
+ v = uint32(0)
+ case 8:
+ v = uint64(0)
+ default:
+ return e.err("invalid unsigned integer size " + fmt.Sprint(typ.Common().ByteSize))
+ }
+ case *dwarf.FloatType:
+ switch typ.Common().ByteSize {
+ case 4:
+ v = float32(0)
+ case 8:
+ v = float64(0)
+ default:
+ return e.err("invalid float size " + fmt.Sprint(typ.Common().ByteSize))
+ }
+ case *dwarf.ComplexType:
+ switch typ.Common().ByteSize {
+ case 8:
+ v = complex64(0)
+ case 16:
+ v = complex128(0)
+ default:
+ return e.err("invalid complex size " + fmt.Sprint(typ.Common().ByteSize))
+ }
+ case *dwarf.BoolType:
+ v = false
+ case *dwarf.PtrType:
+ v = program.Pointer{TypeID: uint64(t.Common().Offset)}
+ case *dwarf.SliceType:
+ v = program.Slice{
+ Array: program.Array{
+ ElementTypeID: uint64(typ.ElemType.Common().Offset),
+ StrideBits: uint64(typ.ElemType.Common().ByteSize) * 8,
+ },
+ }
+ case *dwarf.StringType:
+ v = program.String{}
+ case *dwarf.InterfaceType:
+ v = program.Interface{}
+ case *dwarf.FuncType:
+ v = program.Func{}
+ case *dwarf.MapType:
+ v = program.Map{TypeID: uint64(t.Common().Offset)}
+ case *dwarf.ChanType:
+ v = program.Channel{
+ ElementTypeID: uint64(typ.ElemType.Common().Offset),
+ Stride: uint64(typ.ElemType.Common().ByteSize),
+ }
+ default:
+ return e.err("can't get zero value of this type")
+ }
+ return result{t, v}
+}
+
// convertUntyped converts x to be the same type as y, if x is untyped and the
// conversion is possible.
//
@@ -1552,6 +1915,85 @@
return x
}
+// uint64FromResult converts a result into a uint64 for slice or index expressions.
+// It returns an error if the conversion cannot be done.
+func uint64FromResult(x result) (uint64, error) {
+ switch v := x.v.(type) {
+ case int8:
+ if v < 0 {
+ return 0, errors.New("value is negative")
+ }
+ return uint64(v), nil
+ case int16:
+ if v < 0 {
+ return 0, errors.New("value is negative")
+ }
+ return uint64(v), nil
+ case int32:
+ if v < 0 {
+ return 0, errors.New("value is negative")
+ }
+ return uint64(v), nil
+ case int64:
+ if v < 0 {
+ return 0, errors.New("value is negative")
+ }
+ return uint64(v), nil
+ case uint8:
+ return uint64(v), nil
+ case uint16:
+ return uint64(v), nil
+ case uint32:
+ return uint64(v), nil
+ case uint64:
+ return v, nil
+ case untInt:
+ if v.Int.Sign() == -1 {
+ return 0, errors.New("value is negative")
+ }
+ if v.Int.Cmp(bigIntMaxUint64) == +1 {
+ return 0, errors.New("value is too large")
+ }
+ return v.Int.Uint64(), nil
+ case untRune:
+ if v.Sign() == -1 {
+ return 0, errors.New("value is negative")
+ }
+ if v.Cmp(bigIntMaxUint64) == +1 {
+ return 0, errors.New("value is too large")
+ }
+ return v.Uint64(), nil
+ case untFloat:
+ if !v.IsInt() {
+ return 0, errors.New("value is not an integer")
+ }
+ if v.Sign() == -1 {
+ return 0, errors.New("value is negative")
+ }
+ i, _ := v.Int(nil)
+ if i.Cmp(bigIntMaxUint64) == +1 {
+ return 0, errors.New("value is too large")
+ }
+ return i.Uint64(), nil
+ case untComplex:
+ if v.i.Sign() != 0 {
+ return 0, errors.New("value is complex")
+ }
+ if !v.r.IsInt() {
+ return 0, errors.New("value is not an integer")
+ }
+ if v.r.Sign() == -1 {
+ return 0, errors.New("value is negative")
+ }
+ i, _ := v.r.Int(nil)
+ if i.Cmp(bigIntMaxUint64) == +1 {
+ return 0, errors.New("value is too large")
+ }
+ return i.Uint64(), nil
+ }
+ return 0, fmt.Errorf("cannot convert to unsigned integer")
+}
+
// followTypedefs returns the underlying type of t, removing any typedefs.
// If t leads to a cycle of typedefs, followTypedefs returns nil.
func followTypedefs(t dwarf.Type) dwarf.Type {
diff --git a/ogle/program/server/eval.m4 b/ogle/program/server/eval.m4
index e671a37..694b7c1 100644
--- a/ogle/program/server/eval.m4
+++ b/ogle/program/server/eval.m4
@@ -14,6 +14,7 @@
import (
"errors"
+ "fmt"
"go/ast"
"go/parser"
"go/token"
@@ -28,10 +29,11 @@
var (
// Some big.Ints to use in overflow checks.
- bigIntMaxInt32 = new(big.Int).SetInt64(math.MaxInt32)
- bigIntMinInt32 = new(big.Int).SetInt64(math.MinInt32)
- bigIntMaxInt64 = new(big.Int).SetInt64(math.MaxInt64)
- bigIntMinInt64 = new(big.Int).SetInt64(math.MinInt64)
+ bigIntMaxInt32 = big.NewInt(math.MaxInt32)
+ bigIntMinInt32 = big.NewInt(math.MinInt32)
+ bigIntMaxInt64 = big.NewInt(math.MaxInt64)
+ bigIntMinInt64 = big.NewInt(math.MinInt64)
+ bigIntMaxUint64 = new(big.Int).SetUint64(math.MaxUint64)
)
// result stores an intermediate value produced during evaluation of an expression.
@@ -95,6 +97,11 @@
a uint64
}
+// A sliceOf is a slice created by slicing an array.
+// Unlike program.Slice, the DWARF type stored alongside a value of this type is
+// the type of the slice's elements, not the type of the slice.
+type sliceOf program.Slice
+
// evalExpression evaluates a Go expression.
// If the program counter and stack pointer are nonzero, they are used to determine
// what local variables are available and where in memory they are.
@@ -144,6 +151,8 @@
return program.String{Length: uint64(len(v)), String: string(v)}, nil
case pointerToValue:
return program.Pointer{TypeID: uint64(val.d.Common().Offset), Address: v.a}, nil
+ case sliceOf:
+ return program.Slice(v), nil
case nil, addressableValue:
// This case should not be reachable.
return nil, errors.New("unknown error")
@@ -276,7 +285,7 @@
if pt, ok := t.(*dwarf.PtrType); ok {
return e.resultFrom(v.Address, pt.Type, getAddress)
} else {
- e.err("invalid pointer type")
+ return e.err("invalid DWARF type for pointer")
}
case pointerToValue:
return e.resultFrom(v.a, x.d, getAddress)
@@ -285,6 +294,272 @@
}
return e.err("invalid indirect")
+ case *ast.IndexExpr:
+ x, index := e.evalNode(n.X, false), e.evalNode(n.Index, false)
+ if x.v == nil || index.v == nil {
+ return result{}
+ }
+ // The expression is x[index]
+ if m, ok := x.v.(program.Map); ok {
+ if getAddress {
+ return e.err("can't take address of map value")
+ }
+ mt, ok := followTypedefs(x.d).(*dwarf.MapType)
+ if !ok {
+ return e.err("invalid DWARF type for map")
+ }
+ var (
+ found bool // true if the key was found
+ value result // the map value for the key
+ abort bool // true if an error occurred while searching
+ // fn is a function that checks if one (key, value) pair corresponds
+ // to the index in the expression.
+ fn = func(keyAddr, valAddr uint64, keyType, valType dwarf.Type) bool {
+ key := e.resultFrom(keyAddr, keyType, false)
+ if key.v == nil {
+ abort = true
+ return false // stop searching map
+ }
+ equal, ok := e.evalBinaryOp(token.EQL, index, key).v.(bool)
+ if !ok {
+ abort = true
+ return false // stop searching map
+ }
+ if equal {
+ found = true
+ value = e.resultFrom(valAddr, valType, false)
+ return false // stop searching map
+ }
+ return true // continue searching map
+ }
+ )
+ if err := e.server.peekMapValues(mt, m.Address, fn); err != nil {
+ return e.err(err.Error())
+ }
+ if abort {
+ // Some operation on individual map keys failed.
+ return result{}
+ }
+ if found {
+ return value
+ }
+ // The key wasn't in the map; return the zero value.
+ return e.zero(mt.ElemType)
+ }
+
+ // The index should be a non-negative integer for the remaining cases.
+ u, err := uint64FromResult(index)
+ if err != nil {
+ return e.err("invalid index: " + err.Error())
+ }
+ switch v := x.v.(type) {
+ case program.Array:
+ if u >= v.Length {
+ return e.err("array index out of bounds")
+ }
+ elemType, err := e.server.dwarfData.Type(dwarf.Offset(v.ElementTypeID))
+ if err != nil {
+ return e.err(err.Error())
+ }
+ return e.resultFrom(v.Element(u).Address, elemType, getAddress)
+ case program.Slice:
+ if u >= v.Length {
+ return e.err("slice index out of bounds")
+ }
+ elemType, err := e.server.dwarfData.Type(dwarf.Offset(v.ElementTypeID))
+ if err != nil {
+ return e.err(err.Error())
+ }
+ return e.resultFrom(v.Element(u).Address, elemType, getAddress)
+ case sliceOf:
+ if u >= v.Length {
+ return e.err("slice index out of bounds")
+ }
+ return e.resultFrom(v.Element(u).Address, x.d, getAddress)
+ case program.String:
+ if getAddress {
+ return e.err("can't take address of string element")
+ }
+ if u >= v.Length {
+ return e.err("string index out of bounds")
+ }
+ if u >= uint64(len(v.String)) {
+ return e.err("string element unavailable")
+ }
+ return e.uint8Result(v.String[u])
+ case untString:
+ if getAddress {
+ return e.err("can't take address of string element")
+ }
+ if u >= uint64(len(v)) {
+ return e.err("string index out of bounds")
+ }
+ return e.uint8Result(v[u])
+ }
+ return e.err("invalid index expression")
+
+ case *ast.SliceExpr:
+ if n.Slice3 && n.High == nil {
+ return e.err("middle index required in full slice")
+ }
+ if n.Slice3 && n.Max == nil {
+ return e.err("final index required in full slice")
+ }
+ var (
+ low, high, max uint64
+ err error
+ )
+ if n.Low != nil {
+ low, err = uint64FromResult(e.evalNode(n.Low, false))
+ if err != nil {
+ return e.err("invalid slice lower bound: " + err.Error())
+ }
+ }
+ if n.High != nil {
+ high, err = uint64FromResult(e.evalNode(n.High, false))
+ if err != nil {
+ return e.err("invalid slice upper bound: " + err.Error())
+ }
+ }
+ if n.Max != nil {
+ max, err = uint64FromResult(e.evalNode(n.Max, false))
+ if err != nil {
+ return e.err("invalid slice capacity: " + err.Error())
+ }
+ }
+ x := e.evalNode(n.X, false)
+ switch v := x.v.(type) {
+ case program.Array, program.Pointer, pointerToValue:
+ // This case handles the slicing of arrays and pointers to arrays.
+ var arr program.Array
+ switch v := x.v.(type) {
+ case program.Array:
+ arr = v
+ case program.Pointer:
+ pt, ok := followTypedefs(x.d).(*dwarf.PtrType)
+ if !ok {
+ return e.err("invalid DWARF type for pointer")
+ }
+ a := e.resultFrom(v.Address, pt.Type, false)
+ arr, ok = a.v.(program.Array)
+ if !ok {
+ // v is a pointer to something other than an array.
+ return e.err("cannot slice pointer")
+ }
+ case pointerToValue:
+ a := e.resultFrom(v.a, x.d, false)
+ var ok bool
+ arr, ok = a.v.(program.Array)
+ if !ok {
+ // v is a pointer to something other than an array.
+ return e.err("cannot slice pointer")
+ }
+ }
+ elemType, err := e.server.dwarfData.Type(dwarf.Offset(arr.ElementTypeID))
+ if err != nil {
+ return e.err(err.Error())
+ }
+ if n.High == nil {
+ high = arr.Length
+ } else if high > arr.Length {
+ return e.err("slice upper bound is too large")
+ }
+ if n.Max == nil {
+ max = arr.Length
+ } else if max > arr.Length {
+ return e.err("slice capacity is too large")
+ }
+ if low > high || high > max {
+ return e.err("invalid slice index")
+ }
+ return result{
+ d: elemType,
+ v: sliceOf{
+ Array: program.Array{
+ ElementTypeID: arr.ElementTypeID,
+ Address: arr.Element(low).Address,
+ Length: high - low,
+ StrideBits: uint64(elemType.Common().ByteSize) * 8,
+ },
+ Capacity: max - low,
+ },
+ }
+ case program.Slice:
+ if n.High == nil {
+ high = v.Length
+ } else if high > v.Capacity {
+ return e.err("slice upper bound is too large")
+ }
+ if n.Max == nil {
+ max = v.Capacity
+ } else if max > v.Capacity {
+ return e.err("slice capacity is too large")
+ }
+ if low > high || high > max {
+ return e.err("invalid slice index")
+ }
+ v.Address += low * (v.StrideBits / 8)
+ v.Length = high - low
+ v.Capacity = max - low
+ return result{x.d, v}
+ case sliceOf:
+ if n.High == nil {
+ high = v.Length
+ } else if high > v.Capacity {
+ return e.err("slice upper bound is too large")
+ }
+ if n.Max == nil {
+ max = v.Capacity
+ } else if max > v.Capacity {
+ return e.err("slice capacity is too large")
+ }
+ if low > high || high > max {
+ return e.err("invalid slice index")
+ }
+ v.Address += low * (v.StrideBits / 8)
+ v.Length = high - low
+ v.Capacity = max - low
+ return result{x.d, v}
+ case program.String:
+ if n.Max != nil {
+ return e.err("full slice of string")
+ }
+ if n.High == nil {
+ high = v.Length
+ }
+ if low > high || high > v.Length {
+ return e.err("invalid slice index")
+ }
+ v.Length = high - low
+ if low > uint64(len(v.String)) {
+ // v.String was truncated before the point where this slice starts.
+ v.String = ""
+ } else {
+ if high > uint64(len(v.String)) {
+ // v.String was truncated before the point where this slice ends.
+ high = uint64(len(v.String))
+ }
+ v.String = v.String[low:high]
+ }
+ return result{x.d, v}
+ case untString:
+ if n.Max != nil {
+ return e.err("full slice of string")
+ }
+ if n.High == nil {
+ high = uint64(len(v))
+ }
+ if low > high {
+ return e.err("invalid slice expression")
+ }
+ if high > uint64(len(v)) {
+ return e.err("slice upper bound is too large")
+ }
+ return e.stringResult(string(v[low:high]))
+ default:
+ return e.err("invalid slice expression")
+ }
+
case *ast.UnaryExpr:
if n.Op == token.AND {
x := e.evalNode(n.X, true)
@@ -941,6 +1216,15 @@
return result{t, uint8(v)}
}
+// stringResult constructs a result for a string value.
+func (e *evaluator) stringResult(s string) result {
+ t, ok := e.getBaseType("string")
+ if !ok {
+ e.err("couldn't construct string")
+ }
+ return result{t, program.String{Length: uint64(len(s)), String: s}}
+}
+
// getBaseType returns the *dwarf.Type with a given name.
// TODO: cache this.
func (e *evaluator) getBaseType(name string) (dwarf.Type, bool) {
@@ -973,6 +1257,85 @@
return result{t, v}
}
+// zero returns the zero value of type t.
+// TODO: implement for array and struct.
+func (e *evaluator) zero(t dwarf.Type) result {
+ var v interface{}
+ switch typ := followTypedefs(t).(type) {
+ case *dwarf.CharType, *dwarf.IntType, *dwarf.EnumType:
+ switch typ.Common().ByteSize {
+ case 1:
+ v = int8(0)
+ case 2:
+ v = int16(0)
+ case 4:
+ v = int32(0)
+ case 8:
+ v = int64(0)
+ default:
+ return e.err("invalid integer size " + fmt.Sprint(typ.Common().ByteSize))
+ }
+ case *dwarf.UcharType, *dwarf.UintType:
+ switch typ.Common().ByteSize {
+ case 1:
+ v = uint8(0)
+ case 2:
+ v = uint16(0)
+ case 4:
+ v = uint32(0)
+ case 8:
+ v = uint64(0)
+ default:
+ return e.err("invalid unsigned integer size " + fmt.Sprint(typ.Common().ByteSize))
+ }
+ case *dwarf.FloatType:
+ switch typ.Common().ByteSize {
+ case 4:
+ v = float32(0)
+ case 8:
+ v = float64(0)
+ default:
+ return e.err("invalid float size " + fmt.Sprint(typ.Common().ByteSize))
+ }
+ case *dwarf.ComplexType:
+ switch typ.Common().ByteSize {
+ case 8:
+ v = complex64(0)
+ case 16:
+ v = complex128(0)
+ default:
+ return e.err("invalid complex size " + fmt.Sprint(typ.Common().ByteSize))
+ }
+ case *dwarf.BoolType:
+ v = false
+ case *dwarf.PtrType:
+ v = program.Pointer{TypeID: uint64(t.Common().Offset)}
+ case *dwarf.SliceType:
+ v = program.Slice{
+ Array: program.Array{
+ ElementTypeID: uint64(typ.ElemType.Common().Offset),
+ StrideBits: uint64(typ.ElemType.Common().ByteSize) * 8,
+ },
+ }
+ case *dwarf.StringType:
+ v = program.String{}
+ case *dwarf.InterfaceType:
+ v = program.Interface{}
+ case *dwarf.FuncType:
+ v = program.Func{}
+ case *dwarf.MapType:
+ v = program.Map{TypeID: uint64(t.Common().Offset)}
+ case *dwarf.ChanType:
+ v = program.Channel{
+ ElementTypeID: uint64(typ.ElemType.Common().Offset),
+ Stride: uint64(typ.ElemType.Common().ByteSize),
+ }
+ default:
+ return e.err("can't get zero value of this type")
+ }
+ return result{t, v}
+}
+
// convertUntyped converts x to be the same type as y, if x is untyped and the
// conversion is possible.
//
@@ -1160,6 +1523,85 @@
return x
}
+// uint64FromResult converts a result into a uint64 for slice or index expressions.
+// It returns an error if the conversion cannot be done.
+func uint64FromResult(x result) (uint64, error) {
+ switch v := x.v.(type) {
+ case int8:
+ if v < 0 {
+ return 0, errors.New("value is negative")
+ }
+ return uint64(v), nil
+ case int16:
+ if v < 0 {
+ return 0, errors.New("value is negative")
+ }
+ return uint64(v), nil
+ case int32:
+ if v < 0 {
+ return 0, errors.New("value is negative")
+ }
+ return uint64(v), nil
+ case int64:
+ if v < 0 {
+ return 0, errors.New("value is negative")
+ }
+ return uint64(v), nil
+ case uint8:
+ return uint64(v), nil
+ case uint16:
+ return uint64(v), nil
+ case uint32:
+ return uint64(v), nil
+ case uint64:
+ return v, nil
+ case untInt:
+ if v.Int.Sign() == -1 {
+ return 0, errors.New("value is negative")
+ }
+ if v.Int.Cmp(bigIntMaxUint64) == +1 {
+ return 0, errors.New("value is too large")
+ }
+ return v.Int.Uint64(), nil
+ case untRune:
+ if v.Sign() == -1 {
+ return 0, errors.New("value is negative")
+ }
+ if v.Cmp(bigIntMaxUint64) == +1 {
+ return 0, errors.New("value is too large")
+ }
+ return v.Uint64(), nil
+ case untFloat:
+ if !v.IsInt() {
+ return 0, errors.New("value is not an integer")
+ }
+ if v.Sign() == -1 {
+ return 0, errors.New("value is negative")
+ }
+ i, _ := v.Int(nil)
+ if i.Cmp(bigIntMaxUint64) == +1 {
+ return 0, errors.New("value is too large")
+ }
+ return i.Uint64(), nil
+ case untComplex:
+ if v.i.Sign() != 0 {
+ return 0, errors.New("value is complex")
+ }
+ if !v.r.IsInt() {
+ return 0, errors.New("value is not an integer")
+ }
+ if v.r.Sign() == -1 {
+ return 0, errors.New("value is negative")
+ }
+ i, _ := v.r.Int(nil)
+ if i.Cmp(bigIntMaxUint64) == +1 {
+ return 0, errors.New("value is too large")
+ }
+ return i.Uint64(), nil
+ }
+ return 0, fmt.Errorf("cannot convert to unsigned integer")
+}
+
// followTypedefs returns the underlying type of t, removing any typedefs.
// If t leads to a cycle of typedefs, followTypedefs returns nil.
func followTypedefs(t dwarf.Type) dwarf.Type {