Skip to content

Commit 37a2290

Browse files
committed
go/types: fix cycle detection
For Go 1.13, we rewrote the go/types cycle detection scheme. Unfortunately, it was a bit too clever and introduced a bug (#34333). Here's an example: type A struct { f1 *B f2 B } type B A When type-checking this code, the first cycle A->*B->B->A (via field f1) is ok because there's a pointer indirection. Though in the process B is considered "type-checked" (and painted/marked from "grey" to black"). When type-checking f2, since B is already completely set up, go/types doesn't complain about the invalid cycle A->B->A (via field f2) anymore. On the other hand, with the fields f1, f2 swapped: type A struct { f2 B f1 *B } go/types reports an error because the cycle A->B->A is type-checked first. In general, we cannot know the "right" order in which types need to be type-checked. This CL fixes the issue as follows: 1) The global object path cycle detection does not take (pointer, function, reference type) indirections into account anymore for cycle detection. That mechanism was incorrect to start with and the primary cause for this issue. As a consequence we don't need Checker.indirectType and indir anymore. 2) After processing type declarations, Checker.validType is called to verify that a type doesn't expand indefinitively. This corresponds essentially to cmd/compile's dowidth computation (without size computation). 3) Cycles involving only defined types (e.g.: type (A B; B C; C A)) require separate attention as those must now be detected when resolving "forward chains" of type declarations. Checker.underlying was changed to detect these cycles. All three cycle detection mechanism use an object path ([]Object) to report cycles. The cycle error reporting mechanism is now factored out into Checker.cycleError and used by all three mechanisms. It also makes an attempt to report the cycle starting with the "first" (earliest in the source) object. Fixes #34333. Change-Id: I2c6446445e47344cc2cd034d3c74b1c345b8c1e6 Reviewed-on: https://go-review.googlesource.com/c/go/+/196338 Run-TryBot: Robert Griesemer <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Matthew Dempsky <[email protected]>
1 parent 816ff44 commit 37a2290

File tree

7 files changed

+204
-102
lines changed

7 files changed

+204
-102
lines changed

src/go/types/decl.go

Lines changed: 161 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@ func pathString(path []Object) string {
5353
// For the meaning of def, see Checker.definedType, in typexpr.go.
5454
func (check *Checker) objDecl(obj Object, def *Named) {
5555
if trace {
56-
check.trace(obj.Pos(), "-- checking %s %s (objPath = %s)", obj.color(), obj, pathString(check.objPath))
56+
check.trace(obj.Pos(), "-- checking %s (%s, objPath = %s)", obj, obj.color(), pathString(check.objPath))
5757
check.indent++
5858
defer func() {
5959
check.indent--
60-
check.trace(obj.Pos(), "=> %s", obj)
60+
check.trace(obj.Pos(), "=> %s (%s)", obj, obj.color())
6161
}()
6262
}
6363

@@ -198,13 +198,6 @@ func (check *Checker) objDecl(obj Object, def *Named) {
198198
}
199199
}
200200

201-
// indir is a sentinel type name that is pushed onto the object path
202-
// to indicate an "indirection" in the dependency from one type name
203-
// to the next. For instance, for "type p *p" the object path contains
204-
// p followed by indir, indicating that there's an indirection *p.
205-
// Indirections are used to break type cycles.
206-
var indir = NewTypeName(token.NoPos, nil, "*", nil)
207-
208201
// typeCycle checks if the cycle starting with obj is valid and
209202
// reports an error if it is not.
210203
// TODO(gri) rename s/typeCycle/cycle/ once we don't need the other
@@ -221,52 +214,34 @@ func (check *Checker) typeCycle(obj Object) (isCycle bool) {
221214
}
222215
}
223216

224-
// Given the number of constants and variables (nval) in the cycle
225-
// and the cycle length (ncycle = number of named objects in the cycle),
226-
// we distinguish between cycles involving only constants and variables
227-
// (nval = ncycle), cycles involving types (and functions) only
228-
// (nval == 0), and mixed cycles (nval != 0 && nval != ncycle).
229-
// We ignore functions at the moment (taking them into account correctly
230-
// is complicated and it doesn't improve error reporting significantly).
231-
//
232-
// A cycle must have at least one indirection and one type definition
233-
// to be permitted: If there is no indirection, the size of the type
234-
// cannot be computed (it's either infinite or 0); if there is no type
235-
// definition, we have a sequence of alias type names which will expand
236-
// ad infinitum.
237-
var nval, ncycle int
238-
var hasIndir, hasTDef bool
217+
// Count cycle objects.
239218
assert(obj.color() >= grey)
240219
start := obj.color() - grey // index of obj in objPath
241220
cycle := check.objPath[start:]
242-
ncycle = len(cycle) // including indirections
221+
nval := 0 // number of (constant or variable) values in the cycle
222+
ndef := 0 // number of type definitions in the cycle
243223
for _, obj := range cycle {
244224
switch obj := obj.(type) {
245225
case *Const, *Var:
246226
nval++
247227
case *TypeName:
248-
if obj == indir {
249-
ncycle-- // don't count (indirections are not objects)
250-
hasIndir = true
228+
// Determine if the type name is an alias or not. For
229+
// package-level objects, use the object map which
230+
// provides syntactic information (which doesn't rely
231+
// on the order in which the objects are set up). For
232+
// local objects, we can rely on the order, so use
233+
// the object's predicate.
234+
// TODO(gri) It would be less fragile to always access
235+
// the syntactic information. We should consider storing
236+
// this information explicitly in the object.
237+
var alias bool
238+
if d := check.objMap[obj]; d != nil {
239+
alias = d.alias // package-level object
251240
} else {
252-
// Determine if the type name is an alias or not. For
253-
// package-level objects, use the object map which
254-
// provides syntactic information (which doesn't rely
255-
// on the order in which the objects are set up). For
256-
// local objects, we can rely on the order, so use
257-
// the object's predicate.
258-
// TODO(gri) It would be less fragile to always access
259-
// the syntactic information. We should consider storing
260-
// this information explicitly in the object.
261-
var alias bool
262-
if d := check.objMap[obj]; d != nil {
263-
alias = d.alias // package-level object
264-
} else {
265-
alias = obj.IsAlias() // function local object
266-
}
267-
if !alias {
268-
hasTDef = true
269-
}
241+
alias = obj.IsAlias() // function local object
242+
}
243+
if !alias {
244+
ndef++
270245
}
271246
case *Func:
272247
// ignored for now
@@ -276,8 +251,8 @@ func (check *Checker) typeCycle(obj Object) (isCycle bool) {
276251
}
277252

278253
if trace {
279-
check.trace(obj.Pos(), "## cycle detected: objPath = %s->%s (len = %d)", pathString(cycle), obj.Name(), ncycle)
280-
check.trace(obj.Pos(), "## cycle contains: %d values, has indirection = %v, has type definition = %v", nval, hasIndir, hasTDef)
254+
check.trace(obj.Pos(), "## cycle detected: objPath = %s->%s (len = %d)", pathString(cycle), obj.Name(), len(cycle))
255+
check.trace(obj.Pos(), "## cycle contains: %d values, %d type definitions", nval, ndef)
281256
defer func() {
282257
if isCycle {
283258
check.trace(obj.Pos(), "=> error: cycle is invalid")
@@ -288,30 +263,108 @@ func (check *Checker) typeCycle(obj Object) (isCycle bool) {
288263
// A cycle involving only constants and variables is invalid but we
289264
// ignore them here because they are reported via the initialization
290265
// cycle check.
291-
if nval == ncycle {
266+
if nval == len(cycle) {
292267
return false
293268
}
294269

295-
// A cycle involving only types (and possibly functions) must have at
296-
// least one indirection and one type definition to be permitted: If
297-
// there is no indirection, the size of the type cannot be computed
298-
// (it's either infinite or 0); if there is no type definition, we
270+
// A cycle involving only types (and possibly functions) must have at least
271+
// one type definition to be permitted: If there is no type definition, we
299272
// have a sequence of alias type names which will expand ad infinitum.
300-
if nval == 0 && hasIndir && hasTDef {
273+
if nval == 0 && ndef > 0 {
301274
return false // cycle is permitted
302275
}
303276

304-
// report cycle
305-
check.errorf(obj.Pos(), "illegal cycle in declaration of %s", obj.Name())
306-
for _, obj := range cycle {
307-
if obj == indir {
308-
continue // don't print indir sentinels
277+
check.cycleError(cycle)
278+
279+
return true
280+
}
281+
282+
type typeInfo uint
283+
284+
// validType verifies that the given type does not "expand" infinitely
285+
// producing a cycle in the type graph. Cycles are detected by marking
286+
// defined types.
287+
// (Cycles involving alias types, as in "type A = [10]A" are detected
288+
// earlier, via the objDecl cycle detection mechanism.)
289+
func (check *Checker) validType(typ Type, path []Object) typeInfo {
290+
const (
291+
unknown typeInfo = iota
292+
marked
293+
valid
294+
invalid
295+
)
296+
297+
switch t := typ.(type) {
298+
case *Array:
299+
return check.validType(t.elem, path)
300+
301+
case *Struct:
302+
for _, f := range t.fields {
303+
if check.validType(f.typ, path) == invalid {
304+
return invalid
305+
}
306+
}
307+
308+
case *Interface:
309+
for _, etyp := range t.embeddeds {
310+
if check.validType(etyp, path) == invalid {
311+
return invalid
312+
}
309313
}
314+
315+
case *Named:
316+
switch t.info {
317+
case unknown:
318+
t.info = marked
319+
t.info = check.validType(t.underlying, append(path, t.obj))
320+
case marked:
321+
// cycle detected
322+
for i, tn := range path {
323+
if tn == t.obj {
324+
check.cycleError(path[i:])
325+
t.info = invalid
326+
t.underlying = Typ[Invalid]
327+
return t.info
328+
}
329+
}
330+
panic("internal error: cycle start not found")
331+
}
332+
return t.info
333+
}
334+
335+
return valid
336+
}
337+
338+
// cycleError reports a declaration cycle starting with
339+
// the object in cycle that is "first" in the source.
340+
func (check *Checker) cycleError(cycle []Object) {
341+
// TODO(gri) Should we start with the last (rather than the first) object in the cycle
342+
// since that is the earliest point in the source where we start seeing the
343+
// cycle? That would be more consistent with other error messages.
344+
i := firstInSrc(cycle)
345+
obj := cycle[i]
346+
check.errorf(obj.Pos(), "illegal cycle in declaration of %s", obj.Name())
347+
for range cycle {
310348
check.errorf(obj.Pos(), "\t%s refers to", obj.Name()) // secondary error, \t indented
349+
i++
350+
if i >= len(cycle) {
351+
i = 0
352+
}
353+
obj = cycle[i]
311354
}
312355
check.errorf(obj.Pos(), "\t%s", obj.Name())
356+
}
313357

314-
return true
358+
// firstInSrc reports the index of the object with the "smallest"
359+
// source position in path. path must not be empty.
360+
func firstInSrc(path []Object) int {
361+
fst, pos := 0, path[0].Pos()
362+
for i, t := range path[1:] {
363+
if t.Pos() < pos {
364+
fst, pos = i+1, t.Pos()
365+
}
366+
}
367+
return fst
315368
}
316369

317370
func (check *Checker) constDecl(obj *Const, typ, init ast.Expr) {
@@ -409,15 +462,53 @@ func (check *Checker) varDecl(obj *Var, lhs []*Var, typ, init ast.Expr) {
409462

410463
// underlying returns the underlying type of typ; possibly by following
411464
// forward chains of named types. Such chains only exist while named types
412-
// are incomplete.
413-
func underlying(typ Type) Type {
465+
// are incomplete. If an underlying type is found, resolve the chain by
466+
// setting the underlying type for each defined type in the chain before
467+
// returning it.
468+
//
469+
// If no underlying type is found, a cycle error is reported and Typ[Invalid]
470+
// is used as underlying type for each defined type in the chain and returned
471+
// as result.
472+
func (check *Checker) underlying(typ Type) Type {
473+
// If typ is not a defined type, its underlying type is itself.
474+
n0, _ := typ.(*Named)
475+
if n0 == nil {
476+
return typ // nothing to do
477+
}
478+
479+
// If the underlying type of a defined type is not a defined
480+
// type, then that is the desired underlying type.
481+
typ = n0.underlying
482+
n, _ := typ.(*Named)
483+
if n == nil {
484+
return typ // common case
485+
}
486+
487+
// Otherwise, follow the forward chain.
488+
seen := map[*Named]int{n0: 0, n: 1}
489+
path := []Object{n0.obj, n.obj}
414490
for {
415-
n, _ := typ.(*Named)
491+
typ = n.underlying
492+
n, _ = typ.(*Named)
416493
if n == nil {
494+
break // end of chain
495+
}
496+
497+
if i, ok := seen[n]; ok {
498+
// cycle
499+
check.cycleError(path[i:])
500+
typ = Typ[Invalid]
417501
break
418502
}
419-
typ = n.underlying
503+
504+
seen[n] = len(seen)
505+
path = append(path, n.obj)
420506
}
507+
508+
for n := range seen {
509+
n.underlying = typ
510+
}
511+
421512
return typ
422513
}
423514

@@ -430,6 +521,10 @@ func (n *Named) setUnderlying(typ Type) {
430521
func (check *Checker) typeDecl(obj *TypeName, typ ast.Expr, def *Named, alias bool) {
431522
assert(obj.typ == nil)
432523

524+
check.later(func() {
525+
check.validType(obj.typ, nil)
526+
})
527+
433528
if alias {
434529

435530
obj.typ = Typ[Invalid]
@@ -456,8 +551,8 @@ func (check *Checker) typeDecl(obj *TypeName, typ ast.Expr, def *Named, alias bo
456551
// The type of C is the (named) type of A which is incomplete,
457552
// and which has as its underlying type the named type B.
458553
// Determine the (final, unnamed) underlying type by resolving
459-
// any forward chain (they always end in an unnamed type).
460-
named.underlying = underlying(named.underlying)
554+
// any forward chain.
555+
named.underlying = check.underlying(named)
461556

462557
}
463558

src/go/types/expr.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,12 +1157,9 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
11571157
}
11581158

11591159
case *Array:
1160-
// Prevent crash if the array referred to is not yet set up.
1161-
// This is a stop-gap solution; a better approach would use the mechanism of
1162-
// Checker.ident (typexpr.go) using a path of types. But that would require
1163-
// passing the path everywhere (all expression-checking methods, not just
1164-
// type expression checking), and we're not set up for that (quite possibly
1165-
// an indication that cycle detection needs to be rethought). Was issue #18643.
1160+
// Prevent crash if the array referred to is not yet set up. Was issue #18643.
1161+
// This is a stop-gap solution. Should use Checker.objPath to report entire
1162+
// path starting with earliest declaration in the source. TODO(gri) fix this.
11661163
if utyp.elem == nil {
11671164
check.error(e.Pos(), "illegal cycle in type declaration")
11681165
goto Error

src/go/types/testdata/cycles.src

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ type (
2323
A0 /* ERROR cycle */ [10]A0
2424
A1 [10]*A1
2525

26-
A2 /* ERROR cycle */ [10]A3
27-
A3 [10]A4
26+
// TODO(gri) It would be nicer to report the cycle starting
27+
// with A2 (also below, for S4). See issue #34771.
28+
A2 [10]A3
29+
A3 /* ERROR cycle */ [10]A4
2830
A4 A2
2931

3032
A5 [10]A6
@@ -39,8 +41,8 @@ type (
3941
S2 struct{ _ *S2 }
4042
S3 struct{ *S3 }
4143

42-
S4 /* ERROR cycle */ struct{ S5 }
43-
S5 struct{ S6 }
44+
S4 struct{ S5 }
45+
S5 /* ERROR cycle */ struct{ S6 }
4446
S6 S4
4547

4648
// pointers
@@ -147,7 +149,7 @@ type (
147149
// test cases for issue 18643
148150
// (type cycle detection when non-type expressions are involved)
149151
type (
150-
T14 /* ERROR cycle */ [len(T14{})]int
152+
T14 [len(T14 /* ERROR cycle */ {})]int
151153
T15 [][len(T15 /* ERROR cycle */ {})]int
152154
T16 map[[len(T16 /* ERROR cycle */ {1:2})]int]int
153155
T17 map[int][len(T17 /* ERROR cycle */ {1:2})]int

src/go/types/testdata/cycles5.src

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,13 @@ func h() [h /* ERROR no value */ ()[0]]int { panic(0) }
188188

189189
var c14 /* ERROR cycle */ T14
190190
type T14 [uintptr(unsafe.Sizeof(&c14))]byte
191+
192+
// issue #34333
193+
type T15 /* ERROR cycle */ struct {
194+
f func() T16
195+
b T16
196+
}
197+
198+
type T16 struct {
199+
T15
200+
}

src/go/types/testdata/decls0.src

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,6 @@ type (
7272
a /* ERROR "illegal cycle" */ a
7373
a /* ERROR "redeclared" */ int
7474

75-
// where the cycle error appears depends on the
76-
// order in which declarations are processed
77-
// (which depends on the order in which a map
78-
// is iterated through)
7975
b /* ERROR "illegal cycle" */ c
8076
c d
8177
d e

src/go/types/type.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ func (c *Chan) Elem() Type { return c.elem }
448448

449449
// A Named represents a named type.
450450
type Named struct {
451+
info typeInfo // for cycle detection
451452
obj *TypeName // corresponding declared object
452453
underlying Type // possibly a *Named during setup; never a *Named once set up completely
453454
methods []*Func // methods declared for this type (not the method set of this type); signatures are type-checked lazily

0 commit comments

Comments
 (0)