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.
5 // This file contains the code to check that locks are not passed by value.
19 "check that locks are not passed by value",
21 funcDecl
, rangeStmt
, funcLit
, callExpr
, assignStmt
, genDecl
, compositeLit
, returnStmt
)
24 // checkCopyLocks checks whether node might
25 // inadvertently copy a lock.
26 func checkCopyLocks(f
*File
, node ast
.Node
) {
27 switch node
:= node
.(type) {
29 checkCopyLocksRange(f
, node
)
31 checkCopyLocksFunc(f
, node
.Name
.Name
, node
.Recv
, node
.Type
)
33 checkCopyLocksFunc(f
, "func", nil, node
.Type
)
35 checkCopyLocksCallExpr(f
, node
)
37 checkCopyLocksAssign(f
, node
)
39 checkCopyLocksGenDecl(f
, node
)
40 case *ast
.CompositeLit
:
41 checkCopyLocksCompositeLit(f
, node
)
43 checkCopyLocksReturnStmt(f
, node
)
47 // checkCopyLocksAssign checks whether an assignment
49 func checkCopyLocksAssign(f
*File
, as
*ast
.AssignStmt
) {
50 for i
, x
:= range as
.Rhs
{
51 if path
:= lockPathRhs(f
, x
); path
!= nil {
52 f
.Badf(x
.Pos(), "assignment copies lock value to %v: %v", f
.gofmt(as
.Lhs
[i
]), path
)
57 // checkCopyLocksGenDecl checks whether lock is copied
58 // in variable declaration.
59 func checkCopyLocksGenDecl(f
*File
, gd
*ast
.GenDecl
) {
60 if gd
.Tok
!= token
.VAR
{
63 for _
, spec
:= range gd
.Specs
{
64 valueSpec
:= spec
.(*ast
.ValueSpec
)
65 for i
, x
:= range valueSpec
.Values
{
66 if path
:= lockPathRhs(f
, x
); path
!= nil {
67 f
.Badf(x
.Pos(), "variable declaration copies lock value to %v: %v", valueSpec
.Names
[i
].Name
, path
)
73 // checkCopyLocksCompositeLit detects lock copy inside a composite literal
74 func checkCopyLocksCompositeLit(f
*File
, cl
*ast
.CompositeLit
) {
75 for _
, x
:= range cl
.Elts
{
76 if node
, ok
:= x
.(*ast
.KeyValueExpr
); ok
{
79 if path
:= lockPathRhs(f
, x
); path
!= nil {
80 f
.Badf(x
.Pos(), "literal copies lock value from %v: %v", f
.gofmt(x
), path
)
85 // checkCopyLocksReturnStmt detects lock copy in return statement
86 func checkCopyLocksReturnStmt(f
*File
, rs
*ast
.ReturnStmt
) {
87 for _
, x
:= range rs
.Results
{
88 if path
:= lockPathRhs(f
, x
); path
!= nil {
89 f
.Badf(x
.Pos(), "return copies lock value: %v", path
)
94 // checkCopyLocksCallExpr detects lock copy in the arguments to a function call
95 func checkCopyLocksCallExpr(f
*File
, ce
*ast
.CallExpr
) {
97 switch fun
:= ce
.Fun
.(type) {
100 case *ast
.SelectorExpr
:
103 if fun
, ok
:= f
.pkg
.uses
[id
].(*types
.Builtin
); ok
{
105 case "new", "len", "cap", "Sizeof":
109 for _
, x
:= range ce
.Args
{
110 if path
:= lockPathRhs(f
, x
); path
!= nil {
111 f
.Badf(x
.Pos(), "call of %s copies lock value: %v", f
.gofmt(ce
.Fun
), path
)
116 // checkCopyLocksFunc checks whether a function might
117 // inadvertently copy a lock, by checking whether
118 // its receiver, parameters, or return values
120 func checkCopyLocksFunc(f
*File
, name
string, recv
*ast
.FieldList
, typ
*ast
.FuncType
) {
121 if recv
!= nil && len(recv
.List
) > 0 {
122 expr
:= recv
.List
[0].Type
123 if path
:= lockPath(f
.pkg
.typesPkg
, f
.pkg
.types
[expr
].Type
); path
!= nil {
124 f
.Badf(expr
.Pos(), "%s passes lock by value: %v", name
, path
)
128 if typ
.Params
!= nil {
129 for _
, field
:= range typ
.Params
.List
{
131 if path
:= lockPath(f
.pkg
.typesPkg
, f
.pkg
.types
[expr
].Type
); path
!= nil {
132 f
.Badf(expr
.Pos(), "%s passes lock by value: %v", name
, path
)
137 // Don't check typ.Results. If T has a Lock field it's OK to write
139 // because that is returning the zero value. Leave result checking
140 // to the return statement.
143 // checkCopyLocksRange checks whether a range statement
144 // might inadvertently copy a lock by checking whether
145 // any of the range variables are locks.
146 func checkCopyLocksRange(f
*File
, r
*ast
.RangeStmt
) {
147 checkCopyLocksRangeVar(f
, r
.Tok
, r
.Key
)
148 checkCopyLocksRangeVar(f
, r
.Tok
, r
.Value
)
151 func checkCopyLocksRangeVar(f
*File
, rtok token
.Token
, e ast
.Expr
) {
155 id
, isId
:= e
.(*ast
.Ident
)
156 if isId
&& id
.Name
== "_" {
161 if rtok
== token
.DEFINE
{
165 obj
:= f
.pkg
.defs
[id
]
171 typ
= f
.pkg
.types
[e
].Type
177 if path
:= lockPath(f
.pkg
.typesPkg
, typ
); path
!= nil {
178 f
.Badf(e
.Pos(), "range var %s copies lock: %v", f
.gofmt(e
), path
)
182 type typePath
[]types
.Type
184 // String pretty-prints a typePath.
185 func (path typePath
) String() string {
188 for i
:= range path
{
190 fmt
.Fprint(&buf
, " contains ")
192 // The human-readable path is in reverse order, outermost to innermost.
193 fmt
.Fprint(&buf
, path
[n
-i
-1].String())
198 func lockPathRhs(f
*File
, x ast
.Expr
) typePath
{
199 if _
, ok
:= x
.(*ast
.CompositeLit
); ok
{
202 if _
, ok
:= x
.(*ast
.CallExpr
); ok
{
203 // A call may return a zero value.
206 if star
, ok
:= x
.(*ast
.StarExpr
); ok
{
207 if _
, ok
:= star
.X
.(*ast
.CallExpr
); ok
{
208 // A call may return a pointer to a zero value.
212 return lockPath(f
.pkg
.typesPkg
, f
.pkg
.types
[x
].Type
)
215 // lockPath returns a typePath describing the location of a lock value
216 // contained in typ. If there is no contained lock, it returns nil.
217 func lockPath(tpkg
*types
.Package
, typ types
.Type
) typePath
{
223 atyp
, ok
:= typ
.Underlying().(*types
.Array
)
230 // We're only interested in the case in which the underlying
231 // type is a struct. (Interfaces and pointers are safe to copy.)
232 styp
, ok
:= typ
.Underlying().(*types
.Struct
)
237 // We're looking for cases in which a reference to this type
238 // can be locked, but a value cannot. This differentiates
239 // embedded interfaces from embedded values.
240 if plock
:= types
.NewMethodSet(types
.NewPointer(typ
)).Lookup(tpkg
, "Lock"); plock
!= nil {
241 if lock
:= types
.NewMethodSet(typ
).Lookup(tpkg
, "Lock"); lock
== nil {
242 return []types
.Type
{typ
}
246 nfields
:= styp
.NumFields()
247 for i
:= 0; i
< nfields
; i
++ {
248 ftyp
:= styp
.Field(i
).Type()
249 subpath
:= lockPath(tpkg
, ftyp
)
251 return append(subpath
, typ
)