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