golang.org/x/tools@v0.21.0/go/analysis/passes/copylock/copylock.go (about) 1 // Copyright 2013 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 copylock defines an Analyzer that checks for locks 6 // erroneously passed by value. 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/copylocks", 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 // checkCopyLocksAssign checks whether an assignment 77 // copies a lock. 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 // checkCopyLocksGenDecl checks whether lock is copied 87 // in variable declaration. 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 // checkCopyLocksCompositeLit detects lock copy inside a composite literal 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 // checkCopyLocksReturnStmt detects lock copy in return statement 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 // checkCopyLocksCallExpr detects lock copy in the arguments to a function call 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 // checkCopyLocksFunc checks whether a function might 146 // inadvertently copy a lock, by checking whether 147 // its receiver, parameters, or return values 148 // are locks. 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 // Don't check typ.Results. If T has a Lock field it's OK to write 167 // return T{} 168 // because that is returning the zero value. Leave result checking 169 // to the return statement. 170 } 171 172 // checkCopyLocksRange checks whether a range statement 173 // might inadvertently copy a lock by checking whether 174 // any of the range variables are locks. 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 // String pretty-prints a typePath. 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 // The human-readable path is in reverse order, outermost to innermost. 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) // ignore parens on rhs 229 230 if _, ok := x.(*ast.CompositeLit); ok { 231 return nil 232 } 233 if _, ok := x.(*ast.CallExpr); ok { 234 // A call may return a zero value. 235 return nil 236 } 237 if star, ok := x.(*ast.StarExpr); ok { 238 if _, ok := astutil.Unparen(star.X).(*ast.CallExpr); ok { 239 // A call may return a pointer to a zero value. 240 return nil 241 } 242 } 243 return lockPath(pass.Pkg, pass.TypesInfo.Types[x].Type, nil) 244 } 245 246 // lockPath returns a typePath describing the location of a lock value 247 // contained in typ. If there is no contained lock, it returns nil. 248 // 249 // The seen map is used to short-circuit infinite recursion due to type cycles. 250 func lockPath(tpkg *types.Package, typ types.Type, seen map[types.Type]bool) typePath { 251 if typ == nil || seen[typ] { 252 return nil 253 } 254 if seen == nil { 255 seen = make(map[types.Type]bool) 256 } 257 seen[typ] = true 258 259 if tpar, ok := aliases.Unalias(typ).(*types.TypeParam); ok { 260 terms, err := typeparams.StructuralTerms(tpar) 261 if err != nil { 262 return nil // invalid type 263 } 264 for _, term := range terms { 265 subpath := lockPath(tpkg, term.Type(), seen) 266 if len(subpath) > 0 { 267 if term.Tilde() { 268 // Prepend a tilde to our lock path entry to clarify the resulting 269 // diagnostic message. Consider the following example: 270 // 271 // func _[Mutex interface{ ~sync.Mutex; M() }](m Mutex) {} 272 // 273 // Here the naive error message will be something like "passes lock 274 // by value: Mutex contains sync.Mutex". This is misleading because 275 // the local type parameter doesn't actually contain sync.Mutex, 276 // which lacks the M method. 277 // 278 // With tilde, it is clearer that the containment is via an 279 // approximation element. 280 subpath[len(subpath)-1] = "~" + subpath[len(subpath)-1] 281 } 282 return append(subpath, typ.String()) 283 } 284 } 285 return nil 286 } 287 288 for { 289 atyp, ok := typ.Underlying().(*types.Array) 290 if !ok { 291 break 292 } 293 typ = atyp.Elem() 294 } 295 296 ttyp, ok := typ.Underlying().(*types.Tuple) 297 if ok { 298 for i := 0; i < ttyp.Len(); i++ { 299 subpath := lockPath(tpkg, ttyp.At(i).Type(), seen) 300 if subpath != nil { 301 return append(subpath, typ.String()) 302 } 303 } 304 return nil 305 } 306 307 // We're only interested in the case in which the underlying 308 // type is a struct. (Interfaces and pointers are safe to copy.) 309 styp, ok := typ.Underlying().(*types.Struct) 310 if !ok { 311 return nil 312 } 313 314 // We're looking for cases in which a pointer to this type 315 // is a sync.Locker, but a value is not. This differentiates 316 // embedded interfaces from embedded values. 317 if types.Implements(types.NewPointer(typ), lockerType) && !types.Implements(typ, lockerType) { 318 return []string{typ.String()} 319 } 320 321 // In go1.10, sync.noCopy did not implement Locker. 322 // (The Unlock method was added only in CL 121876.) 323 // TODO(adonovan): remove workaround when we drop go1.10. 324 if analysisutil.IsNamedType(typ, "sync", "noCopy") { 325 return []string{typ.String()} 326 } 327 328 nfields := styp.NumFields() 329 for i := 0; i < nfields; i++ { 330 ftyp := styp.Field(i).Type() 331 subpath := lockPath(tpkg, ftyp, seen) 332 if subpath != nil { 333 return append(subpath, typ.String()) 334 } 335 } 336 337 return nil 338 } 339 340 var lockerType *types.Interface 341 342 // Construct a sync.Locker interface type. 343 func init() { 344 nullary := types.NewSignature(nil, nil, nil, false) // func() 345 methods := []*types.Func{ 346 types.NewFunc(token.NoPos, nil, "Lock", nullary), 347 types.NewFunc(token.NoPos, nil, "Unlock", nullary), 348 } 349 lockerType = types.NewInterface(methods, nil).Complete() 350 }