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