Skip to content

Commit 48987ba

Browse files
committed
cmd/compile: correct alias cycle detection
The original fix (https://go-review.googlesource.com/c/go/+/35831) for this issue was incorrect as it reported cycles in cases where it shouldn't. Instead, use a different approach: A type cycle containing aliases is only a cycle if there are no type definitions. As soon as there is a type definition, alias expansion terminates and there is no cycle. Approach: Split sprint_depchain into two non-recursive and more easily understandable functions (cycleFor and cycleTrace), and use those instead for cycle reporting. Analyze the cycle returned by cycleFor before issueing an alias cycle error. Also: Removed original fix (main.go) which introduced a separate crash (#23823). Fixes #18640. Fixes #23823. Fixes #24939. Change-Id: Ic3707a9dec40a71dc928a3e49b4868c5fac3d3b7 Reviewed-on: https://go-review.googlesource.com/118078 Reviewed-by: Matthew Dempsky <[email protected]>
1 parent dda7985 commit 48987ba

File tree

5 files changed

+100
-21
lines changed

5 files changed

+100
-21
lines changed

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

+2-4
Original file line numberDiff line numberDiff line change
@@ -481,15 +481,13 @@ func Main(archInit func(*Arch)) {
481481
// Phase 1: const, type, and names and types of funcs.
482482
// This will gather all the information about types
483483
// and methods but doesn't depend on any of it.
484-
// We also defer type alias declarations until phase 2
485-
// to avoid cycles like #18640.
486484
defercheckwidth()
487485

488486
// Don't use range--typecheck can add closures to xtop.
489487
timings.Start("fe", "typecheck", "top1")
490488
for i := 0; i < len(xtop); i++ {
491489
n := xtop[i]
492-
if op := n.Op; op != ODCL && op != OAS && op != OAS2 && (op != ODCLTYPE || !n.Left.Name.Param.Alias) {
490+
if op := n.Op; op != ODCL && op != OAS && op != OAS2 {
493491
xtop[i] = typecheck(n, Etop)
494492
}
495493
}
@@ -501,7 +499,7 @@ func Main(archInit func(*Arch)) {
501499
timings.Start("fe", "typecheck", "top2")
502500
for i := 0; i < len(xtop); i++ {
503501
n := xtop[i]
504-
if op := n.Op; op == ODCL || op == OAS || op == OAS2 || op == ODCLTYPE && n.Left.Name.Param.Alias {
502+
if op := n.Op; op == ODCL || op == OAS || op == OAS2 {
505503
xtop[i] = typecheck(n, Etop)
506504
}
507505
}

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

+41-17
Original file line numberDiff line numberDiff line change
@@ -110,19 +110,35 @@ func typekind(t *types.Type) string {
110110
return fmt.Sprintf("etype=%d", et)
111111
}
112112

113-
// sprint_depchain prints a dependency chain of nodes into trace.
114-
// It is used by typecheck in the case of OLITERAL nodes
115-
// to print constant definition loops.
116-
func sprint_depchain(trace *string, stack []*Node, cur *Node, first *Node) {
117-
for i := len(stack) - 1; i >= 0; i-- {
118-
if n := stack[i]; n.Op == cur.Op {
119-
if n != first {
120-
sprint_depchain(trace, stack[:i], n, first)
121-
}
122-
*trace += fmt.Sprintf("\n\t%v: %v uses %v", n.Line(), n, cur)
123-
return
113+
func cycleFor(start *Node) []*Node {
114+
// Find the start node in typecheck_tcstack.
115+
// We know that it must exist because each time we mark
116+
// a node with n.SetTypecheck(2) we push it on the stack,
117+
// and each time we mark a node with n.SetTypecheck(2) we
118+
// pop it from the stack. We hit a cycle when we encounter
119+
// a node marked 2 in which case is must be on the stack.
120+
i := len(typecheck_tcstack) - 1
121+
for i > 0 && typecheck_tcstack[i] != start {
122+
i--
123+
}
124+
125+
// collect all nodes with same Op
126+
var cycle []*Node
127+
for _, n := range typecheck_tcstack[i:] {
128+
if n.Op == start.Op {
129+
cycle = append(cycle, n)
124130
}
125131
}
132+
133+
return cycle
134+
}
135+
136+
func cycleTrace(cycle []*Node) string {
137+
var s string
138+
for i, n := range cycle {
139+
s += fmt.Sprintf("\n\t%v: %v uses %v", n.Line(), n, cycle[(i+1)%len(cycle)])
140+
}
141+
return s
126142
}
127143

128144
var typecheck_tcstack []*Node
@@ -174,20 +190,28 @@ func typecheck(n *Node, top int) *Node {
174190
}
175191

176192
case OTYPE:
193+
// Only report a type cycle if we are expecting a type.
194+
// Otherwise let other code report an error.
177195
if top&Etype == Etype {
178-
var trace string
179-
sprint_depchain(&trace, typecheck_tcstack, n, n)
180-
yyerrorl(n.Pos, "invalid recursive type alias %v%s", n, trace)
196+
// A cycle containing only alias types is an error
197+
// since it would expand indefinitely when aliases
198+
// are substituted.
199+
cycle := cycleFor(n)
200+
for _, n := range cycle {
201+
if n.Name != nil && !n.Name.Param.Alias {
202+
lineno = lno
203+
return n
204+
}
205+
}
206+
yyerrorl(n.Pos, "invalid recursive type alias %v%s", n, cycleTrace(cycle))
181207
}
182208

183209
case OLITERAL:
184210
if top&(Erv|Etype) == Etype {
185211
yyerror("%v is not a type", n)
186212
break
187213
}
188-
var trace string
189-
sprint_depchain(&trace, typecheck_tcstack, n, n)
190-
yyerrorl(n.Pos, "constant definition loop%s", trace)
214+
yyerrorl(n.Pos, "constant definition loop%s", cycleTrace(cycleFor(n)))
191215
}
192216

193217
if nsavederrors+nerrors == 0 {

test/fixedbugs/issue18640.go

+21
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,37 @@ type (
1111
b struct {
1212
*a
1313
}
14+
)
1415

16+
type (
1517
c struct {
1618
*d
1719
}
1820
d = c
21+
)
1922

23+
// The compiler reports an incorrect (non-alias related)
24+
// type cycle here (via dowith()). Disabled for now.
25+
// See issue #25838.
26+
/*
27+
type (
2028
e = f
2129
f = g
2230
g = []h
2331
h i
2432
i = j
2533
j = e
2634
)
35+
*/
36+
37+
type (
38+
a1 struct{ *b1 }
39+
b1 = c1
40+
c1 struct{ *b1 }
41+
)
42+
43+
type (
44+
a2 struct{ b2 }
45+
b2 = c2
46+
c2 struct{ *b2 }
47+
)

test/fixedbugs/issue23823.go

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// errorcheck
2+
3+
// Copyright 2018 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+
type I1 = interface {
10+
I2
11+
}
12+
13+
type I2 interface { // ERROR "invalid recursive type"
14+
I1
15+
}

test/fixedbugs/issue24939.go

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// compile
2+
3+
// Copyright 2018 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 main
8+
9+
type T interface {
10+
M(P)
11+
}
12+
13+
type M interface {
14+
F() P
15+
}
16+
17+
type P = interface {
18+
I() M
19+
}
20+
21+
func main() {}

0 commit comments

Comments
 (0)