GoPLS Viewer

Home|gopls/go/analysis/passes/copylock/copylock.go
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    "golang.org/x/tools/internal/typeparams"
21)
22
23const Doc = `check for locks erroneously passed by value
24
25Inadvertently copying a value containing a lock, such as sync.Mutex or
26sync.WaitGroup, may cause both copies to malfunction. Generally such
27values should be referred to through a pointer.`
28
29var Analyzer = &analysis.Analyzer{
30    Name:             "copylocks",
31    Doc:              Doc,
32    Requires:         []*analysis.Analyzer{inspect.Analyzer},
33    RunDespiteErrorstrue,
34    Run:              run,
35}
36
37func 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(passnode)
54        case *ast.FuncDecl:
55            checkCopyLocksFunc(passnode.Name.Namenode.Recvnode.Type)
56        case *ast.FuncLit:
57            checkCopyLocksFunc(pass"func"nilnode.Type)
58        case *ast.CallExpr:
59            checkCopyLocksCallExpr(passnode)
60        case *ast.AssignStmt:
61            checkCopyLocksAssign(passnode)
62        case *ast.GenDecl:
63            checkCopyLocksGenDecl(passnode)
64        case *ast.CompositeLit:
65            checkCopyLocksCompositeLit(passnode)
66        case *ast.ReturnStmt:
67            checkCopyLocksReturnStmt(passnode)
68        }
69    })
70    return nilnil
71}
72
73// checkCopyLocksAssign checks whether an assignment
74// copies a lock.
75func checkCopyLocksAssign(pass *analysis.Passas *ast.AssignStmt) {
76    for ix := range as.Rhs {
77        if path := lockPathRhs(passx); path != nil {
78            pass.ReportRangef(x"assignment copies lock value to %v: %v"analysisutil.Format(pass.Fsetas.Lhs[i]), path)
79        }
80    }
81}
82
83// checkCopyLocksGenDecl checks whether lock is copied
84// in variable declaration.
85func checkCopyLocksGenDecl(pass *analysis.Passgd *ast.GenDecl) {
86    if gd.Tok != token.VAR {
87        return
88    }
89    for _spec := range gd.Specs {
90        valueSpec := spec.(*ast.ValueSpec)
91        for ix := range valueSpec.Values {
92            if path := lockPathRhs(passx); path != nil {
93                pass.ReportRangef(x"variable declaration copies lock value to %v: %v"valueSpec.Names[i].Namepath)
94            }
95        }
96    }
97}
98
99// checkCopyLocksCompositeLit detects lock copy inside a composite literal
100func checkCopyLocksCompositeLit(pass *analysis.Passcl *ast.CompositeLit) {
101    for _x := range cl.Elts {
102        if nodeok := x.(*ast.KeyValueExpr); ok {
103            x = node.Value
104        }
105        if path := lockPathRhs(passx); path != nil {
106            pass.ReportRangef(x"literal copies lock value from %v: %v"analysisutil.Format(pass.Fsetx), path)
107        }
108    }
109}
110
111// checkCopyLocksReturnStmt detects lock copy in return statement
112func checkCopyLocksReturnStmt(pass *analysis.Passrs *ast.ReturnStmt) {
113    for _x := range rs.Results {
114        if path := lockPathRhs(passx); 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
121func checkCopyLocksCallExpr(pass *analysis.Passce *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 funok := 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(passx); path != nil {
137            pass.ReportRangef(x"call of %s copies lock value: %v"analysisutil.Format(pass.Fsetce.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.
146func checkCopyLocksFunc(pass *analysis.Passname stringrecv *ast.FieldListtyp *ast.FuncType) {
147    if recv != nil && len(recv.List) > 0 {
148        expr := recv.List[0].Type
149        if path := lockPath(pass.Pkgpass.TypesInfo.Types[expr].Typenil); path != nil {
150            pass.ReportRangef(expr"%s passes lock by value: %v"namepath)
151        }
152    }
153
154    if typ.Params != nil {
155        for _field := range typ.Params.List {
156            expr := field.Type
157            if path := lockPath(pass.Pkgpass.TypesInfo.Types[expr].Typenil); path != nil {
158                pass.ReportRangef(expr"%s passes lock by value: %v"namepath)
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.
172func checkCopyLocksRange(pass *analysis.Passr *ast.RangeStmt) {
173    checkCopyLocksRangeVar(passr.Tokr.Key)
174    checkCopyLocksRangeVar(passr.Tokr.Value)
175}
176
177func checkCopyLocksRangeVar(pass *analysis.Passrtok token.Tokene ast.Expr) {
178    if e == nil {
179        return
180    }
181    idisId := 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.Pkgtypnil); path != nil {
204        pass.Reportf(e.Pos(), "range var %s copies lock: %v"analysisutil.Format(pass.Fsete), path)
205    }
206}
207
208type typePath []string
209
210// String pretty-prints a typePath.
211func (path typePathString() 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(&bufpath[n-i-1])
220    }
221    return buf.String()
222}
223
224func lockPathRhs(pass *analysis.Passx ast.ExprtypePath {
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 starok := 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.Pkgpass.TypesInfo.Types[x].Typenil)
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.
246func lockPath(tpkg *types.Packagetyp types.TypeseenTParams map[*typeparams.TypeParam]booltypePath {
247    if typ == nil {
248        return nil
249    }
250
251    if tparok := 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        termserr := typeparams.StructuralTerms(tpar)
262        if err != nil {
263            return nil // invalid type
264        }
265        for _term := range terms {
266            subpath := lockPath(tpkgterm.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(subpathtyp.String())
284            }
285        }
286        return nil
287    }
288
289    for {
290        atypok := typ.Underlying().(*types.Array)
291        if !ok {
292            break
293        }
294        typ = atyp.Elem()
295    }
296
297    ttypok := typ.Underlying().(*types.Tuple)
298    if ok {
299        for i := 0i < ttyp.Len(); i++ {
300            subpath := lockPath(tpkgttyp.At(i).Type(), seenTParams)
301            if subpath != nil {
302                return append(subpathtyp.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    stypok := 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(typlockerType) {
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 namedok := 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 := 0i < nfieldsi++ {
333        ftyp := styp.Field(i).Type()
334        subpath := lockPath(tpkgftypseenTParams)
335        if subpath != nil {
336            return append(subpathtyp.String())
337        }
338    }
339
340    return nil
341}
342
343var lockerType *types.Interface
344
345// Construct a sync.Locker interface type.
346func init() {
347    nullary := types.NewSignature(nilnilnilfalse// func()
348    methods := []*types.Func{
349        types.NewFunc(token.NoPosnil"Lock"nullary),
350        types.NewFunc(token.NoPosnil"Unlock"nullary),
351    }
352    lockerType = types.NewInterface(methodsnil).Complete()
353}
354
MembersX
checkCopyLocksCallExpr.pass
checkCopyLocksFunc.recv
typePath.String.path
lockPath.BlockStmt.RangeStmt_7526.term
lockPath.BlockStmt.BlockStmt.subpath
checkCopyLocksCompositeLit
checkCopyLocksFunc.pass
typePath.String.RangeStmt_6111.i
lockPath.nfields
analysis
run.nodeFilter
checkCopyLocksAssign.RangeStmt_2144.i
checkCopyLocksFunc
checkCopyLocksFunc.name
lockPath.seenTParams
lockPath.BlockStmt.terms
init
checkCopyLocksReturnStmt.rs
typePath.String
lockPath
lockPath.BlockStmt.ftyp
types
Doc
checkCopyLocksGenDecl.RangeStmt_2534.spec
checkCopyLocksReturnStmt.pass
checkCopyLocksFunc.typ
lockPath.typ
bytes
checkCopyLocksAssign.RangeStmt_2144.x
checkCopyLocksReturnStmt.RangeStmt_3368.x
checkCopyLocksFunc.BlockStmt.expr
checkCopyLocksRangeVar.path
fmt
analysisutil
checkCopyLocksAssign
checkCopyLocksGenDecl.RangeStmt_2534.BlockStmt.RangeStmt_2605.BlockStmt.path
checkCopyLocksCompositeLit.RangeStmt_2968.BlockStmt.path
checkCopyLocksCallExpr.RangeStmt_3954.x
checkCopyLocksFunc.BlockStmt.path
checkCopyLocksRange.pass
checkCopyLocksRangeVar
checkCopyLocksRangeVar.rtok
lockPath.BlockStmt.subpath
lockerType
run
run.pass
checkCopyLocksGenDecl.RangeStmt_2534.BlockStmt.RangeStmt_2605.i
checkCopyLocksCompositeLit.cl
typePath
ast
inspect
checkCopyLocksFunc.BlockStmt.RangeStmt_4668.BlockStmt.expr
checkCopyLocksRange
checkCopyLocksRangeVar.e
lockPath.tpkg
checkCopyLocksGenDecl.gd
checkCopyLocksGenDecl.RangeStmt_2534.BlockStmt.RangeStmt_2605.x
checkCopyLocksAssign.as
typePath.String.n
typePath.String.buf
lockPath.i
checkCopyLocksCompositeLit.pass
checkCopyLocksReturnStmt
checkCopyLocksReturnStmt.RangeStmt_3368.BlockStmt.path
checkCopyLocksFunc.BlockStmt.RangeStmt_4668.field
checkCopyLocksRangeVar.typ
lockPath.BlockStmt.i
token
checkCopyLocksRange.r
checkCopyLocksRangeVar.pass
init.nullary
inspector
checkCopyLocksGenDecl
checkCopyLocksCompositeLit.RangeStmt_2968.x
checkCopyLocksFunc.BlockStmt.RangeStmt_4668.BlockStmt.path
typeparams
checkCopyLocksGenDecl.pass
checkCopyLocksCallExpr
checkCopyLocksCallExpr.id
lockPathRhs
lockPathRhs.x
lockPath.BlockStmt.err
lockPath.BlockStmt.RangeStmt_7526.BlockStmt.subpath
checkCopyLocksAssign.pass
checkCopyLocksAssign.RangeStmt_2144.BlockStmt.path
checkCopyLocksCallExpr.ce
checkCopyLocksCallExpr.RangeStmt_3954.BlockStmt.path
lockPathRhs.pass
init.methods
Members
X