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:
wenxuan70
2025-09-10 23:47:52 +08:00
committed by GitHub
parent 079addae06
commit 1e3cceeb6d
3 changed files with 404 additions and 46 deletions

219
_fixtures/issue4116.go Normal file
View 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
}

View File

@ -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
}
}

View File

@ -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())
}
}
}
})
}