github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/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/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 // checkCopyLocksAssign checks whether an assignment 74 // copies a lock. 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 // checkCopyLocksGenDecl checks whether lock is copied 84 // in variable declaration. 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 // checkCopyLocksCompositeLit detects lock copy inside a composite literal 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 // checkCopyLocksReturnStmt detects lock copy in return statement 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 // checkCopyLocksCallExpr detects lock copy in the arguments to a function call 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 // checkCopyLocksFunc checks whether a function might 143 // inadvertently copy a lock, by checking whether 144 // its receiver, parameters, or return values 145 // are locks. 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 // Don't check typ.Results. If T has a Lock field it's OK to write 164 // return T{} 165 // because that is returning the zero value. Leave result checking 166 // to the return statement. 167 } 168 169 // checkCopyLocksRange checks whether a range statement 170 // might inadvertently copy a lock by checking whether 171 // any of the range variables are locks. 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 // String pretty-prints a typePath. 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 // The human-readable path is in reverse order, outermost to innermost. 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 // A call may return a zero value. 230 return nil 231 } 232 if star, ok := x.(*ast.StarExpr); ok { 233 if _, ok := star.X.(*ast.CallExpr); ok { 234 // A call may return a pointer to a zero value. 235 return nil 236 } 237 } 238 return lockPath(pass.Pkg, pass.TypesInfo.Types[x].Type, nil) 239 } 240 241 // lockPath returns a typePath describing the location of a lock value 242 // contained in typ. If there is no contained lock, it returns nil. 243 // 244 // The seenTParams map is used to short-circuit infinite recursion via type 245 // parameters. 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 // Lazily allocate seenTParams, since the common case will not involve 254 // any type parameters. 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 // invalid type 264 } 265 for _, term := range terms { 266 subpath := lockPath(tpkg, term.Type(), seenTParams) 267 if len(subpath) > 0 { 268 if term.Tilde() { 269 // Prepend a tilde to our lock path entry to clarify the resulting 270 // diagnostic message. Consider the following example: 271 // 272 // func _[Mutex interface{ ~sync.Mutex; M() }](m Mutex) {} 273 // 274 // Here the naive error message will be something like "passes lock 275 // by value: Mutex contains sync.Mutex". This is misleading because 276 // the local type parameter doesn't actually contain sync.Mutex, 277 // which lacks the M method. 278 // 279 // With tilde, it is clearer that the containment is via an 280 // approximation element. 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 // We're only interested in the case in which the underlying 309 // type is a struct. (Interfaces and pointers are safe to copy.) 310 styp, ok := typ.Underlying().(*types.Struct) 311 if !ok { 312 return nil 313 } 314 315 // We're looking for cases in which a pointer to this type 316 // is a sync.Locker, but a value is not. This differentiates 317 // embedded interfaces from embedded values. 318 if types.Implements(types.NewPointer(typ), lockerType) && !types.Implements(typ, lockerType) { 319 return []string{typ.String()} 320 } 321 322 // In go1.10, sync.noCopy did not implement Locker. 323 // (The Unlock method was added only in CL 121876.) 324 // TODO(adonovan): remove workaround when we drop go1.10. 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 // Construct a sync.Locker interface type. 346 func init() { 347 nullary := types.NewSignature(nil, nil, nil, false) // func() 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 }