Skip to content

Commit 462c182

Browse files
committed
go/types: use color-marking based cycle detection at package level
The existing cycle detection scheme passes around a (type name) path; when a type name re-appears in the path, a cycle is reported. Indirections (as in *T, func(T), etc.) are broken by starting a new (nil) path. The problem with this approach is that it doesn't work for cycles involving alias type names since they may be invalid even if there is an indirection. Furthermore, the path must be passed around through all functions which is currently not the case, which leads to less optimial error reporting. The new code is using the previously introduced color marking scheme and global object path for package-level cycle detection (function-local cycle detection doesn't use the same code path yet but is also much less important as cycles can only be created using the type being declared). The new code is guarded with an internal flag (useCycleMarking) so it can be disabled in short notice if this change introduced unexpected new issues. Fixes #23139. Fixes #25141. For #18640. For #24939. Change-Id: I1bbf2d2d61a375cf5885b2de1df0a9819d63e5fa Reviewed-on: https://go-review.googlesource.com/115455 Reviewed-by: Alan Donovan <[email protected]>
1 parent e57cdd8 commit 462c182

File tree

3 files changed

+137
-5
lines changed

3 files changed

+137
-5
lines changed

src/go/types/decl.go

+83-2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ func pathString(path []*TypeName) string {
4949
return s
5050
}
5151

52+
// useCycleMarking enables the new coloring-based cycle marking scheme
53+
// for package-level objects. Set this flag to false to disable this
54+
// code quickly and revert to the existing mechanism (and comment out
55+
// some of the new tests in cycles5.src that will fail again).
56+
// TODO(gri) remove this for Go 1.12
57+
const useCycleMarking = true
58+
5259
// objDecl type-checks the declaration of obj in its respective (file) context.
5360
// See check.typ for the details on def and path.
5461
func (check *Checker) objDecl(obj Object, def *Named, path []*TypeName) {
@@ -117,12 +124,32 @@ func (check *Checker) objDecl(obj Object, def *Named, path []*TypeName) {
117124
switch obj := obj.(type) {
118125
case *Const:
119126
visited = obj.visited
127+
120128
case *Var:
121129
visited = obj.visited
122-
default:
130+
131+
case *TypeName:
132+
assert(obj.Type() != nil)
133+
if useCycleMarking {
134+
check.typeCycle(obj)
135+
}
136+
return
137+
138+
case *Func:
139+
// Cycles involving functions require variables in
140+
// the cycle; they are pretty esoteric. For now we
141+
// handle this as before (for grey functions, the
142+
// function type is set to an empty signature which
143+
// makes it impossible to initialize a variable with
144+
// the function).
123145
assert(obj.Type() != nil)
124146
return
147+
148+
default:
149+
unreachable()
125150
}
151+
152+
// we have a *Const or *Var
126153
if obj.Type() != nil {
127154
return
128155
}
@@ -176,6 +203,60 @@ func (check *Checker) objDecl(obj Object, def *Named, path []*TypeName) {
176203
}
177204
}
178205

206+
// indir is a sentinel type name that is pushed onto the object path
207+
// to indicate an "indirection" in the dependency from one type name
208+
// to the next. For instance, for "type p *p" the object path contains
209+
// p followed by indir, indicating that there's an indirection *p.
210+
// Indirections are used to break type cycles.
211+
var indir = new(TypeName)
212+
213+
// typeCycle checks if the cycle starting with obj is valid and
214+
// reports an error if it is not.
215+
func (check *Checker) typeCycle(obj *TypeName) {
216+
d := check.objMap[obj]
217+
if d == nil {
218+
check.dump("%v: %s should have been declared", obj.Pos(), obj)
219+
unreachable()
220+
}
221+
222+
// A cycle must have at least one indirection and one defined
223+
// type to be permitted: If there is no indirection, the size
224+
// of the type cannot be computed (it's either infinite or 0);
225+
// if there is no defined type, we have a sequence of alias
226+
// type names which will expand ad infinitum.
227+
var hasIndir, hasDefType bool
228+
assert(obj.color() >= grey)
229+
start := obj.color() - grey // index of obj in objPath
230+
cycle := check.objPath[start:]
231+
for _, obj := range cycle {
232+
// Cycles may contain various objects; for now only look at type names.
233+
if tname, _ := obj.(*TypeName); tname != nil {
234+
if tname == indir {
235+
hasIndir = true
236+
} else if !check.objMap[tname].alias {
237+
hasDefType = true
238+
}
239+
if hasIndir && hasDefType {
240+
return // cycle is permitted
241+
}
242+
}
243+
}
244+
245+
// break cycle
246+
// (without this, calling underlying() below may lead to an endless loop)
247+
obj.typ = Typ[Invalid]
248+
249+
// report cycle
250+
check.errorf(obj.Pos(), "illegal cycle in declaration of %s", obj.Name())
251+
for _, obj := range cycle {
252+
if obj == indir {
253+
continue // don't print indir sentinels
254+
}
255+
check.errorf(obj.Pos(), "\t%s refers to", obj.Name()) // secondary error, \t indented
256+
}
257+
check.errorf(obj.Pos(), "\t%s", obj.Name())
258+
}
259+
179260
func (check *Checker) constDecl(obj *Const, typ, init ast.Expr) {
180261
assert(obj.typ == nil)
181262

@@ -353,7 +434,7 @@ func (check *Checker) addMethodDecls(obj *TypeName) {
353434
return
354435
}
355436
delete(check.methods, obj)
356-
assert(!obj.IsAlias())
437+
assert(!check.objMap[obj].alias) // don't use TypeName.IsAlias (requires fully set up object)
357438

358439
// use an objset to check for name conflicts
359440
var mset objset

src/go/types/testdata/cycles5.src

+37-2
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,12 @@ var _ = err.Error()
9797
// more esoteric cases
9898

9999
type (
100-
T1 interface { T2 /* ERROR not an interface */ }
100+
T1 interface { T2 }
101101
T2 /* ERROR cycle */ T2
102102
)
103103

104104
type (
105-
T3 interface { T4 /* ERROR not an interface */ }
105+
T3 interface { T4 }
106106
T4 /* ERROR cycle */ T5
107107
T5 = T6
108108
T6 = T7
@@ -117,3 +117,38 @@ const n = unsafe.Sizeof(func(){})
117117
type I interface {
118118
m([unsafe.Sizeof(func() { I.m(nil, [n]byte{}) })]byte)
119119
}
120+
121+
122+
// test cases for varias alias cycles
123+
124+
type T10 /* ERROR cycle */ = *T10 // issue #25141
125+
type T11 /* ERROR cycle */ = interface{ f(T11) } // issue #23139
126+
127+
// issue #18640
128+
type (
129+
aa = bb
130+
bb struct {
131+
*aa
132+
}
133+
)
134+
135+
type (
136+
a struct{ *b }
137+
b = c
138+
c struct{ *b }
139+
)
140+
141+
// issue #24939
142+
type (
143+
_ interface {
144+
M(P)
145+
}
146+
147+
M interface {
148+
F() P
149+
}
150+
151+
P = interface {
152+
I() M
153+
}
154+
)

src/go/types/typexpr.go

+17-1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *Named, path []*TypeNa
7171

7272
case *TypeName:
7373
x.mode = typexpr
74+
// package-level alias cycles are now checked by Checker.objDecl
75+
if useCycleMarking {
76+
if check.objMap[obj] != nil {
77+
break
78+
}
79+
}
7480
if check.cycle(obj, path, true) {
7581
// maintain x.mode == typexpr despite error
7682
typ = Typ[Invalid]
@@ -132,7 +138,11 @@ func (check *Checker) cycle(obj *TypeName, path []*TypeName, report bool) bool {
132138
// If def != nil, e is the type specification for the named type def, declared
133139
// in a type declaration, and def.underlying will be set to the type of e before
134140
// any components of e are type-checked. Path contains the path of named types
135-
// referring to this type.
141+
// referring to this type; i.e. it is the path of named types directly containing
142+
// each other and leading to the current type e. Indirect containment (e.g. via
143+
// pointer indirection, function parameter, etc.) breaks the path (leads to a new
144+
// path, and usually via calling Checker.typ below) and those types are not found
145+
// in the path.
136146
//
137147
func (check *Checker) typExpr(e ast.Expr, def *Named, path []*TypeName) (T Type) {
138148
if trace {
@@ -152,6 +162,12 @@ func (check *Checker) typExpr(e ast.Expr, def *Named, path []*TypeName) (T Type)
152162
}
153163

154164
func (check *Checker) typ(e ast.Expr) Type {
165+
// typExpr is called with a nil path indicating an indirection:
166+
// push indir sentinel on object path
167+
if useCycleMarking {
168+
check.push(indir)
169+
defer check.pop()
170+
}
155171
return check.typExpr(e, nil, nil)
156172
}
157173

0 commit comments

Comments
 (0)