Skip to content

Commit 0e85fd7

Browse files
committed
cmd/compile: report type loop for invalid recursive types
Similar to how we report initialization loops in initorder.go and type alias loops in typecheck.go, this CL updates align.go to warn about invalid recursive types. The code is based on the loop code from initorder.go, with minimal changes to adapt from detecting variable/function initialization loops to detecting type declaration loops. Thanks to Cuong Manh Le for investigating this, helping come up with test cases, and exploring solutions. Fixes #41575 Updates #41669. Change-Id: Idb2cb8c5e1d645e62900e178fcb50af33e1700a1 Reviewed-on: https://go-review.googlesource.com/c/go/+/258177 Run-TryBot: Matthew Dempsky <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Robert Griesemer <[email protected]> Reviewed-by: Cuong Manh Le <[email protected]> Trust: Matthew Dempsky <[email protected]> Trust: Cuong Manh Le <[email protected]>
1 parent af9c5e5 commit 0e85fd7

File tree

6 files changed

+147
-20
lines changed

6 files changed

+147
-20
lines changed

src/cmd/compile/internal/gc/align.go

Lines changed: 89 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
package gc
66

77
import (
8+
"bytes"
89
"cmd/compile/internal/types"
10+
"fmt"
911
"sort"
1012
)
1113

@@ -173,6 +175,91 @@ func widstruct(errtype *types.Type, t *types.Type, o int64, flag int) int64 {
173175
return o
174176
}
175177

178+
// findTypeLoop searches for an invalid type declaration loop involving
179+
// type t and reports whether one is found. If so, path contains the
180+
// loop.
181+
//
182+
// path points to a slice used for tracking the sequence of types
183+
// visited. Using a pointer to a slice allows the slice capacity to
184+
// grow and limit reallocations.
185+
func findTypeLoop(t *types.Type, path *[]*types.Type) bool {
186+
// We implement a simple DFS loop-finding algorithm. This
187+
// could be faster, but type cycles are rare.
188+
189+
if t.Sym != nil {
190+
// Declared type. Check for loops and otherwise
191+
// recurse on the type expression used in the type
192+
// declaration.
193+
194+
for i, x := range *path {
195+
if x == t {
196+
*path = (*path)[i:]
197+
return true
198+
}
199+
}
200+
201+
*path = append(*path, t)
202+
if findTypeLoop(asNode(t.Nod).Name.Param.Ntype.Type, path) {
203+
return true
204+
}
205+
*path = (*path)[:len(*path)-1]
206+
} else {
207+
// Anonymous type. Recurse on contained types.
208+
209+
switch t.Etype {
210+
case TARRAY:
211+
if findTypeLoop(t.Elem(), path) {
212+
return true
213+
}
214+
case TSTRUCT:
215+
for _, f := range t.Fields().Slice() {
216+
if findTypeLoop(f.Type, path) {
217+
return true
218+
}
219+
}
220+
case TINTER:
221+
for _, m := range t.Methods().Slice() {
222+
if m.Type.IsInterface() { // embedded interface
223+
if findTypeLoop(m.Type, path) {
224+
return true
225+
}
226+
}
227+
}
228+
}
229+
}
230+
231+
return false
232+
}
233+
234+
func reportTypeLoop(t *types.Type) {
235+
if t.Broke() {
236+
return
237+
}
238+
239+
var l []*types.Type
240+
if !findTypeLoop(t, &l) {
241+
Fatalf("failed to find type loop for: %v", t)
242+
}
243+
244+
// Rotate loop so that the earliest type declaration is first.
245+
i := 0
246+
for j, t := range l[1:] {
247+
if typePos(t).Before(typePos(l[i])) {
248+
i = j + 1
249+
}
250+
}
251+
l = append(l[i:], l[:i]...)
252+
253+
var msg bytes.Buffer
254+
fmt.Fprintf(&msg, "invalid recursive type %v\n", l[0])
255+
for _, t := range l {
256+
fmt.Fprintf(&msg, "\t%v: %v refers to\n", linestr(typePos(t)), t)
257+
t.SetBroke(true)
258+
}
259+
fmt.Fprintf(&msg, "\t%v: %v", linestr(typePos(l[0])), l[0])
260+
yyerrorl(typePos(l[0]), msg.String())
261+
}
262+
176263
// dowidth calculates and stores the size and alignment for t.
177264
// If sizeCalculationDisabled is set, and the size/alignment
178265
// have not already been calculated, it calls Fatal.
@@ -192,11 +279,7 @@ func dowidth(t *types.Type) {
192279
}
193280

194281
if t.Width == -2 {
195-
if !t.Broke() {
196-
t.SetBroke(true)
197-
yyerrorl(asNode(t.Nod).Pos, "invalid recursive type %v", t)
198-
}
199-
282+
reportTypeLoop(t)
200283
t.Width = 0
201284
t.Align = 1
202285
return
@@ -308,10 +391,7 @@ func dowidth(t *types.Type) {
308391
checkwidth(t.Key())
309392

310393
case TFORW: // should have been filled in
311-
if !t.Broke() {
312-
t.SetBroke(true)
313-
yyerror("invalid recursive type %v", t)
314-
}
394+
reportTypeLoop(t)
315395
w = 1 // anything will do
316396

317397
case TANY:

src/cmd/compile/internal/gc/subr.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1921,3 +1921,13 @@ func ifaceData(pos src.XPos, n *Node, t *types.Type) *Node {
19211921
ind.SetBounded(true)
19221922
return ind
19231923
}
1924+
1925+
// typePos returns the position associated with t.
1926+
// This is where t was declared or where it appeared as a type expression.
1927+
func typePos(t *types.Type) src.XPos {
1928+
n := asNode(t.Nod)
1929+
if n == nil || !n.Pos.IsKnown() {
1930+
Fatalf("bad type: %v", t)
1931+
}
1932+
return n.Pos
1933+
}

test/fixedbugs/bug195.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,22 @@
66

77
package main
88

9-
type I1 interface { I2 } // ERROR "interface"
9+
type I1 interface{ I2 } // ERROR "interface"
1010
type I2 int
1111

12-
type I3 interface { int } // ERROR "interface"
12+
type I3 interface{ int } // ERROR "interface"
1313

1414
type S struct {
15-
x interface{ S } // ERROR "interface"
15+
x interface{ S } // ERROR "interface"
1616
}
17-
type I4 interface { // GC_ERROR "invalid recursive type"
18-
I4 // GCCGO_ERROR "interface"
17+
type I4 interface { // GC_ERROR "invalid recursive type I4\n\tLINE: I4 refers to\n\tLINE: I4$"
18+
I4 // GCCGO_ERROR "interface"
1919
}
2020

21-
type I5 interface { // GC_ERROR "invalid recursive type"
22-
I6 // GCCGO_ERROR "interface"
21+
type I5 interface { // GC_ERROR "invalid recursive type I5\n\tLINE: I5 refers to\n\tLINE+4: I6 refers to\n\tLINE: I5$"
22+
I6 // GCCGO_ERROR "interface"
2323
}
2424

2525
type I6 interface {
26-
I5 // GCCGO_ERROR "interface"
26+
I5 // GCCGO_ERROR "interface"
2727
}

test/fixedbugs/issue22904.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99

1010
package p
1111

12-
type a struct{ b }
13-
type b struct{ a } // ERROR "invalid recursive type"
12+
type a struct{ b } // ERROR "invalid recursive type"
13+
type b struct{ a }
1414

1515
var x interface{}
1616

test/fixedbugs/issue23823.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type I1 = interface {
1010
I2
1111
}
1212

13-
type I2 interface { // ERROR "invalid recursive type"
13+
// BAD: type loop should mention I1; see also #41669
14+
type I2 interface { // ERROR "invalid recursive type I2\n\tLINE: I2 refers to\n\tLINE: I2$"
1415
I1
1516
}

test/fixedbugs/issue41575.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// errorcheck
2+
3+
// Copyright 2020 The Go Authors. All rights reserved. Use of this
4+
// source code is governed by a BSD-style license that can be found in
5+
// the LICENSE file.
6+
7+
package p
8+
9+
type T1 struct { // ERROR "invalid recursive type T1\n\tLINE: T1 refers to\n\tLINE+4: T2 refers to\n\tLINE: T1$"
10+
f2 T2
11+
}
12+
13+
type T2 struct {
14+
f1 T1
15+
}
16+
17+
type a b
18+
type b c // ERROR "invalid recursive type b\n\tLINE: b refers to\n\tLINE+1: c refers to\n\tLINE: b$"
19+
type c b
20+
21+
type d e
22+
type e f
23+
type f f // ERROR "invalid recursive type f\n\tLINE: f refers to\n\tLINE: f$"
24+
25+
type g struct { // ERROR "invalid recursive type g\n\tLINE: g refers to\n\tLINE: g$"
26+
h struct {
27+
g
28+
}
29+
}
30+
31+
type w x
32+
type x y // ERROR "invalid recursive type x\n\tLINE: x refers to\n\tLINE+1: y refers to\n\tLINE+2: z refers to\n\tLINE: x$"
33+
type y struct{ z }
34+
type z [10]x
35+
36+
type w2 w // refer to the type loop again

0 commit comments

Comments
 (0)