diff --git a/_fixtures/issue4116.go b/_fixtures/issue4116.go new file mode 100644 index 00000000..c36610b8 --- /dev/null +++ b/_fixtures/issue4116.go @@ -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 +} diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index 0bf32962..54852364 100644 --- a/pkg/proc/eval.go +++ b/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 .() @@ -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 } } diff --git a/pkg/proc/variables_test.go b/pkg/proc/variables_test.go index 42416892..0ca3ca68 100644 --- a/pkg/proc/variables_test.go +++ b/pkg/proc/variables_test.go @@ -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()) + } + } + } + }) +}