Skip to content

Commit 058ed05

Browse files
committed
go/analysis/passes/copylock: find locks via type parameters
Add support for finding incorrect usage of locks via type parameters. It would probably be fine not to do this, since using locks explicitly via type parameters should be exceedingly rare. However, it was straightforward to add, and is consistent with other analyzers. Updates golang/go#48704 Change-Id: I329a2fa9f11c6bbb491d49afde7fabce8299cbdf Reviewed-on: https://go-review.googlesource.com/c/tools/+/360234 Trust: Robert Findley <[email protected]> Run-TryBot: Robert Findley <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Tim King <[email protected]>
1 parent ebc40b3 commit 058ed05

File tree

3 files changed

+86
-6
lines changed

3 files changed

+86
-6
lines changed

go/analysis/passes/copylock/copylock.go

+35-5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"golang.org/x/tools/go/analysis/passes/inspect"
1818
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
1919
"golang.org/x/tools/go/ast/inspector"
20+
"golang.org/x/tools/internal/typeparams"
2021
)
2122

2223
const Doc = `check for locks erroneously passed by value
@@ -204,7 +205,7 @@ func checkCopyLocksRangeVar(pass *analysis.Pass, rtok token.Token, e ast.Expr) {
204205
}
205206
}
206207

207-
type typePath []types.Type
208+
type typePath []string
208209

209210
// String pretty-prints a typePath.
210211
func (path typePath) String() string {
@@ -215,7 +216,7 @@ func (path typePath) String() string {
215216
fmt.Fprint(&buf, " contains ")
216217
}
217218
// The human-readable path is in reverse order, outermost to innermost.
218-
fmt.Fprint(&buf, path[n-i-1].String())
219+
fmt.Fprint(&buf, path[n-i-1])
219220
}
220221
return buf.String()
221222
}
@@ -244,6 +245,35 @@ func lockPath(tpkg *types.Package, typ types.Type) typePath {
244245
return nil
245246
}
246247

248+
if tpar, ok := typ.(*typeparams.TypeParam); ok {
249+
terms, err := typeparams.StructuralTerms(tpar)
250+
if err != nil {
251+
return nil // invalid type
252+
}
253+
for _, term := range terms {
254+
subpath := lockPath(tpkg, term.Type())
255+
if len(subpath) > 0 {
256+
if term.Tilde() {
257+
// Prepend a tilde to our lock path entry to clarify the resulting
258+
// diagnostic message. Consider the following example:
259+
//
260+
// func _[Mutex interface{ ~sync.Mutex; M() }](m Mutex) {}
261+
//
262+
// Here the naive error message will be something like "passes lock
263+
// by value: Mutex contains sync.Mutex". This is misleading because
264+
// the local type parameter doesn't actually contain sync.Mutex,
265+
// which lacks the M method.
266+
//
267+
// With tilde, it is clearer that the containment is via an
268+
// approximation element.
269+
subpath[len(subpath)-1] = "~" + subpath[len(subpath)-1]
270+
}
271+
return append(subpath, typ.String())
272+
}
273+
}
274+
return nil
275+
}
276+
247277
for {
248278
atyp, ok := typ.Underlying().(*types.Array)
249279
if !ok {
@@ -263,7 +293,7 @@ func lockPath(tpkg *types.Package, typ types.Type) typePath {
263293
// is a sync.Locker, but a value is not. This differentiates
264294
// embedded interfaces from embedded values.
265295
if types.Implements(types.NewPointer(typ), lockerType) && !types.Implements(typ, lockerType) {
266-
return []types.Type{typ}
296+
return []string{typ.String()}
267297
}
268298

269299
// In go1.10, sync.noCopy did not implement Locker.
@@ -272,15 +302,15 @@ func lockPath(tpkg *types.Package, typ types.Type) typePath {
272302
if named, ok := typ.(*types.Named); ok &&
273303
named.Obj().Name() == "noCopy" &&
274304
named.Obj().Pkg().Path() == "sync" {
275-
return []types.Type{typ}
305+
return []string{typ.String()}
276306
}
277307

278308
nfields := styp.NumFields()
279309
for i := 0; i < nfields; i++ {
280310
ftyp := styp.Field(i).Type()
281311
subpath := lockPath(tpkg, ftyp)
282312
if subpath != nil {
283-
return append(subpath, typ)
313+
return append(subpath, typ.String())
284314
}
285315
}
286316

go/analysis/passes/copylock/copylock_test.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,14 @@ import (
99

1010
"golang.org/x/tools/go/analysis/analysistest"
1111
"golang.org/x/tools/go/analysis/passes/copylock"
12+
"golang.org/x/tools/internal/typeparams"
1213
)
1314

1415
func Test(t *testing.T) {
1516
testdata := analysistest.TestData()
16-
analysistest.Run(t, testdata, copylock.Analyzer, "a")
17+
pkgs := []string{"a"}
18+
if typeparams.Enabled {
19+
pkgs = append(pkgs, "typeparams")
20+
}
21+
analysistest.Run(t, testdata, copylock.Analyzer, pkgs...)
1722
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package typeparams
6+
7+
import "sync"
8+
9+
func OkFunc1[Struct ~*struct{ mu sync.Mutex }](s Struct) {
10+
}
11+
12+
func BadFunc1[Struct ~struct{ mu sync.Mutex }](s Struct) { // want `passes lock by value: .*Struct contains ~struct{mu sync.Mutex}`
13+
}
14+
15+
func OkFunc2[MutexPtr *sync.Mutex](m MutexPtr) {
16+
var x *MutexPtr
17+
p := x
18+
var y MutexPtr
19+
p = &y
20+
*p = *x
21+
22+
var mus []MutexPtr
23+
24+
for _, _ = range mus {
25+
}
26+
}
27+
28+
func BadFunc2[Mutex sync.Mutex](m Mutex) { // want `passes lock by value: .*Mutex contains sync.Mutex`
29+
var x *Mutex
30+
p := x
31+
var y Mutex
32+
p = &y
33+
*p = *x // want `assignment copies lock value to \*p: .*Mutex contains sync.Mutex`
34+
35+
var mus []Mutex
36+
37+
for _, _ = range mus {
38+
}
39+
}
40+
41+
func ApproximationError[Mutex interface {
42+
~sync.Mutex
43+
M()
44+
}](m Mutex) { // want `passes lock by value: .*Mutex contains ~sync.Mutex`
45+
}

0 commit comments

Comments
 (0)