mirror of
https://github.com/go-delve/delve.git
synced 2025-10-28 04:35:19 +08:00
pkg/proc: hierarchical search structMember or method (#4118)
Add findStructMemberOrMethod method to replace findMethod method, findStructMemberOrMethod method is to search for methods or struct member according to hierarchy.
This commit is contained in:
219
_fixtures/issue4116.go
Normal file
219
_fixtures/issue4116.go
Normal file
@ -0,0 +1,219 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type A struct {
|
||||
}
|
||||
|
||||
func (a *A) Model() string {
|
||||
return "A"
|
||||
}
|
||||
|
||||
type B struct {
|
||||
*A
|
||||
Model string
|
||||
}
|
||||
|
||||
type A1 struct {
|
||||
X int
|
||||
}
|
||||
|
||||
type B1 struct {
|
||||
*A1
|
||||
}
|
||||
|
||||
func (*B1) X() {}
|
||||
|
||||
type A2 struct {
|
||||
X int
|
||||
}
|
||||
|
||||
type B2 struct {
|
||||
*A2
|
||||
}
|
||||
|
||||
func (*B2) X() {}
|
||||
|
||||
type C2 struct {
|
||||
*B2
|
||||
}
|
||||
|
||||
type A3 struct {
|
||||
X int
|
||||
}
|
||||
|
||||
type B3 struct {
|
||||
*A3
|
||||
}
|
||||
|
||||
func (b *B3) X() {}
|
||||
|
||||
type C3 struct {
|
||||
*B3
|
||||
}
|
||||
|
||||
type TestX interface {
|
||||
X()
|
||||
}
|
||||
|
||||
type A4 struct {
|
||||
}
|
||||
|
||||
func (a *A4) X() {}
|
||||
|
||||
type B4 struct {
|
||||
TestX
|
||||
}
|
||||
|
||||
type XFunc = func() string
|
||||
|
||||
type A5 struct {
|
||||
X XFunc
|
||||
}
|
||||
|
||||
type B5 struct {
|
||||
A5
|
||||
}
|
||||
|
||||
type C5 struct {
|
||||
*B5
|
||||
X int
|
||||
}
|
||||
|
||||
type Chan = chan struct{}
|
||||
|
||||
type A6 struct {
|
||||
Chan
|
||||
}
|
||||
|
||||
type B7 struct {
|
||||
}
|
||||
|
||||
func (b *B7) X() {}
|
||||
|
||||
type A7 struct {
|
||||
*B7
|
||||
}
|
||||
|
||||
type TestX1 interface {
|
||||
X()
|
||||
}
|
||||
|
||||
type B8 struct {
|
||||
}
|
||||
|
||||
func (*B8) X() {}
|
||||
|
||||
type A8 struct {
|
||||
TestX
|
||||
}
|
||||
|
||||
type B9 struct {
|
||||
}
|
||||
|
||||
func (*B9) X() {}
|
||||
|
||||
type A9 struct {
|
||||
V TestX
|
||||
}
|
||||
|
||||
type B10 struct {
|
||||
X int
|
||||
}
|
||||
|
||||
type A10 struct {
|
||||
B10
|
||||
}
|
||||
|
||||
func main() {
|
||||
v := &B{Model: "B", A: &A{}}
|
||||
fmt.Println(v.Model, v.A.Model)
|
||||
|
||||
v1 := &B1{A1: &A1{}}
|
||||
fmt.Println(v1.X, v1.A1.X)
|
||||
|
||||
v2 := &C2{B2: &B2{A2: &A2{}}}
|
||||
fmt.Println(v2.X, v2.B2.X, v2.B2.A2.X, v2.A2.X)
|
||||
|
||||
v3 := &C3{B3: &B3{A3: &A3{}}}
|
||||
fmt.Println(v3.X, v3.A3.X, v3.B3.X, v3.B3.A3.X)
|
||||
|
||||
v4 := &B4{TestX: &A4{}}
|
||||
fmt.Println(v4.X, v4.TestX.X)
|
||||
|
||||
v5 := C5{B5: &B5{A5: A5{X: func() string { return "anonymous func" }}}}
|
||||
fmt.Println(v5.X, v5.B5.X, v5.B5.A5.X, v5.A5.X)
|
||||
|
||||
ch := make(chan int)
|
||||
fmt.Println(ch)
|
||||
|
||||
v6 := A6{Chan: make(chan struct{})}
|
||||
fmt.Println(v6.Chan)
|
||||
|
||||
v7 := A7{}
|
||||
fmt.Print(v7.X, v7.B7.X)
|
||||
|
||||
var x TestX
|
||||
x = &A7{}
|
||||
fmt.Println(x.X, x.(*A7).X, x.(*A7).B7.X)
|
||||
|
||||
var x1 TestX
|
||||
|
||||
fmt.Println(x1)
|
||||
|
||||
var (
|
||||
x2 TestX
|
||||
x3 TestX1
|
||||
)
|
||||
|
||||
x3 = &A7{}
|
||||
x2 = x3
|
||||
fmt.Println(x2.X, x3.X)
|
||||
|
||||
v8 := A8{TestX: &B8{}}
|
||||
fmt.Println(v8)
|
||||
|
||||
v9 := A9{V: &B9{}}
|
||||
fmt.Println(v9.V.X)
|
||||
|
||||
a := struct {
|
||||
TestX
|
||||
}{
|
||||
TestX: TestX(v8),
|
||||
}
|
||||
|
||||
fmt.Println(a.X)
|
||||
|
||||
b := struct {
|
||||
TestX
|
||||
}{
|
||||
TestX: TestX(nil),
|
||||
}
|
||||
|
||||
fmt.Println(b)
|
||||
|
||||
c := struct {
|
||||
A10
|
||||
TestX
|
||||
}{
|
||||
A10: A10{B10{X: 1}},
|
||||
TestX: TestX(&v8),
|
||||
}
|
||||
|
||||
fmt.Println(c.X)
|
||||
|
||||
d := struct {
|
||||
A10
|
||||
TestX
|
||||
}{
|
||||
A10: A10{B10{X: 1}},
|
||||
TestX: TestX(nil),
|
||||
}
|
||||
|
||||
fmt.Println(d)
|
||||
|
||||
runtime.Breakpoint() // breakpoint here
|
||||
}
|
||||
175
pkg/proc/eval.go
175
pkg/proc/eval.go
@ -2112,16 +2112,7 @@ func (scope *EvalScope) evalStructSelector(op *evalop.Select, stack *evalStack)
|
||||
return
|
||||
}
|
||||
|
||||
rv, err := xv.findMethod(op.Name)
|
||||
if err != nil {
|
||||
stack.err = err
|
||||
return
|
||||
}
|
||||
if rv != nil {
|
||||
stack.push(rv)
|
||||
return
|
||||
}
|
||||
stack.pushErr(xv.structMember(op.Name))
|
||||
stack.pushErr(xv.findStructMemberOrMethod(op.Name, true))
|
||||
}
|
||||
|
||||
// Evaluates expressions <subexpr>.(<type>)
|
||||
@ -3005,18 +2996,60 @@ func (v *Variable) reslice(low int64, high int64, trustLen bool) (*Variable, err
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// findMethod finds method mname in the type of variable v
|
||||
func (v *Variable) findMethod(mname string) (*Variable, error) {
|
||||
if _, isiface := v.RealType.(*godwarf.InterfaceType); isiface {
|
||||
v.loadInterface(0, false, loadFullValue)
|
||||
// findStructMemberOrMethod finds struct member or method name in the type of variable v
|
||||
func (v *Variable) findStructMemberOrMethod(name string, includeStructMember bool) (*Variable, error) {
|
||||
if v.Unreadable != nil {
|
||||
return v.clone(), nil
|
||||
}
|
||||
vname := v.Name
|
||||
if v.Name == "" {
|
||||
vname = v.DwarfType.String()
|
||||
}
|
||||
if v.loaded && (v.Flags&VariableFakeAddress) != 0 {
|
||||
for i := range v.Children {
|
||||
if v.Children[i].Name == name {
|
||||
return &v.Children[i], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("%s has no member %s", vname, name)
|
||||
}
|
||||
closure := false
|
||||
switch v.Kind {
|
||||
case reflect.Chan:
|
||||
v = v.clone()
|
||||
v.RealType = godwarf.ResolveTypedef(&(v.RealType.(*godwarf.ChanType).TypedefType))
|
||||
case reflect.Interface:
|
||||
v.loadInterface(0, false, LoadConfig{})
|
||||
if len(v.Children) > 0 {
|
||||
v = &v.Children[0]
|
||||
}
|
||||
case reflect.Func:
|
||||
fn := v.bi.PCToFunc(v.Base)
|
||||
v.loadFunctionPtr(0, LoadConfig{MaxVariableRecurse: -1})
|
||||
if v.Unreadable != nil {
|
||||
cst := fn.extra(v.bi).closureStructType
|
||||
if cst == nil || cst.ByteSize == 0 {
|
||||
// Not a closure, normal function
|
||||
if _, ok := v.bi.PackageMap[vname]; ok {
|
||||
return nil, fmt.Errorf("package %s has no function %s", vname, name)
|
||||
}
|
||||
return nil, fmt.Errorf("%s has no member %s", vname, name)
|
||||
}
|
||||
return nil, v.Unreadable
|
||||
}
|
||||
return v.Children[0].findMethod(mname)
|
||||
if v.closureAddr != 0 {
|
||||
fn = v.bi.PCToFunc(v.Base)
|
||||
if fn != nil {
|
||||
cst := fn.extra(v.bi).closureStructType
|
||||
v = v.newVariable(v.Name, v.closureAddr, cst, v.mem)
|
||||
closure = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
queue := []*Variable{v}
|
||||
seen := map[string]struct{}{}
|
||||
first := true
|
||||
|
||||
for len(queue) > 0 {
|
||||
v := queue[0]
|
||||
@ -3032,42 +3065,20 @@ func (v *Variable) findMethod(mname string) (*Variable, error) {
|
||||
typ = ptyp.Type
|
||||
}
|
||||
|
||||
var pkg, receiver string
|
||||
typePath := typ.Common().Name
|
||||
dot := strings.LastIndex(typePath, ".")
|
||||
if dot < 0 {
|
||||
// probably just a C type
|
||||
continue
|
||||
if dot >= 0 {
|
||||
pkg, receiver = typePath[:dot], typePath[dot+1:]
|
||||
}
|
||||
|
||||
pkg := typePath[:dot]
|
||||
receiver := typePath[dot+1:]
|
||||
|
||||
//TODO(aarzilli): support generic functions?
|
||||
|
||||
if fns := v.bi.LookupFunc()[fmt.Sprintf("%s.%s.%s", pkg, receiver, mname)]; len(fns) == 1 {
|
||||
r, err := functionToVariable(fns[0], v.bi, v.mem)
|
||||
if len(pkg) > 0 && len(receiver) > 0 {
|
||||
rv, err := lookupMethod(v, isptr, pkg, receiver, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if rv != nil {
|
||||
return rv, nil
|
||||
}
|
||||
if isptr {
|
||||
r.Children = append(r.Children, *(v.maybeDereference()))
|
||||
} else {
|
||||
r.Children = append(r.Children, *v)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
if fns := v.bi.LookupFunc()[fmt.Sprintf("%s.(*%s).%s", pkg, receiver, mname)]; len(fns) == 1 {
|
||||
r, err := functionToVariable(fns[0], v.bi, v.mem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isptr {
|
||||
r.Children = append(r.Children, *v)
|
||||
} else {
|
||||
r.Children = append(r.Children, *(v.pointerToVariable()))
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// queue embedded fields for search
|
||||
@ -3079,14 +3090,88 @@ func (v *Variable) findMethod(mname string) (*Variable, error) {
|
||||
switch t := structVar.RealType.(type) {
|
||||
case *godwarf.StructType:
|
||||
for _, field := range t.Field {
|
||||
if field.Embedded {
|
||||
if !includeStructMember {
|
||||
if !field.Embedded {
|
||||
continue
|
||||
}
|
||||
embeddedVar, err := structVar.toField(field)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queue = append(queue, embeddedVar)
|
||||
continue
|
||||
}
|
||||
|
||||
if field.Name == name {
|
||||
return structVar.toField(field)
|
||||
}
|
||||
if len(queue) == 0 && field.Name == "&"+name && closure {
|
||||
f, err := structVar.toField(field)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f.maybeDereference(), nil
|
||||
}
|
||||
if !field.Embedded {
|
||||
continue
|
||||
}
|
||||
embeddedVar, err := structVar.toField(field)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Check for embedded field referenced by type name
|
||||
parts := strings.Split(field.Name, ".")
|
||||
if includeStructMember && len(parts) > 1 && parts[1] == name {
|
||||
return embeddedVar, nil
|
||||
}
|
||||
embeddedVar.Name = structVar.Name
|
||||
queue = append(queue, embeddedVar)
|
||||
}
|
||||
case *godwarf.InterfaceType:
|
||||
v.loadInterface(0, false, LoadConfig{})
|
||||
if len(v.Children) > 0 {
|
||||
if rv, _ := v.Children[0].findStructMemberOrMethod(name, false); rv != nil {
|
||||
return rv, nil
|
||||
}
|
||||
}
|
||||
default:
|
||||
if first {
|
||||
return nil, fmt.Errorf("%s (type %s) has no member %s", vname, structVar.TypeString(), name)
|
||||
}
|
||||
}
|
||||
first = false
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%s has no member %s", vname, name)
|
||||
}
|
||||
|
||||
func lookupMethod(v *Variable, isptr bool, pkg, receiver, name string) (*Variable, error) {
|
||||
checks := []struct {
|
||||
fmt string
|
||||
ptrRecv bool
|
||||
}{
|
||||
{"%s.(*%s).%s", true},
|
||||
{"%s.%s.%s", false},
|
||||
}
|
||||
if !isptr {
|
||||
checks[0], checks[1] = checks[1], checks[0]
|
||||
}
|
||||
|
||||
for _, check := range checks {
|
||||
if fns := v.bi.LookupFunc()[fmt.Sprintf(check.fmt, pkg, receiver, name)]; len(fns) == 1 {
|
||||
r, err := functionToVariable(fns[0], v.bi, v.mem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch {
|
||||
case isptr == check.ptrRecv:
|
||||
r.Children = append(r.Children, *v)
|
||||
case isptr && !check.ptrRecv:
|
||||
r.Children = append(r.Children, *(v.maybeDereference()))
|
||||
case !isptr && check.ptrRecv:
|
||||
r.Children = append(r.Children, *(v.pointerToVariable()))
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -815,7 +815,7 @@ func getEvalExpressionTestCases() []varTest {
|
||||
{"i2 << f1", false, "", "", "", errors.New("shift count type float64, must be unsigned integer")},
|
||||
{"i2 << -1", false, "", "", "", errors.New("shift count must not be negative")},
|
||||
{"*(i2 + i3)", false, "", "", "", errors.New("expression \"(i2 + i3)\" (int) can not be dereferenced")},
|
||||
{"i2.member", false, "", "", "", errors.New("i2 (type int) is not a struct")},
|
||||
{"i2.member", false, "", "", "", errors.New("i2 (type int) has no member member")},
|
||||
{"fmt.Println(\"hello\")", false, "", "", "", errors.New("function calls not allowed without using 'call'")},
|
||||
{"*nil", false, "", "", "", errors.New("nil can not be dereferenced")},
|
||||
{"!nil", false, "", "", "", errors.New("operator ! can not be applied to \"nil\"")},
|
||||
@ -2056,3 +2056,57 @@ func TestCapturedVarVisibleOnFirstLine(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// See issue #4116
|
||||
func TestEmbeddedStructMethodsAndFieldLookup(t *testing.T) {
|
||||
varTestcases := []varTest{
|
||||
{"v.Model", true, "\"B\"", "", "string", nil},
|
||||
{"v.A.Model", false, "main.(*A).Model", "", "func() string", nil},
|
||||
{"v1.X", false, "main.(*B1).X", "", "func()", nil},
|
||||
{"v1.A1.X", false, "0", "", "int", nil},
|
||||
{"v2.X", false, "main.(*B2).X", "", "func()", nil},
|
||||
{"v2.B2.X", false, "main.(*B2).X", "", "func()", nil},
|
||||
{"v2.B2.A2.X", true, "0", "", "int", nil},
|
||||
{"v2.A2.X", true, "0", "", "int", nil},
|
||||
{"v7.X", false, "main.A7.X", "", "func()", nil},
|
||||
{"x.X", false, "main.(*A7).X", "", "func()", nil},
|
||||
{"x1.X", false, "", "", "", errors.New("x1 (type void) has no member X")},
|
||||
{"x2.X", false, "main.(*A7).X", "", "func()", nil},
|
||||
{"x3.X", false, "main.(*A7).X", "", "func()", nil},
|
||||
{"v9.V.X", false, "main.(*B9).X", "", "func()", nil},
|
||||
{"a.X", false, "main.A8.X", "", "func()", nil},
|
||||
{"b.X", false, "", "", "", errors.New("b has no member X")},
|
||||
{"c.X", false, "main.(*A8).X", "", "func()", nil},
|
||||
{"d.X", true, "1", "", "int", nil},
|
||||
{"v3.X", false, "main.(*B3).X", "", "func()", nil},
|
||||
{"v3.B3.X", false, "main.(*B3).X", "", "func()", nil},
|
||||
{"v4.X", false, "main.(*A4).X", "", "func()", nil},
|
||||
{"v4.TestX.X", false, "main.(*A4).X", "", "func()", nil},
|
||||
{"v5.X", true, "0", "", "int", nil},
|
||||
{"v5.B5.X", false, "main.main.func1", "", "func() string", nil},
|
||||
{"v5.B5.A5.X", false, "main.main.func1", "", "func() string", nil},
|
||||
{"v5.A5.X", false, "main.main.func1", "", "func() string", nil},
|
||||
{"*(ch.buf)", true, "[0]int []", "", "[0]int", nil},
|
||||
{"*(v6.Chan.buf)", true, "[0]struct struct {} []", "", "[0]struct struct {}", nil},
|
||||
{"*(v6.buf)", false, "", "", "", errors.New("v6 has no member buf")},
|
||||
}
|
||||
|
||||
withTestProcess("issue4116", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||
assertNoError(grp.Continue(), t, "Continue()")
|
||||
|
||||
for _, tc := range varTestcases {
|
||||
variable, err := evalVariableWithCfg(p, tc.name, pnormalLoadConfig)
|
||||
if tc.err == nil {
|
||||
assertNoError(err, t, "EvalVariable() returned an error")
|
||||
assertVariable(t, variable, tc)
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error %s, got no error: %s\n", tc.err.Error(), api.ConvertVar(variable).SinglelineString())
|
||||
}
|
||||
if tc.err.Error() != err.Error() {
|
||||
t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user