Skip to content

Commit 840b346

Browse files
mdempskygopherbot
authored andcommitted
cmd/compile: reject anonymous interface cycles
This CL changes cmd/compile to reject anonymous interface cycles like: type I interface { m() interface { I } } We don't anticipate any users to be affected by this change in practice. Nonetheless, this CL also adds a `-d=interfacecycles` compiler flag to suppress the error. And assuming no issue reports from users, we'll move the check into go/types and types2 instead. Updates #56103. Change-Id: I1f1dce2d7aa19fb388312cc020e99cc354afddcb Reviewed-on: https://go-review.googlesource.com/c/go/+/445598 Run-TryBot: Matthew Dempsky <[email protected]> Reviewed-by: Robert Griesemer <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Auto-Submit: Matthew Dempsky <[email protected]>
1 parent 199d77a commit 840b346

File tree

7 files changed

+159
-2
lines changed

7 files changed

+159
-2
lines changed

src/cmd/compile/internal/base/debug.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type DebugFlags struct {
3131
GCProg int `help:"print dump of GC programs"`
3232
Gossahash string `help:"hash value for use in debugging the compiler"`
3333
InlFuncsWithClosures int `help:"allow functions with closures to be inlined" concurrent:"ok"`
34+
InterfaceCycles int `help:"allow anonymous interface cycles"`
3435
Libfuzzer int `help:"enable coverage instrumentation for libfuzzer"`
3536
LocationLists int `help:"print information about DWARF location list creation"`
3637
Nil int `help:"print information about nil checks"`

src/cmd/compile/internal/noder/irgen.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,26 @@ func checkFiles(noders []*noder) (posMap, *types2.Package, *types2.Info) {
7272

7373
pkg, err := conf.Check(base.Ctxt.Pkgpath, files, info)
7474

75+
// Check for anonymous interface cycles (#56103).
76+
if base.Debug.InterfaceCycles == 0 {
77+
var f cycleFinder
78+
for _, file := range files {
79+
syntax.Inspect(file, func(n syntax.Node) bool {
80+
if n, ok := n.(*syntax.InterfaceType); ok {
81+
if f.hasCycle(n.GetTypeInfo().Type.(*types2.Interface)) {
82+
base.ErrorfAt(m.makeXPos(n.Pos()), "invalid recursive type: anonymous interface refers to itself (see https://go.dev/issue/56103)")
83+
84+
for typ := range f.cyclic {
85+
f.cyclic[typ] = false // suppress duplicate errors
86+
}
87+
}
88+
return false
89+
}
90+
return true
91+
})
92+
}
93+
}
94+
7595
// Implementation restriction: we don't allow not-in-heap types to
7696
// be used as type arguments (#54765).
7797
{
@@ -406,3 +426,91 @@ func (g *irgen) type2(x syntax.Expr) syntax.Type {
406426
}
407427
return tv.Type
408428
}
429+
430+
// A cycleFinder detects anonymous interface cycles (go.dev/issue/56103).
431+
type cycleFinder struct {
432+
cyclic map[*types2.Interface]bool
433+
}
434+
435+
// hasCycle reports whether typ is part of an anonymous interface cycle.
436+
func (f *cycleFinder) hasCycle(typ *types2.Interface) bool {
437+
// We use Method instead of ExplicitMethod to implicitly expand any
438+
// embedded interfaces. Then we just need to walk any anonymous
439+
// types, keeping track of *types2.Interface types we visit along
440+
// the way.
441+
for i := 0; i < typ.NumMethods(); i++ {
442+
if f.visit(typ.Method(i).Type()) {
443+
return true
444+
}
445+
}
446+
return false
447+
}
448+
449+
// visit recursively walks typ0 to check any referenced interface types.
450+
func (f *cycleFinder) visit(typ0 types2.Type) bool {
451+
for { // loop for tail recursion
452+
switch typ := typ0.(type) {
453+
default:
454+
base.Fatalf("unexpected type: %T", typ)
455+
456+
case *types2.Basic, *types2.Named, *types2.TypeParam:
457+
return false // named types cannot be part of an anonymous cycle
458+
case *types2.Pointer:
459+
typ0 = typ.Elem()
460+
case *types2.Array:
461+
typ0 = typ.Elem()
462+
case *types2.Chan:
463+
typ0 = typ.Elem()
464+
case *types2.Map:
465+
if f.visit(typ.Key()) {
466+
return true
467+
}
468+
typ0 = typ.Elem()
469+
case *types2.Slice:
470+
typ0 = typ.Elem()
471+
472+
case *types2.Struct:
473+
for i := 0; i < typ.NumFields(); i++ {
474+
if f.visit(typ.Field(i).Type()) {
475+
return true
476+
}
477+
}
478+
return false
479+
480+
case *types2.Interface:
481+
// The empty interface (e.g., "any") cannot be part of a cycle.
482+
if typ.NumExplicitMethods() == 0 && typ.NumEmbeddeds() == 0 {
483+
return false
484+
}
485+
486+
// As an optimization, we wait to allocate cyclic here, after
487+
// we've found at least one other (non-empty) anonymous
488+
// interface. This means when a cycle is present, we need to
489+
// make an extra recursive call to actually detect it. But for
490+
// most packages, it allows skipping the map allocation
491+
// entirely.
492+
if x, ok := f.cyclic[typ]; ok {
493+
return x
494+
}
495+
if f.cyclic == nil {
496+
f.cyclic = make(map[*types2.Interface]bool)
497+
}
498+
f.cyclic[typ] = true
499+
if f.hasCycle(typ) {
500+
return true
501+
}
502+
f.cyclic[typ] = false
503+
return false
504+
505+
case *types2.Signature:
506+
return f.visit(typ.Params()) || f.visit(typ.Results())
507+
case *types2.Tuple:
508+
for i := 0; i < typ.Len(); i++ {
509+
if f.visit(typ.At(i).Type()) {
510+
return true
511+
}
512+
}
513+
return false
514+
}
515+
}
516+
}

src/cmd/compile/internal/types2/stdlib_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ func TestStdFixed(t *testing.T) {
197197
"issue48230.go", // go/types doesn't check validity of //go:xxx directives
198198
"issue49767.go", // go/types does not have constraints on channel element size
199199
"issue49814.go", // go/types does not have constraints on array size
200+
"issue56103.go", // anonymous interface cycles; will be a type checker error in 1.22
200201

201202
// These tests requires runtime/cgo.Incomplete, which is only available on some platforms.
202203
// However, types2 does not know about build constraints.

src/go/types/stdlib_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ func TestStdFixed(t *testing.T) {
199199
"issue48230.go", // go/types doesn't check validity of //go:xxx directives
200200
"issue49767.go", // go/types does not have constraints on channel element size
201201
"issue49814.go", // go/types does not have constraints on array size
202+
"issue56103.go", // anonymous interface cycles; will be a type checker error in 1.22
202203

203204
// These tests requires runtime/cgo.Incomplete, which is only available on some platforms.
204205
// However, go/types does not know about build constraints.

test/fixedbugs/bug398.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// compile
1+
// compile -d=interfacecycles
22

33
// Copyright 2012 The Go Authors. All rights reserved.
44
// Use of this source code is governed by a BSD-style

test/fixedbugs/issue16369.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// compile
1+
// compile -d=interfacecycles
22

33
// Copyright 2016 The Go Authors. All rights reserved.
44
// Use of this source code is governed by a BSD-style

test/fixedbugs/issue56103.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// errorcheck
2+
3+
// Copyright 2022 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package p
8+
9+
// Self recursion.
10+
type i interface{ m() interface{ i } } // ERROR "invalid recursive type"
11+
type _ interface{ i } // no redundant error
12+
13+
// Mutual recursion.
14+
type j interface{ m() interface{ k } } // ERROR "invalid recursive type"
15+
type k interface{ m() interface{ j } }
16+
17+
// Both self and mutual recursion.
18+
type (
19+
a interface { // ERROR "invalid recursive type"
20+
m() interface {
21+
a
22+
b
23+
}
24+
}
25+
b interface {
26+
m() interface {
27+
a
28+
b
29+
}
30+
}
31+
)
32+
33+
// Self recursion through other types.
34+
func _() { type i interface{ m() *interface{ i } } } // ERROR "invalid recursive type"
35+
func _() { type i interface{ m() []interface{ i } } } // ERROR "invalid recursive type"
36+
func _() { type i interface{ m() [0]interface{ i } } } // ERROR "invalid recursive type"
37+
func _() { type i interface{ m() chan interface{ i } } } // ERROR "invalid recursive type"
38+
func _() { type i interface{ m() map[interface{ i }]int } } // ERROR "invalid recursive type"
39+
func _() { type i interface{ m() map[int]interface{ i } } } // ERROR "invalid recursive type"
40+
func _() { type i interface{ m() func(interface{ i }) } } // ERROR "invalid recursive type"
41+
func _() { type i interface{ m() func() interface{ i } } } // ERROR "invalid recursive type"
42+
func _() {
43+
type i interface { // ERROR "invalid recursive type"
44+
m() struct{ i interface{ i } }
45+
}
46+
}

0 commit comments

Comments
 (0)