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 <mdempsky@google.com>
This commit is contained in:
Robert Griesemer 2018-06-06 10:21:15 -07:00
parent dda7985a7b
commit 48987baa09
5 changed files with 100 additions and 21 deletions

View File

@ -481,15 +481,13 @@ func Main(archInit func(*Arch)) {
// Phase 1: const, type, and names and types of funcs. // Phase 1: const, type, and names and types of funcs.
// This will gather all the information about types // This will gather all the information about types
// and methods but doesn't depend on any of it. // and methods but doesn't depend on any of it.
// We also defer type alias declarations until phase 2
// to avoid cycles like #18640.
defercheckwidth() defercheckwidth()
// Don't use range--typecheck can add closures to xtop. // Don't use range--typecheck can add closures to xtop.
timings.Start("fe", "typecheck", "top1") timings.Start("fe", "typecheck", "top1")
for i := 0; i < len(xtop); i++ { for i := 0; i < len(xtop); i++ {
n := xtop[i] n := xtop[i]
if op := n.Op; op != ODCL && op != OAS && op != OAS2 && (op != ODCLTYPE || !n.Left.Name.Param.Alias) { if op := n.Op; op != ODCL && op != OAS && op != OAS2 {
xtop[i] = typecheck(n, Etop) xtop[i] = typecheck(n, Etop)
} }
} }
@ -501,7 +499,7 @@ func Main(archInit func(*Arch)) {
timings.Start("fe", "typecheck", "top2") timings.Start("fe", "typecheck", "top2")
for i := 0; i < len(xtop); i++ { for i := 0; i < len(xtop); i++ {
n := xtop[i] n := xtop[i]
if op := n.Op; op == ODCL || op == OAS || op == OAS2 || op == ODCLTYPE && n.Left.Name.Param.Alias { if op := n.Op; op == ODCL || op == OAS || op == OAS2 {
xtop[i] = typecheck(n, Etop) xtop[i] = typecheck(n, Etop)
} }
} }

View File

@ -110,19 +110,35 @@ func typekind(t *types.Type) string {
return fmt.Sprintf("etype=%d", et) return fmt.Sprintf("etype=%d", et)
} }
// sprint_depchain prints a dependency chain of nodes into trace. func cycleFor(start *Node) []*Node {
// It is used by typecheck in the case of OLITERAL nodes // Find the start node in typecheck_tcstack.
// to print constant definition loops. // We know that it must exist because each time we mark
func sprint_depchain(trace *string, stack []*Node, cur *Node, first *Node) { // a node with n.SetTypecheck(2) we push it on the stack,
for i := len(stack) - 1; i >= 0; i-- { // and each time we mark a node with n.SetTypecheck(2) we
if n := stack[i]; n.Op == cur.Op { // pop it from the stack. We hit a cycle when we encounter
if n != first { // a node marked 2 in which case is must be on the stack.
sprint_depchain(trace, stack[:i], n, first) i := len(typecheck_tcstack) - 1
} for i > 0 && typecheck_tcstack[i] != start {
*trace += fmt.Sprintf("\n\t%v: %v uses %v", n.Line(), n, cur) i--
return }
// collect all nodes with same Op
var cycle []*Node
for _, n := range typecheck_tcstack[i:] {
if n.Op == start.Op {
cycle = append(cycle, n)
} }
} }
return cycle
}
func cycleTrace(cycle []*Node) string {
var s string
for i, n := range cycle {
s += fmt.Sprintf("\n\t%v: %v uses %v", n.Line(), n, cycle[(i+1)%len(cycle)])
}
return s
} }
var typecheck_tcstack []*Node var typecheck_tcstack []*Node
@ -174,10 +190,20 @@ func typecheck(n *Node, top int) *Node {
} }
case OTYPE: case OTYPE:
// Only report a type cycle if we are expecting a type.
// Otherwise let other code report an error.
if top&Etype == Etype { if top&Etype == Etype {
var trace string // A cycle containing only alias types is an error
sprint_depchain(&trace, typecheck_tcstack, n, n) // since it would expand indefinitely when aliases
yyerrorl(n.Pos, "invalid recursive type alias %v%s", n, trace) // are substituted.
cycle := cycleFor(n)
for _, n := range cycle {
if n.Name != nil && !n.Name.Param.Alias {
lineno = lno
return n
}
}
yyerrorl(n.Pos, "invalid recursive type alias %v%s", n, cycleTrace(cycle))
} }
case OLITERAL: case OLITERAL:
@ -185,9 +211,7 @@ func typecheck(n *Node, top int) *Node {
yyerror("%v is not a type", n) yyerror("%v is not a type", n)
break break
} }
var trace string yyerrorl(n.Pos, "constant definition loop%s", cycleTrace(cycleFor(n)))
sprint_depchain(&trace, typecheck_tcstack, n, n)
yyerrorl(n.Pos, "constant definition loop%s", trace)
} }
if nsavederrors+nerrors == 0 { if nsavederrors+nerrors == 0 {

View File

@ -11,12 +11,20 @@ type (
b struct { b struct {
*a *a
} }
)
type (
c struct { c struct {
*d *d
} }
d = c d = c
)
// The compiler reports an incorrect (non-alias related)
// type cycle here (via dowith()). Disabled for now.
// See issue #25838.
/*
type (
e = f e = f
f = g f = g
g = []h g = []h
@ -24,3 +32,16 @@ type (
i = j i = j
j = e j = e
) )
*/
type (
a1 struct{ *b1 }
b1 = c1
c1 struct{ *b1 }
)
type (
a2 struct{ b2 }
b2 = c2
c2 struct{ *b2 }
)

View File

@ -0,0 +1,15 @@
// errorcheck
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package p
type I1 = interface {
I2
}
type I2 interface { // ERROR "invalid recursive type"
I1
}

View File

@ -0,0 +1,21 @@
// compile
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
type T interface {
M(P)
}
type M interface {
F() P
}
type P = interface {
I() M
}
func main() {}