Skip to content

Commit e172e97

Browse files
committed
go/types/typeutil: break recursion through anonymous interfaces
In a type such as type X interface { m() []*interface { X } } the traversal never encounters the named type X in the result of m since Interface.Methods expands it to a set of methods, one that includes m, causing the traversal to get stuck. This change uses an alternative, shallow hash function on the types of interface methods to avoid the possibility of getting stuck in such a cycle. (An earlier draft used a stack of interface types to detect cycles, but the logic of caching made this approach quite tricky.) Fixes golang/go#56048 Fixes golang/go#26863 Change-Id: I28a604e6affae5dfdd05a62e405d49a3efc8d709 Reviewed-on: https://go-review.googlesource.com/c/tools/+/439117 gopls-CI: kokoro <noreply+kokoro@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Tim King <taking@google.com> Run-TryBot: Alan Donovan <adonovan@google.com>
1 parent f1c8f7f commit e172e97

File tree

2 files changed

+95
-7
lines changed

2 files changed

+95
-7
lines changed

go/types/typeutil/map.go

+76-1
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,9 @@ func (h Hasher) hashFor(t types.Type) uint32 {
332332
// Method order is not significant.
333333
// Ignore m.Pkg().
334334
m := t.Method(i)
335-
hash += 3*hashString(m.Name()) + 5*h.Hash(m.Type())
335+
// Use shallow hash on method signature to
336+
// avoid anonymous interface cycles.
337+
hash += 3*hashString(m.Name()) + 5*h.shallowHash(m.Type())
336338
}
337339

338340
// Hash type restrictions.
@@ -434,3 +436,76 @@ func (h Hasher) hashPtr(ptr interface{}) uint32 {
434436
h.ptrMap[ptr] = hash
435437
return hash
436438
}
439+
440+
// shallowHash computes a hash of t without looking at any of its
441+
// element Types, to avoid potential anonymous cycles in the types of
442+
// interface methods.
443+
//
444+
// When an unnamed non-empty interface type appears anywhere among the
445+
// arguments or results of an interface method, there is a potential
446+
// for endless recursion. Consider:
447+
//
448+
// type X interface { m() []*interface { X } }
449+
//
450+
// The problem is that the Methods of the interface in m's result type
451+
// include m itself; there is no mention of the named type X that
452+
// might help us break the cycle.
453+
// (See comment in go/types.identical, case *Interface, for more.)
454+
func (h Hasher) shallowHash(t types.Type) uint32 {
455+
// t is the type of an interface method (Signature),
456+
// its params or results (Tuples), or their immediate
457+
// elements (mostly Slice, Pointer, Basic, Named),
458+
// so there's no need to optimize anything else.
459+
switch t := t.(type) {
460+
case *types.Signature:
461+
var hash uint32 = 604171
462+
if t.Variadic() {
463+
hash *= 971767
464+
}
465+
// The Signature/Tuple recursion is always finite
466+
// and invariably shallow.
467+
return hash + 1062599*h.shallowHash(t.Params()) + 1282529*h.shallowHash(t.Results())
468+
469+
case *types.Tuple:
470+
n := t.Len()
471+
hash := 9137 + 2*uint32(n)
472+
for i := 0; i < n; i++ {
473+
hash += 53471161 * h.shallowHash(t.At(i).Type())
474+
}
475+
return hash
476+
477+
case *types.Basic:
478+
return 45212177 * uint32(t.Kind())
479+
480+
case *types.Array:
481+
return 1524181 + 2*uint32(t.Len())
482+
483+
case *types.Slice:
484+
return 2690201
485+
486+
case *types.Struct:
487+
return 3326489
488+
489+
case *types.Pointer:
490+
return 4393139
491+
492+
case *typeparams.Union:
493+
return 562448657
494+
495+
case *types.Interface:
496+
return 2124679 // no recursion here
497+
498+
case *types.Map:
499+
return 9109
500+
501+
case *types.Chan:
502+
return 9127
503+
504+
case *types.Named:
505+
return h.hashPtr(t.Obj())
506+
507+
case *typeparams.TypeParam:
508+
return h.hashPtr(t.Obj())
509+
}
510+
panic(fmt.Sprintf("shallowHash: %T: %v", t, t))
511+
}

go/types/typeutil/map_test.go

+19-6
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,14 @@ func Bar[P Constraint[P]]() {}
244244
func Baz[Q any]() {} // The underlying type of Constraint[P] is any.
245245
// But Quux is not.
246246
func Quux[Q interface{ quux() }]() {}
247+
248+
249+
type Issue56048_I interface{ m() interface { Issue56048_I } }
250+
var Issue56048 = Issue56048_I.m
251+
252+
type Issue56048_Ib interface{ m() chan []*interface { Issue56048_Ib } }
253+
var Issue56048b = Issue56048_Ib.m
254+
247255
`
248256

249257
fset := token.NewFileSet()
@@ -296,12 +304,14 @@ func Quux[Q interface{ quux() }]() {}
296304
ME1Type = scope.Lookup("ME1Type").Type()
297305
ME2 = scope.Lookup("ME2").Type()
298306

299-
Constraint = scope.Lookup("Constraint").Type()
300-
Foo = scope.Lookup("Foo").Type()
301-
Fn = scope.Lookup("Fn").Type()
302-
Bar = scope.Lookup("Foo").Type()
303-
Baz = scope.Lookup("Foo").Type()
304-
Quux = scope.Lookup("Quux").Type()
307+
Constraint = scope.Lookup("Constraint").Type()
308+
Foo = scope.Lookup("Foo").Type()
309+
Fn = scope.Lookup("Fn").Type()
310+
Bar = scope.Lookup("Foo").Type()
311+
Baz = scope.Lookup("Foo").Type()
312+
Quux = scope.Lookup("Quux").Type()
313+
Issue56048 = scope.Lookup("Issue56048").Type()
314+
Issue56048b = scope.Lookup("Issue56048b").Type()
305315
)
306316

307317
tmap := new(typeutil.Map)
@@ -371,6 +381,9 @@ func Quux[Q interface{ quux() }]() {}
371381
{Bar, "Bar", false},
372382
{Baz, "Baz", false},
373383
{Quux, "Quux", true},
384+
385+
{Issue56048, "Issue56048", true}, // (not actually about generics)
386+
{Issue56048b, "Issue56048b", true}, // (not actually about generics)
374387
}
375388

376389
for _, step := range steps {

0 commit comments

Comments
 (0)