1
2
3
4
5
6
7 package copylock
8
9 import (
10 "bytes"
11 "fmt"
12 "go/ast"
13 "go/token"
14 "go/types"
15
16 "golang.org/x/tools/go/analysis"
17 "golang.org/x/tools/go/analysis/passes/inspect"
18 "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
19 "golang.org/x/tools/go/ast/astutil"
20 "golang.org/x/tools/go/ast/inspector"
21 "golang.org/x/tools/internal/typeparams"
22 )
23
24 const Doc = `check for locks erroneously passed by value
25
26 Inadvertently copying a value containing a lock, such as sync.Mutex or
27 sync.WaitGroup, may cause both copies to malfunction. Generally such
28 values should be referred to through a pointer.`
29
30 var Analyzer = &analysis.Analyzer{
31 Name: "copylocks",
32 Doc: Doc,
33 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/copylocks",
34 Requires: []*analysis.Analyzer{inspect.Analyzer},
35 RunDespiteErrors: true,
36 Run: run,
37 }
38
39 func run(pass *analysis.Pass) (interface{}, error) {
40 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
41
42 nodeFilter := []ast.Node{
43 (*ast.AssignStmt)(nil),
44 (*ast.CallExpr)(nil),
45 (*ast.CompositeLit)(nil),
46 (*ast.FuncDecl)(nil),
47 (*ast.FuncLit)(nil),
48 (*ast.GenDecl)(nil),
49 (*ast.RangeStmt)(nil),
50 (*ast.ReturnStmt)(nil),
51 }
52 inspect.Preorder(nodeFilter, func(node ast.Node) {
53 switch node := node.(type) {
54 case *ast.RangeStmt:
55 checkCopyLocksRange(pass, node)
56 case *ast.FuncDecl:
57 checkCopyLocksFunc(pass, node.Name.Name, node.Recv, node.Type)
58 case *ast.FuncLit:
59 checkCopyLocksFunc(pass, "func", nil, node.Type)
60 case *ast.CallExpr:
61 checkCopyLocksCallExpr(pass, node)
62 case *ast.AssignStmt:
63 checkCopyLocksAssign(pass, node)
64 case *ast.GenDecl:
65 checkCopyLocksGenDecl(pass, node)
66 case *ast.CompositeLit:
67 checkCopyLocksCompositeLit(pass, node)
68 case *ast.ReturnStmt:
69 checkCopyLocksReturnStmt(pass, node)
70 }
71 })
72 return nil, nil
73 }
74
75
76
77 func checkCopyLocksAssign(pass *analysis.Pass, as *ast.AssignStmt) {
78 for i, x := range as.Rhs {
79 if path := lockPathRhs(pass, x); path != nil {
80 pass.ReportRangef(x, "assignment copies lock value to %v: %v", analysisutil.Format(pass.Fset, as.Lhs[i]), path)
81 }
82 }
83 }
84
85
86
87 func checkCopyLocksGenDecl(pass *analysis.Pass, gd *ast.GenDecl) {
88 if gd.Tok != token.VAR {
89 return
90 }
91 for _, spec := range gd.Specs {
92 valueSpec := spec.(*ast.ValueSpec)
93 for i, x := range valueSpec.Values {
94 if path := lockPathRhs(pass, x); path != nil {
95 pass.ReportRangef(x, "variable declaration copies lock value to %v: %v", valueSpec.Names[i].Name, path)
96 }
97 }
98 }
99 }
100
101
102 func checkCopyLocksCompositeLit(pass *analysis.Pass, cl *ast.CompositeLit) {
103 for _, x := range cl.Elts {
104 if node, ok := x.(*ast.KeyValueExpr); ok {
105 x = node.Value
106 }
107 if path := lockPathRhs(pass, x); path != nil {
108 pass.ReportRangef(x, "literal copies lock value from %v: %v", analysisutil.Format(pass.Fset, x), path)
109 }
110 }
111 }
112
113
114 func checkCopyLocksReturnStmt(pass *analysis.Pass, rs *ast.ReturnStmt) {
115 for _, x := range rs.Results {
116 if path := lockPathRhs(pass, x); path != nil {
117 pass.ReportRangef(x, "return copies lock value: %v", path)
118 }
119 }
120 }
121
122
123 func checkCopyLocksCallExpr(pass *analysis.Pass, ce *ast.CallExpr) {
124 var id *ast.Ident
125 switch fun := ce.Fun.(type) {
126 case *ast.Ident:
127 id = fun
128 case *ast.SelectorExpr:
129 id = fun.Sel
130 }
131 if fun, ok := pass.TypesInfo.Uses[id].(*types.Builtin); ok {
132 switch fun.Name() {
133 case "new", "len", "cap", "Sizeof", "Offsetof", "Alignof":
134 return
135 }
136 }
137 for _, x := range ce.Args {
138 if path := lockPathRhs(pass, x); path != nil {
139 pass.ReportRangef(x, "call of %s copies lock value: %v", analysisutil.Format(pass.Fset, ce.Fun), path)
140 }
141 }
142 }
143
144
145
146
147
148 func checkCopyLocksFunc(pass *analysis.Pass, name string, recv *ast.FieldList, typ *ast.FuncType) {
149 if recv != nil && len(recv.List) > 0 {
150 expr := recv.List[0].Type
151 if path := lockPath(pass.Pkg, pass.TypesInfo.Types[expr].Type, nil); path != nil {
152 pass.ReportRangef(expr, "%s passes lock by value: %v", name, path)
153 }
154 }
155
156 if typ.Params != nil {
157 for _, field := range typ.Params.List {
158 expr := field.Type
159 if path := lockPath(pass.Pkg, pass.TypesInfo.Types[expr].Type, nil); path != nil {
160 pass.ReportRangef(expr, "%s passes lock by value: %v", name, path)
161 }
162 }
163 }
164
165
166
167
168
169 }
170
171
172
173
174 func checkCopyLocksRange(pass *analysis.Pass, r *ast.RangeStmt) {
175 checkCopyLocksRangeVar(pass, r.Tok, r.Key)
176 checkCopyLocksRangeVar(pass, r.Tok, r.Value)
177 }
178
179 func checkCopyLocksRangeVar(pass *analysis.Pass, rtok token.Token, e ast.Expr) {
180 if e == nil {
181 return
182 }
183 id, isId := e.(*ast.Ident)
184 if isId && id.Name == "_" {
185 return
186 }
187
188 var typ types.Type
189 if rtok == token.DEFINE {
190 if !isId {
191 return
192 }
193 obj := pass.TypesInfo.Defs[id]
194 if obj == nil {
195 return
196 }
197 typ = obj.Type()
198 } else {
199 typ = pass.TypesInfo.Types[e].Type
200 }
201
202 if typ == nil {
203 return
204 }
205 if path := lockPath(pass.Pkg, typ, nil); path != nil {
206 pass.Reportf(e.Pos(), "range var %s copies lock: %v", analysisutil.Format(pass.Fset, e), path)
207 }
208 }
209
210 type typePath []string
211
212
213 func (path typePath) String() string {
214 n := len(path)
215 var buf bytes.Buffer
216 for i := range path {
217 if i > 0 {
218 fmt.Fprint(&buf, " contains ")
219 }
220
221 fmt.Fprint(&buf, path[n-i-1])
222 }
223 return buf.String()
224 }
225
226 func lockPathRhs(pass *analysis.Pass, x ast.Expr) typePath {
227 x = astutil.Unparen(x)
228
229 if _, ok := x.(*ast.CompositeLit); ok {
230 return nil
231 }
232 if _, ok := x.(*ast.CallExpr); ok {
233
234 return nil
235 }
236 if star, ok := x.(*ast.StarExpr); ok {
237 if _, ok := astutil.Unparen(star.X).(*ast.CallExpr); ok {
238
239 return nil
240 }
241 }
242 return lockPath(pass.Pkg, pass.TypesInfo.Types[x].Type, nil)
243 }
244
245
246
247
248
249 func lockPath(tpkg *types.Package, typ types.Type, seen map[types.Type]bool) typePath {
250 if typ == nil || seen[typ] {
251 return nil
252 }
253 if seen == nil {
254 seen = make(map[types.Type]bool)
255 }
256 seen[typ] = true
257
258 if tpar, ok := typ.(*types.TypeParam); ok {
259 terms, err := typeparams.StructuralTerms(tpar)
260 if err != nil {
261 return nil
262 }
263 for _, term := range terms {
264 subpath := lockPath(tpkg, term.Type(), seen)
265 if len(subpath) > 0 {
266 if term.Tilde() {
267
268
269
270
271
272
273
274
275
276
277
278
279 subpath[len(subpath)-1] = "~" + subpath[len(subpath)-1]
280 }
281 return append(subpath, typ.String())
282 }
283 }
284 return nil
285 }
286
287 for {
288 atyp, ok := typ.Underlying().(*types.Array)
289 if !ok {
290 break
291 }
292 typ = atyp.Elem()
293 }
294
295 ttyp, ok := typ.Underlying().(*types.Tuple)
296 if ok {
297 for i := 0; i < ttyp.Len(); i++ {
298 subpath := lockPath(tpkg, ttyp.At(i).Type(), seen)
299 if subpath != nil {
300 return append(subpath, typ.String())
301 }
302 }
303 return nil
304 }
305
306
307
308 styp, ok := typ.Underlying().(*types.Struct)
309 if !ok {
310 return nil
311 }
312
313
314
315
316 if types.Implements(types.NewPointer(typ), lockerType) && !types.Implements(typ, lockerType) {
317 return []string{typ.String()}
318 }
319
320
321
322
323 if analysisutil.IsNamedType(typ, "sync", "noCopy") {
324 return []string{typ.String()}
325 }
326
327 nfields := styp.NumFields()
328 for i := 0; i < nfields; i++ {
329 ftyp := styp.Field(i).Type()
330 subpath := lockPath(tpkg, ftyp, seen)
331 if subpath != nil {
332 return append(subpath, typ.String())
333 }
334 }
335
336 return nil
337 }
338
339 var lockerType *types.Interface
340
341
342 func init() {
343 nullary := types.NewSignature(nil, nil, nil, false)
344 methods := []*types.Func{
345 types.NewFunc(token.NoPos, nil, "Lock", nullary),
346 types.NewFunc(token.NoPos, nil, "Unlock", nullary),
347 }
348 lockerType = types.NewInterface(methods, nil).Complete()
349 }
350
View as plain text