Skip to content

Commit

Permalink
go/types/objectpath: break cycles through type parameters in find
Browse files Browse the repository at this point in the history
When searching for objects, we naively traversed through type parameter
constraints. This leads to infinite recursion when there are cycles
through type parameter constraints.

Break these cycles by tracking type parameter names that have previously
been encountered. This ensures we walk type parameter constraints at
most once. Note that if the desired object was not found on the first
search of the constraint, there is no need to search again.

For golang/go#51717

Change-Id: Ifcdf4b138a0e95441e485bbb9ee21c01b04eaed4
Reviewed-on: https://go-review.googlesource.com/c/tools/+/393376
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
  • Loading branch information
findleyr committed Mar 16, 2022
1 parent 77aa08b commit d67eab4
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 24 deletions.
56 changes: 33 additions & 23 deletions go/types/objectpath/objectpath.go
Expand Up @@ -254,18 +254,18 @@ func For(obj types.Object) (Path, error) {

if tname.IsAlias() {
// type alias
if r := find(obj, T, path); r != nil {
if r := find(obj, T, path, nil); r != nil {
return Path(r), nil
}
} else {
if named, _ := T.(*types.Named); named != nil {
if r := findTypeParam(obj, typeparams.ForNamed(named), path); r != nil {
if r := findTypeParam(obj, typeparams.ForNamed(named), path, nil); r != nil {
// generic named type
return Path(r), nil
}
}
// defined (named) type
if r := find(obj, T.Underlying(), append(path, opUnderlying)); r != nil {
if r := find(obj, T.Underlying(), append(path, opUnderlying), nil); r != nil {
return Path(r), nil
}
}
Expand All @@ -279,7 +279,7 @@ func For(obj types.Object) (Path, error) {
if _, ok := o.(*types.TypeName); !ok {
if o.Exported() {
// exported non-type (const, var, func)
if r := find(obj, o.Type(), append(path, opType)); r != nil {
if r := find(obj, o.Type(), append(path, opType), nil); r != nil {
return Path(r), nil
}
}
Expand All @@ -299,7 +299,7 @@ func For(obj types.Object) (Path, error) {
if m == obj {
return Path(path2), nil // found declared method
}
if r := find(obj, m.Type(), append(path2, opType)); r != nil {
if r := find(obj, m.Type(), append(path2, opType), nil); r != nil {
return Path(r), nil
}
}
Expand All @@ -316,41 +316,44 @@ func appendOpArg(path []byte, op byte, arg int) []byte {
}

// find finds obj within type T, returning the path to it, or nil if not found.
func find(obj types.Object, T types.Type, path []byte) []byte {
//
// The seen map is used to short circuit cycles through type parameters. If
// nil, it will be allocated as necessary.
func find(obj types.Object, T types.Type, path []byte, seen map[*types.TypeName]bool) []byte {
switch T := T.(type) {
case *types.Basic, *types.Named:
// Named types belonging to pkg were handled already,
// so T must belong to another package. No path.
return nil
case *types.Pointer:
return find(obj, T.Elem(), append(path, opElem))
return find(obj, T.Elem(), append(path, opElem), seen)
case *types.Slice:
return find(obj, T.Elem(), append(path, opElem))
return find(obj, T.Elem(), append(path, opElem), seen)
case *types.Array:
return find(obj, T.Elem(), append(path, opElem))
return find(obj, T.Elem(), append(path, opElem), seen)
case *types.Chan:
return find(obj, T.Elem(), append(path, opElem))
return find(obj, T.Elem(), append(path, opElem), seen)
case *types.Map:
if r := find(obj, T.Key(), append(path, opKey)); r != nil {
if r := find(obj, T.Key(), append(path, opKey), seen); r != nil {
return r
}
return find(obj, T.Elem(), append(path, opElem))
return find(obj, T.Elem(), append(path, opElem), seen)
case *types.Signature:
if r := findTypeParam(obj, typeparams.ForSignature(T), path); r != nil {
if r := findTypeParam(obj, typeparams.ForSignature(T), path, seen); r != nil {
return r
}
if r := find(obj, T.Params(), append(path, opParams)); r != nil {
if r := find(obj, T.Params(), append(path, opParams), seen); r != nil {
return r
}
return find(obj, T.Results(), append(path, opResults))
return find(obj, T.Results(), append(path, opResults), seen)
case *types.Struct:
for i := 0; i < T.NumFields(); i++ {
f := T.Field(i)
fld := T.Field(i)
path2 := appendOpArg(path, opField, i)
if f == obj {
if fld == obj {
return path2 // found field var
}
if r := find(obj, f.Type(), append(path2, opType)); r != nil {
if r := find(obj, fld.Type(), append(path2, opType), seen); r != nil {
return r
}
}
Expand All @@ -362,7 +365,7 @@ func find(obj types.Object, T types.Type, path []byte) []byte {
if v == obj {
return path2 // found param/result var
}
if r := find(obj, v.Type(), append(path2, opType)); r != nil {
if r := find(obj, v.Type(), append(path2, opType), seen); r != nil {
return r
}
}
Expand All @@ -374,7 +377,7 @@ func find(obj types.Object, T types.Type, path []byte) []byte {
if m == obj {
return path2 // found interface method
}
if r := find(obj, m.Type(), append(path2, opType)); r != nil {
if r := find(obj, m.Type(), append(path2, opType), seen); r != nil {
return r
}
}
Expand All @@ -384,19 +387,26 @@ func find(obj types.Object, T types.Type, path []byte) []byte {
if name == obj {
return append(path, opObj)
}
if r := find(obj, T.Constraint(), append(path, opConstraint)); r != nil {
if seen[name] {
return nil
}
if seen == nil {
seen = make(map[*types.TypeName]bool)
}
seen[name] = true
if r := find(obj, T.Constraint(), append(path, opConstraint), seen); r != nil {
return r
}
return nil
}
panic(T)
}

func findTypeParam(obj types.Object, list *typeparams.TypeParamList, path []byte) []byte {
func findTypeParam(obj types.Object, list *typeparams.TypeParamList, path []byte, seen map[*types.TypeName]bool) []byte {
for i := 0; i < list.Len(); i++ {
tparam := list.At(i)
path2 := appendOpArg(path, opTypeParam, i)
if r := find(obj, tparam, path2); r != nil {
if r := find(obj, tparam, path2, seen); r != nil {
return r
}
}
Expand Down
45 changes: 44 additions & 1 deletion go/types/objectpath/objectpath_go118_test.go
Expand Up @@ -34,7 +34,7 @@ func (N) M1()
type A = T[int, N]
func F[FP0, FP1 any](FP0, FP1) {}
func F[FP0 any, FP1 interface{ M() }](FP0, FP1) {}
`},
}
paths := []pathTest{
Expand All @@ -45,6 +45,8 @@ func F[FP0, FP1 any](FP0, FP1) {}
{"b", "T.T0O", "type parameter TP0 any", ""},
{"b", "T.T1O", "type parameter TP1 interface{M0(); M1()}", ""},
{"b", "T.T1CM0", "func (interface).M0()", ""},
{"b", "F.T0O", "type parameter FP0 any", ""},
{"b", "F.T1CM0", "func (interface).M()", ""},
// Obj of an instance is the generic declaration.
{"b", "A.O", "type b.T[TP0 any, TP1 interface{M0(); M1()}] struct{}", ""},
{"b", "A.M0", "func (b.T[int, b.N]).M()", ""},
Expand Down Expand Up @@ -91,3 +93,44 @@ func F[FP0, FP1 any](FP0, FP1) {}
}
}
}

func TestGenericPaths_Issue51717(t *testing.T) {
pkgs := map[string]map[string]string{
"p": {"p.go": `
package p
type S struct{}
func (_ S) M() {
// The go vet stackoverflow crash disappears when the following line is removed
panic("")
}
func F[WL interface{ N(item W) WL }, W any]() {
}
func main() {}
`},
}
paths := []pathTest{
{"p", "F.T0CM0.RA0", "var WL", ""},
{"p", "F.T0CM0.RA0.CM0", "func (interface).N(item W) WL", ""},

// Finding S.M0 reproduced the infinite recursion reported in #51717,
// because F is searched before S.
{"p", "S.M0", "func (p.S).M()", ""},
}

conf := loader.Config{Build: buildutil.FakeContext(pkgs)}
conf.Import("p")
prog, err := conf.Load()
if err != nil {
t.Fatal(err)
}

for _, test := range paths {
if err := testPath(prog, test); err != nil {
t.Error(err)
}
}
}

0 comments on commit d67eab4

Please sign in to comment.