Skip to content

Commit d0b18e2

Browse files
findleyrgopherbot
authored andcommitted
go/analysis/passes/copylock: fix infinite recursion
Generalize the 'seenTParams' map to short-circuit all forms of infinite recursion in the lockPath function, not just through type parameters. For invalid code, it is possible to have infinite recursion through named types. Fixes golang/go#61678 Change-Id: I59d39c6fcaf3dd8bbd4f989516e2fb373b277889 Reviewed-on: https://go-review.googlesource.com/c/tools/+/514818 Reviewed-by: Robert Griesemer <[email protected]> Auto-Submit: Robert Findley <[email protected]> Run-TryBot: Robert Findley <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 5b4d426 commit d0b18e2

File tree

2 files changed

+40
-16
lines changed

2 files changed

+40
-16
lines changed

go/analysis/passes/copylock/copylock.go

+10-16
Original file line numberDiff line numberDiff line change
@@ -242,29 +242,23 @@ func lockPathRhs(pass *analysis.Pass, x ast.Expr) typePath {
242242
// lockPath returns a typePath describing the location of a lock value
243243
// contained in typ. If there is no contained lock, it returns nil.
244244
//
245-
// The seenTParams map is used to short-circuit infinite recursion via type
246-
// parameters.
247-
func lockPath(tpkg *types.Package, typ types.Type, seenTParams map[*typeparams.TypeParam]bool) typePath {
248-
if typ == nil {
245+
// The seen map is used to short-circuit infinite recursion due to type cycles.
246+
func lockPath(tpkg *types.Package, typ types.Type, seen map[types.Type]bool) typePath {
247+
if typ == nil || seen[typ] {
249248
return nil
250249
}
250+
if seen == nil {
251+
seen = make(map[types.Type]bool)
252+
}
253+
seen[typ] = true
251254

252255
if tpar, ok := typ.(*typeparams.TypeParam); ok {
253-
if seenTParams == nil {
254-
// Lazily allocate seenTParams, since the common case will not involve
255-
// any type parameters.
256-
seenTParams = make(map[*typeparams.TypeParam]bool)
257-
}
258-
if seenTParams[tpar] {
259-
return nil
260-
}
261-
seenTParams[tpar] = true
262256
terms, err := typeparams.StructuralTerms(tpar)
263257
if err != nil {
264258
return nil // invalid type
265259
}
266260
for _, term := range terms {
267-
subpath := lockPath(tpkg, term.Type(), seenTParams)
261+
subpath := lockPath(tpkg, term.Type(), seen)
268262
if len(subpath) > 0 {
269263
if term.Tilde() {
270264
// Prepend a tilde to our lock path entry to clarify the resulting
@@ -298,7 +292,7 @@ func lockPath(tpkg *types.Package, typ types.Type, seenTParams map[*typeparams.T
298292
ttyp, ok := typ.Underlying().(*types.Tuple)
299293
if ok {
300294
for i := 0; i < ttyp.Len(); i++ {
301-
subpath := lockPath(tpkg, ttyp.At(i).Type(), seenTParams)
295+
subpath := lockPath(tpkg, ttyp.At(i).Type(), seen)
302296
if subpath != nil {
303297
return append(subpath, typ.String())
304298
}
@@ -332,7 +326,7 @@ func lockPath(tpkg *types.Package, typ types.Type, seenTParams map[*typeparams.T
332326
nfields := styp.NumFields()
333327
for i := 0; i < nfields; i++ {
334328
ftyp := styp.Field(i).Type()
335-
subpath := lockPath(tpkg, ftyp, seenTParams)
329+
subpath := lockPath(tpkg, ftyp, seen)
336330
if subpath != nil {
337331
return append(subpath, typ.String())
338332
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package a
2+
3+
import "sync"
4+
5+
// These examples are taken from golang/go#61678, modified so that A and B
6+
// contain a mutex.
7+
8+
type A struct {
9+
a A
10+
mu sync.Mutex
11+
}
12+
13+
type B struct {
14+
a A
15+
b B
16+
mu sync.Mutex
17+
}
18+
19+
func okay(x A) {}
20+
func sure() { var x A; nop(x) }
21+
22+
var fine B
23+
24+
func what(x B) {} // want `passes lock by value`
25+
func bad() { var x B; nop(x) } // want `copies lock value`
26+
func good() { nop(B{}) }
27+
func stillgood() { nop(B{b: B{b: B{b: B{}}}}) }
28+
func nope() { nop(B{}.b) } // want `copies lock value`
29+
30+
func nop(any) {} // only used to get around unused variable errors

0 commit comments

Comments
 (0)