mirror of
https://github.com/go-delve/delve.git
synced 2025-10-30 10:17:03 +08:00
proc: fix embedded field search (#2320)
Both structMember and findMethod implemented a depth-first search in embedded fields but the Go specification requires a breadth-first search. They also allowed promotion of fields in the concrete type of embedded interfaces even though this is not allowed by Go. Furthermore they both lacked protection from infinite recursion when a type embeds itself and the user requests a non-existent field. Fixes #2316
This commit is contained in:
committed by
GitHub
parent
dceffacb89
commit
f19d5e5c13
@ -106,6 +106,38 @@ type List struct {
|
||||
Next *List
|
||||
}
|
||||
|
||||
type T struct {
|
||||
F string
|
||||
}
|
||||
|
||||
type W1 struct {
|
||||
T
|
||||
}
|
||||
|
||||
func (*W1) M() {}
|
||||
|
||||
type I interface{ M() }
|
||||
|
||||
type W2 struct {
|
||||
W1
|
||||
T
|
||||
}
|
||||
|
||||
type W3 struct {
|
||||
I
|
||||
T
|
||||
}
|
||||
|
||||
type W4 struct {
|
||||
I
|
||||
}
|
||||
|
||||
type W5 struct {
|
||||
*W5
|
||||
}
|
||||
|
||||
var _ I = (*W2)(nil)
|
||||
|
||||
func main() {
|
||||
i1 := 1
|
||||
i2 := 2
|
||||
@ -312,6 +344,12 @@ func main() {
|
||||
ll := &List{0, &List{1, &List{2, &List{3, &List{4, nil}}}}}
|
||||
unread := (*int)(unsafe.Pointer(uintptr(12345)))
|
||||
|
||||
w2 := &W2{W1{T{"T-inside-W1"}}, T{"T-inside-W2"}}
|
||||
w3 := &W3{&W1{T{"T-inside-W1"}}, T{"T-inside-W3"}}
|
||||
w4 := &W4{&W1{T{"T-inside-W1"}}}
|
||||
w5 := &W5{nil}
|
||||
w5.W5 = w5
|
||||
|
||||
var amb1 = 1
|
||||
runtime.Breakpoint()
|
||||
for amb1 := 0; amb1 < 10; amb1++ {
|
||||
@ -319,5 +357,5 @@ func main() {
|
||||
}
|
||||
|
||||
runtime.Breakpoint()
|
||||
fmt.Println(i1, i2, i3, p1, amb1, s1, s3, a0, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, m4, upnil, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, ni64, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, errtypednil, emptyslice, emptymap, byteslice, runeslice, bytearray, runearray, longstr, nilstruct, as2, as2.NonPointerRecieverMethod, s4, iface2map, issue1578, ll, unread)
|
||||
fmt.Println(i1, i2, i3, p1, amb1, s1, s3, a0, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, m4, upnil, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, ni64, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, errtypednil, emptyslice, emptymap, byteslice, runeslice, bytearray, runearray, longstr, nilstruct, as2, as2.NonPointerRecieverMethod, s4, iface2map, issue1578, ll, unread, w2, w3, w4, w5)
|
||||
}
|
||||
|
||||
@ -1963,6 +1963,17 @@ func (v *Variable) findMethod(mname string) (*Variable, error) {
|
||||
return v.Children[0].findMethod(mname)
|
||||
}
|
||||
|
||||
queue := []*Variable{v}
|
||||
seen := map[string]struct{}{}
|
||||
|
||||
for len(queue) > 0 {
|
||||
v := queue[0]
|
||||
queue = append(queue[:0], queue[1:]...)
|
||||
if _, isseen := seen[v.RealType.String()]; isseen {
|
||||
continue
|
||||
}
|
||||
seen[v.RealType.String()] = struct{}{}
|
||||
|
||||
typ := v.DwarfType
|
||||
ptyp, isptr := typ.(*godwarf.PtrType)
|
||||
if isptr {
|
||||
@ -1973,7 +1984,7 @@ func (v *Variable) findMethod(mname string) (*Variable, error) {
|
||||
dot := strings.LastIndex(typePath, ".")
|
||||
if dot < 0 {
|
||||
// probably just a C type
|
||||
return nil, nil
|
||||
continue
|
||||
}
|
||||
|
||||
pkg := typePath[:dot]
|
||||
@ -2004,10 +2015,8 @@ func (v *Variable) findMethod(mname string) (*Variable, error) {
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
return v.tryFindMethodInEmbeddedFields(mname)
|
||||
}
|
||||
|
||||
func (v *Variable) tryFindMethodInEmbeddedFields(mname string) (*Variable, error) {
|
||||
// queue embedded fields for search
|
||||
structVar := v.maybeDereference()
|
||||
structVar.Name = v.Name
|
||||
if structVar.Unreadable != nil {
|
||||
@ -2017,19 +2026,16 @@ func (v *Variable) tryFindMethodInEmbeddedFields(mname string) (*Variable, error
|
||||
case *godwarf.StructType:
|
||||
for _, field := range t.Field {
|
||||
if field.Embedded {
|
||||
// Recursively check for promoted fields on the embedded field
|
||||
embeddedVar, err := structVar.toField(field)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if embeddedMethod, err := embeddedVar.findMethod(mname); err != nil {
|
||||
return nil, err
|
||||
} else if embeddedMethod != nil {
|
||||
return embeddedMethod, nil
|
||||
queue = append(queue, embeddedVar)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
@ -1060,6 +1060,19 @@ func (v *Variable) structMember(memberName string) (*Variable, error) {
|
||||
v = &v.Children[0]
|
||||
}
|
||||
}
|
||||
|
||||
queue := []*Variable{v}
|
||||
seen := map[string]struct{}{} // prevent infinite loops
|
||||
first := true
|
||||
|
||||
for len(queue) > 0 {
|
||||
v := queue[0]
|
||||
queue = append(queue[:0], queue[1:]...)
|
||||
if _, isseen := seen[v.RealType.String()]; isseen {
|
||||
continue
|
||||
}
|
||||
seen[v.RealType.String()] = struct{}{}
|
||||
|
||||
structVar := v.maybeDereference()
|
||||
structVar.Name = v.Name
|
||||
if structVar.Unreadable != nil {
|
||||
@ -1069,14 +1082,9 @@ func (v *Variable) structMember(memberName string) (*Variable, error) {
|
||||
switch t := structVar.RealType.(type) {
|
||||
case *godwarf.StructType:
|
||||
for _, field := range t.Field {
|
||||
if field.Name != memberName {
|
||||
continue
|
||||
}
|
||||
if field.Name == memberName {
|
||||
return structVar.toField(field)
|
||||
}
|
||||
// Check for embedded field only if field was
|
||||
// not a regular struct member
|
||||
for _, field := range t.Field {
|
||||
isEmbeddedStructMember :=
|
||||
field.Embedded ||
|
||||
(field.Type.Common().Name == field.Name) ||
|
||||
@ -1086,34 +1094,28 @@ func (v *Variable) structMember(memberName string) (*Variable, error) {
|
||||
if !isEmbeddedStructMember {
|
||||
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 len(parts) > 1 && parts[1] == memberName {
|
||||
embeddedVar, err := structVar.toField(field)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return embeddedVar, nil
|
||||
}
|
||||
// Recursively check for promoted fields on the embedded field
|
||||
embeddedVar, err := structVar.toField(field)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
embeddedVar.Name = structVar.Name
|
||||
embeddedField, _ := embeddedVar.structMember(memberName)
|
||||
if embeddedField != nil {
|
||||
return embeddedField, nil
|
||||
queue = append(queue, embeddedVar)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("%s has no member %s", vname, memberName)
|
||||
default:
|
||||
if v.Name == "" {
|
||||
return nil, fmt.Errorf("type %s is not a struct", structVar.TypeString())
|
||||
}
|
||||
if first {
|
||||
return nil, fmt.Errorf("%s (type %s) is not a struct", vname, structVar.TypeString())
|
||||
}
|
||||
}
|
||||
first = false
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%s has no member %s", vname, memberName)
|
||||
}
|
||||
|
||||
func readVarEntry(entry *godwarf.Tree, image *Image) (name string, typ godwarf.Type, err error) {
|
||||
name, ok := entry.Val(dwarf.AttrName).(string)
|
||||
|
||||
@ -1026,7 +1026,7 @@ func TestScopesAndVariablesRequests2(t *testing.T) {
|
||||
execute: func() {
|
||||
client.StackTraceRequest(1, 0, 20)
|
||||
stack := client.ExpectStackTraceResponse(t)
|
||||
expectStackFrames(t, stack, "main.main", 317, 1000, 3, 3)
|
||||
expectStackFrames(t, stack, "main.main", -1, 1000, 3, 3)
|
||||
|
||||
client.ScopesRequest(1000)
|
||||
scopes := client.ExpectScopesResponse(t)
|
||||
@ -1039,7 +1039,7 @@ func TestScopesAndVariablesRequests2(t *testing.T) {
|
||||
execute: func() {
|
||||
client.StackTraceRequest(1, 0, 20)
|
||||
stack := client.ExpectStackTraceResponse(t)
|
||||
expectStackFrames(t, stack, "main.main", 322, 1000, 3, 3)
|
||||
expectStackFrames(t, stack, "main.main", -1, 1000, 3, 3)
|
||||
|
||||
client.ScopesRequest(1000)
|
||||
scopes := client.ExpectScopesResponse(t)
|
||||
|
||||
@ -504,6 +504,20 @@ func TestEmbeddedStruct(t *testing.T) {
|
||||
{"b.C.s", true, "\"hello\"", "\"hello\"", "string", nil},
|
||||
{"b.s", true, "\"hello\"", "\"hello\"", "string", nil},
|
||||
{"b2", true, "main.B {A: main.A {val: 42}, C: *main.C nil, a: main.A {val: 47}, ptr: *main.A nil}", "main.B {A: (*main.A)(0x…", "main.B", nil},
|
||||
|
||||
// Issue 2316: field promotion is breadth first and embedded interfaces never get promoted
|
||||
{"w2.W1.T.F", true, `"T-inside-W1"`, `"T-inside-W1"`, "string", nil},
|
||||
{"w2.W1.F", true, `"T-inside-W1"`, `"T-inside-W1"`, "string", nil},
|
||||
{"w2.T.F", true, `"T-inside-W2"`, `"T-inside-W2"`, "string", nil},
|
||||
{"w2.F", true, `"T-inside-W2"`, `"T-inside-W2"`, "string", nil},
|
||||
{"w3.I.T.F", false, `"T-inside-W1"`, `"T-inside-W1"`, "string", nil},
|
||||
{"w3.I.F", false, `"T-inside-W1"`, `"T-inside-W1"`, "string", nil},
|
||||
{"w3.T.F", true, `"T-inside-W3"`, `"T-inside-W3"`, "string", nil},
|
||||
{"w3.F", true, `"T-inside-W3"`, `"T-inside-W3"`, "string", nil},
|
||||
{"w4.I.T.F", false, `"T-inside-W1"`, `"T-inside-W1"`, "string", nil},
|
||||
{"w4.I.F", false, `"T-inside-W1"`, `"T-inside-W1"`, "string", nil},
|
||||
{"w4.F", false, ``, ``, "", errors.New("w4 has no member F")},
|
||||
{"w5.F", false, ``, ``, "", errors.New("w5 has no member F")},
|
||||
}
|
||||
assertNoError(p.Continue(), t, "Continue()")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user