proc/variables: bugfix: ifaces with types in user defined packages

The concrete type of an interface only contains the abbreviated
package name, we must construct a map from package names to package
paths to be able to resolve the concrete type of an interface.
This commit is contained in:
aarzilli
2015-11-26 14:10:58 +01:00
committed by Derek Parker
parent 8346a6ee08
commit 38716dcc26
6 changed files with 144 additions and 27 deletions

View File

@ -2,6 +2,7 @@ package main
import (
"fmt"
"go/constant"
"runtime"
"unsafe"
)
@ -109,15 +110,20 @@ func main() {
var err2 error = c1.pb
var errnil error = nil
var iface1 interface{} = c1.sa[0]
var iface2 interface{} = "test"
var iface3 interface{} = map[string]constant.Value{}
var iface4 interface{} = []constant.Value{constant.MakeInt64(4)}
var ifacenil interface{} = nil
arr1 := [4]int{0, 1, 2, 3}
parr := &arr1
cpx1 := complex(1, 2)
const1 := constant.MakeInt64(3)
var amb1 = 1
runtime.Breakpoint()
for amb1 := 0; amb1 < 10; amb1++ {
fmt.Println(amb1)
}
fmt.Println(i1, i2, i3, p1, amb1, s1, s3, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, up1, i4, i5, i6, err1, err2, errnil, iface1, ifacenil, arr1, parr, cpx1)
runtime.Breakpoint()
fmt.Println(i1, i2, i3, p1, amb1, s1, s3, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4)
}

View File

@ -932,15 +932,6 @@ func equalChildren(xv, yv *Variable, shortcircuit bool) (bool, error) {
return r, nil
}
func (dbp *Process) findType(name string) (dwarf.Type, error) {
reader := dbp.DwarfReader()
typentry, err := reader.SeekToTypeNamed(name)
if err != nil {
return nil, err
}
return dbp.dwarf.Type(typentry.Offset)
}
func (v *Variable) asInt() (int64, error) {
if v.DwarfType == nil {
if v.Value.Kind() != constant.Int {

View File

@ -39,6 +39,9 @@ type Process struct {
// Normally SelectedGoroutine is CurrentThread.GetG, it will not be only if SwitchGoroutine is called with a goroutine that isn't attached to a thread
SelectedGoroutine *G
// Maps package names to package paths, needed to lookup types inside DWARF info
packageMap map[string]string
allGCache []*G
dwarf *dwarf.Data
goSymTable *gosym.Table

123
proc/types.go Normal file
View File

@ -0,0 +1,123 @@
package proc
import (
"debug/dwarf"
"go/ast"
"strings"
)
// Do not call this function directly it isn't able to deal correctly with package paths
func (dbp *Process) findType(name string) (dwarf.Type, error) {
reader := dbp.DwarfReader()
typentry, err := reader.SeekToTypeNamed(name)
if err != nil {
return nil, err
}
return dbp.dwarf.Type(typentry.Offset)
}
func (dbp *Process) pointerTo(typ dwarf.Type) dwarf.Type {
return &dwarf.PtrType{dwarf.CommonType{int64(dbp.arch.PtrSize()), ""}, typ}
}
func (dbp *Process) findTypeExpr(expr ast.Expr) (dwarf.Type, error) {
dbp.loadPackageMap()
dbp.expandPackagesInType(expr)
if snode, ok := expr.(*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 := dbp.findType(exprToString(snode.X))
if err != nil {
return nil, err
}
return dbp.pointerTo(ptyp), nil
}
return dbp.findType(exprToString(expr))
}
func complexType(typename string) bool {
dot := 0
for _, ch := range typename {
switch ch {
case '*', '[', '<', '{', '(', ' ':
return true
case '.':
dot++
if dot > 1 {
return true
}
}
}
return false
}
func (dbp *Process) loadPackageMap() error {
if dbp.packageMap != nil {
return nil
}
dbp.packageMap = map[string]string{}
reader := dbp.DwarfReader()
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
if err != nil {
return err
}
if entry.Tag != dwarf.TagTypedef && entry.Tag != dwarf.TagBaseType && entry.Tag != dwarf.TagClassType && entry.Tag != dwarf.TagStructType {
continue
}
typename, ok := entry.Val(dwarf.AttrName).(string)
if !ok || complexType(typename) {
continue
}
dot := strings.Index(typename, ".")
if dot < 0 {
continue
}
path := typename[:dot]
slash := strings.LastIndex(path, "/")
if slash < 0 || slash+1 >= len(path) {
continue
}
name := path[slash+1:]
dbp.packageMap[name] = path
}
return nil
}
func (dbp *Process) expandPackagesInType(expr ast.Expr) {
switch e := expr.(type) {
case *ast.ArrayType:
dbp.expandPackagesInType(e.Elt)
case *ast.ChanType:
dbp.expandPackagesInType(e.Value)
case *ast.FuncType:
for i := range e.Params.List {
dbp.expandPackagesInType(e.Params.List[i].Type)
}
for i := range e.Results.List {
dbp.expandPackagesInType(e.Results.List[i].Type)
}
case *ast.MapType:
dbp.expandPackagesInType(e.Key)
dbp.expandPackagesInType(e.Value)
case *ast.ParenExpr:
dbp.expandPackagesInType(e.X)
case *ast.SelectorExpr:
switch x := e.X.(type) {
case *ast.Ident:
if path, ok := dbp.packageMap[x.Name]; ok {
x.Name = path
}
default:
dbp.expandPackagesInType(e.X)
}
case *ast.StarExpr:
dbp.expandPackagesInType(e.X)
default:
// nothing to do
}
}

View File

@ -5,7 +5,6 @@ import (
"debug/dwarf"
"encoding/binary"
"fmt"
"go/ast"
"go/constant"
"go/parser"
"go/token"
@ -1451,10 +1450,16 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool) {
typ, err := v.thread.dbp.findTypeExpr(t)
if err != nil {
v.Unreadable = fmt.Errorf("invalid interface type: %v", err)
v.Unreadable = fmt.Errorf("interface type \"%s\" not found for 0x%x: %v", constant.StringVal(typestring.Value), data.Addr, err)
return
}
realtyp := resolveTypedef(typ)
if _, isptr := realtyp.(*dwarf.PtrType); !isptr {
// interface to non-pointer types are pointers even if the type says otherwise
typ = v.thread.dbp.pointerTo(typ)
}
data = newVariable("data", data.Addr, typ, data.thread)
v.Children = []Variable{*data}
@ -1464,21 +1469,6 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool) {
return
}
func (dbp *Process) findTypeExpr(expr ast.Expr) (dwarf.Type, error) {
if snode, ok := expr.(*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 := dbp.findType(exprToString(snode.X))
if err != nil {
return nil, err
}
return &dwarf.PtrType{dwarf.CommonType{int64(dbp.arch.PtrSize()), exprToString(expr)}, ptyp}, nil
}
return dbp.findType(exprToString(expr))
}
// Fetches all variables of a specific type in the current function scope
func (scope *EvalScope) variablesByTag(tag dwarf.Tag) ([]*Variable, error) {
reader := scope.DwarfReader()

View File

@ -404,6 +404,9 @@ func TestEvalExpression(t *testing.T) {
{"err2", true, "error(*struct main.bstruct) *{a: main.astruct {A: 1, B: 2}}", "", "error", nil},
{"errnil", true, "error nil", "", "error", nil},
{"iface1", true, "interface {}(*struct main.astruct) *{A: 1, B: 2}", "", "interface {}", nil},
{"iface2", true, "interface {}(*struct string) *\"test\"", "", "interface {}", nil},
{"iface3", true, "interface {}(map[string]go/constant.Value) []", "", "interface {}", nil},
{"iface4", true, "interface {}(*struct []go/constant.Value) *[*4]", "", "interface {}", nil},
{"ifacenil", true, "interface {} nil", "", "interface {}", nil},
{"err1 == err2", false, "false", "", "", nil},
{"err1 == iface1", false, "", "", "", fmt.Errorf("mismatched types \"error\" and \"interface {}\"")},
@ -412,6 +415,7 @@ func TestEvalExpression(t *testing.T) {
{"err1.(*main.astruct)", false, "*struct main.astruct {A: 1, B: 2}", "", "*struct main.astruct", nil},
{"err1.(*main.bstruct)", false, "", "", "", fmt.Errorf("interface conversion: error is *struct main.astruct, not *struct main.bstruct")},
{"errnil.(*main.astruct)", false, "", "", "", fmt.Errorf("interface conversion: error is nil, not *main.astruct")},
{"const1", true, "go/constant.Value(*go/constant.int64Val) *3", "", "go/constant.Value", nil},
// combined expressions
{"c1.pb.a.A", true, "1", "", "int", nil},