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. 7package copylock 8 9import ( 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) 21 22const Doc = `check for locks erroneously passed by value 23 24Inadvertently copying a value containing a lock, such as sync.Mutex or 25sync.WaitGroup, may cause both copies to malfunction. Generally such 26values should be referred to through a pointer.` 27 28var Analyzer = &analysis.Analyzer{ 29 Name: "copylocks", 30 Doc: Doc, 31 Requires: []*analysis.Analyzer{inspect.Analyzer}, 32 RunDespiteErrors: true, 33 Run: run, 34} 35 36func run(pass *analysis.Pass) (interface{}, error) { 37 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 38 39 nodeFilter := []ast.Node{ 40 (*ast.AssignStmt)(nil), 41 (*ast.CallExpr)(nil), 42 (*ast.CompositeLit)(nil), 43 (*ast.FuncDecl)(nil), 44 (*ast.FuncLit)(nil), 45 (*ast.GenDecl)(nil), 46 (*ast.RangeStmt)(nil), 47 (*ast.ReturnStmt)(nil), 48 } 49 inspect.Preorder(nodeFilter, func(node ast.Node) { 50 switch node := node.(type) { 51 case *ast.RangeStmt: 52 checkCopyLocksRange(pass, node) 53 case *ast.FuncDecl: 54 checkCopyLocksFunc(pass, node.Name.Name, node.Recv, node.Type) 55 case *ast.FuncLit: 56 checkCopyLocksFunc(pass, "func", nil, node.Type) 57 case *ast.CallExpr: 58 checkCopyLocksCallExpr(pass, node) 59 case *ast.AssignStmt: 60 checkCopyLocksAssign(pass, node) 61 case *ast.GenDecl: 62 checkCopyLocksGenDecl(pass, node) 63 case *ast.CompositeLit: 64 checkCopyLocksCompositeLit(pass, node) 65 case *ast.ReturnStmt: 66 checkCopyLocksReturnStmt(pass, node) 67 } 68 }) 69 return nil, nil 70} 71 72// checkCopyLocksAssign checks whether an assignment 73// copies a lock. 74func checkCopyLocksAssign(pass *analysis.Pass, as *ast.AssignStmt) { 75 for i, x := range as.Rhs { 76 if path := lockPathRhs(pass, x); path != nil { 77 pass.ReportRangef(x, "assignment copies lock value to %v: %v", analysisutil.Format(pass.Fset, as.Lhs[i]), path) 78 } 79 } 80} 81 82// checkCopyLocksGenDecl checks whether lock is copied 83// in variable declaration. 84func checkCopyLocksGenDecl(pass *analysis.Pass, gd *ast.GenDecl) { 85 if gd.Tok != token.VAR { 86 return 87 } 88 for _, spec := range gd.Specs { 89 valueSpec := spec.(*ast.ValueSpec) 90 for i, x := range valueSpec.Values { 91 if path := lockPathRhs(pass, x); path != nil { 92 pass.ReportRangef(x, "variable declaration copies lock value to %v: %v", valueSpec.Names[i].Name, path) 93 } 94 } 95 } 96} 97 98// checkCopyLocksCompositeLit detects lock copy inside a composite literal 99func checkCopyLocksCompositeLit(pass *analysis.Pass, cl *ast.CompositeLit) { 100 for _, x := range cl.Elts { 101 if node, ok := x.(*ast.KeyValueExpr); ok { 102 x = node.Value 103 } 104 if path := lockPathRhs(pass, x); path != nil { 105 pass.ReportRangef(x, "literal copies lock value from %v: %v", analysisutil.Format(pass.Fset, x), path) 106 } 107 } 108} 109 110// checkCopyLocksReturnStmt detects lock copy in return statement 111func checkCopyLocksReturnStmt(pass *analysis.Pass, rs *ast.ReturnStmt) { 112 for _, x := range rs.Results { 113 if path := lockPathRhs(pass, x); path != nil { 114 pass.ReportRangef(x, "return copies lock value: %v", path) 115 } 116 } 117} 118 119// checkCopyLocksCallExpr detects lock copy in the arguments to a function call 120func checkCopyLocksCallExpr(pass *analysis.Pass, ce *ast.CallExpr) { 121 var id *ast.Ident 122 switch fun := ce.Fun.(type) { 123 case *ast.Ident: 124 id = fun 125 case *ast.SelectorExpr: 126 id = fun.Sel 127 } 128 if fun, ok := pass.TypesInfo.Uses[id].(*types.Builtin); ok { 129 switch fun.Name() { 130 case "new", "len", "cap", "Sizeof": 131 return 132 } 133 } 134 for _, x := range ce.Args { 135 if path := lockPathRhs(pass, x); path != nil { 136 pass.ReportRangef(x, "call of %s copies lock value: %v", analysisutil.Format(pass.Fset, ce.Fun), path) 137 } 138 } 139} 140 141// checkCopyLocksFunc checks whether a function might 142// inadvertently copy a lock, by checking whether 143// its receiver, parameters, or return values 144// are locks. 145func checkCopyLocksFunc(pass *analysis.Pass, name string, recv *ast.FieldList, typ *ast.FuncType) { 146 if recv != nil && len(recv.List) > 0 { 147 expr := recv.List[0].Type 148 if path := lockPath(pass.Pkg, pass.TypesInfo.Types[expr].Type); path != nil { 149 pass.ReportRangef(expr, "%s passes lock by value: %v", name, path) 150 } 151 } 152 153 if typ.Params != nil { 154 for _, field := range typ.Params.List { 155 expr := field.Type 156 if path := lockPath(pass.Pkg, pass.TypesInfo.Types[expr].Type); path != nil { 157 pass.ReportRangef(expr, "%s passes lock by value: %v", name, path) 158 } 159 } 160 } 161 162 // Don't check typ.Results. If T has a Lock field it's OK to write 163 // return T{} 164 // because that is returning the zero value. Leave result checking 165 // to the return statement. 166} 167 168// checkCopyLocksRange checks whether a range statement 169// might inadvertently copy a lock by checking whether 170// any of the range variables are locks. 171func checkCopyLocksRange(pass *analysis.Pass, r *ast.RangeStmt) { 172 checkCopyLocksRangeVar(pass, r.Tok, r.Key) 173 checkCopyLocksRangeVar(pass, r.Tok, r.Value) 174} 175 176func checkCopyLocksRangeVar(pass *analysis.Pass, rtok token.Token, e ast.Expr) { 177 if e == nil { 178 return 179 } 180 id, isId := e.(*ast.Ident) 181 if isId && id.Name == "_" { 182 return 183 } 184 185 var typ types.Type 186 if rtok == token.DEFINE { 187 if !isId { 188 return 189 } 190 obj := pass.TypesInfo.Defs[id] 191 if obj == nil { 192 return 193 } 194 typ = obj.Type() 195 } else { 196 typ = pass.TypesInfo.Types[e].Type 197 } 198 199 if typ == nil { 200 return 201 } 202 if path := lockPath(pass.Pkg, typ); path != nil { 203 pass.Reportf(e.Pos(), "range var %s copies lock: %v", analysisutil.Format(pass.Fset, e), path) 204 } 205} 206 207type typePath []types.Type 208 209// String pretty-prints a typePath. 210func (path typePath) String() string { 211 n := len(path) 212 var buf bytes.Buffer 213 for i := range path { 214 if i > 0 { 215 fmt.Fprint(&buf, " contains ") 216 } 217 // The human-readable path is in reverse order, outermost to innermost. 218 fmt.Fprint(&buf, path[n-i-1].String()) 219 } 220 return buf.String() 221} 222 223func lockPathRhs(pass *analysis.Pass, x ast.Expr) typePath { 224 if _, ok := x.(*ast.CompositeLit); ok { 225 return nil 226 } 227 if _, ok := x.(*ast.CallExpr); ok { 228 // A call may return a zero value. 229 return nil 230 } 231 if star, ok := x.(*ast.StarExpr); ok { 232 if _, ok := star.X.(*ast.CallExpr); ok { 233 // A call may return a pointer to a zero value. 234 return nil 235 } 236 } 237 return lockPath(pass.Pkg, pass.TypesInfo.Types[x].Type) 238} 239 240// lockPath returns a typePath describing the location of a lock value 241// contained in typ. If there is no contained lock, it returns nil. 242func lockPath(tpkg *types.Package, typ types.Type) typePath { 243 if typ == nil { 244 return nil 245 } 246 247 for { 248 atyp, ok := typ.Underlying().(*types.Array) 249 if !ok { 250 break 251 } 252 typ = atyp.Elem() 253 } 254 255 // We're only interested in the case in which the underlying 256 // type is a struct. (Interfaces and pointers are safe to copy.) 257 styp, ok := typ.Underlying().(*types.Struct) 258 if !ok { 259 return nil 260 } 261 262 // We're looking for cases in which a pointer to this type 263 // is a sync.Locker, but a value is not. This differentiates 264 // embedded interfaces from embedded values. 265 if types.Implements(types.NewPointer(typ), lockerType) && !types.Implements(typ, lockerType) { 266 return []types.Type{typ} 267 } 268 269 // In go1.10, sync.noCopy did not implement Locker. 270 // (The Unlock method was added only in CL 121876.) 271 // TODO(adonovan): remove workaround when we drop go1.10. 272 if named, ok := typ.(*types.Named); ok && 273 named.Obj().Name() == "noCopy" && 274 named.Obj().Pkg().Path() == "sync" { 275 return []types.Type{typ} 276 } 277 278 nfields := styp.NumFields() 279 for i := 0; i < nfields; i++ { 280 ftyp := styp.Field(i).Type() 281 subpath := lockPath(tpkg, ftyp) 282 if subpath != nil { 283 return append(subpath, typ) 284 } 285 } 286 287 return nil 288} 289 290var lockerType *types.Interface 291 292// Construct a sync.Locker interface type. 293func init() { 294 nullary := types.NewSignature(nil, nil, nil, false) // func() 295 methods := []*types.Func{ 296 types.NewFunc(token.NoPos, nil, "Lock", nullary), 297 types.NewFunc(token.NoPos, nil, "Unlock", nullary), 298 } 299 lockerType = types.NewInterface(methods, nil).Complete() 300} 301