Commit 1374515a authored by Aliaksandr Valialkin's avatar Aliaksandr Valialkin Committed by Rob Pike

cmd/vet: check lock copy in function calls and return statements

Fixes #14529

Change-Id: I6ed059d279ba0fe12d76416859659f28d61781d2
Reviewed-on: https://go-review.googlesource.com/20832
Run-TryBot: Rob Pike <r@golang.org>
Reviewed-by: default avatarRob Pike <r@golang.org>
parent 49da9312
...@@ -18,7 +18,7 @@ func init() { ...@@ -18,7 +18,7 @@ func init() {
register("copylocks", register("copylocks",
"check that locks are not passed by value", "check that locks are not passed by value",
checkCopyLocks, checkCopyLocks,
funcDecl, rangeStmt, funcLit, assignStmt, genDecl, compositeLit) funcDecl, rangeStmt, funcLit, callExpr, assignStmt, genDecl, compositeLit, returnStmt)
} }
// checkCopyLocks checks whether node might // checkCopyLocks checks whether node might
...@@ -31,12 +31,16 @@ func checkCopyLocks(f *File, node ast.Node) { ...@@ -31,12 +31,16 @@ func checkCopyLocks(f *File, node ast.Node) {
checkCopyLocksFunc(f, node.Name.Name, node.Recv, node.Type) checkCopyLocksFunc(f, node.Name.Name, node.Recv, node.Type)
case *ast.FuncLit: case *ast.FuncLit:
checkCopyLocksFunc(f, "func", nil, node.Type) checkCopyLocksFunc(f, "func", nil, node.Type)
case *ast.CallExpr:
checkCopyLocksCallExpr(f, node)
case *ast.AssignStmt: case *ast.AssignStmt:
checkCopyLocksAssign(f, node) checkCopyLocksAssign(f, node)
case *ast.GenDecl: case *ast.GenDecl:
checkCopyLocksGenDecl(f, node) checkCopyLocksGenDecl(f, node)
case *ast.CompositeLit: case *ast.CompositeLit:
checkCopyCompositeLit(f, node) checkCopyLocksCompositeLit(f, node)
case *ast.ReturnStmt:
checkCopyLocksReturnStmt(f, node)
} }
} }
...@@ -66,8 +70,8 @@ func checkCopyLocksGenDecl(f *File, gd *ast.GenDecl) { ...@@ -66,8 +70,8 @@ func checkCopyLocksGenDecl(f *File, gd *ast.GenDecl) {
} }
} }
// checkCopyCompositeLit detects lock copy inside a composite literal // checkCopyLocksCompositeLit detects lock copy inside a composite literal
func checkCopyCompositeLit(f *File, cl *ast.CompositeLit) { func checkCopyLocksCompositeLit(f *File, cl *ast.CompositeLit) {
for _, x := range cl.Elts { for _, x := range cl.Elts {
if node, ok := x.(*ast.KeyValueExpr); ok { if node, ok := x.(*ast.KeyValueExpr); ok {
x = node.Value x = node.Value
...@@ -78,6 +82,24 @@ func checkCopyCompositeLit(f *File, cl *ast.CompositeLit) { ...@@ -78,6 +82,24 @@ func checkCopyCompositeLit(f *File, cl *ast.CompositeLit) {
} }
} }
// checkCopyLocksReturnStmt detects lock copy in return statement
func checkCopyLocksReturnStmt(f *File, rs *ast.ReturnStmt) {
for _, x := range rs.Results {
if path := lockPathRhs(f, x); path != nil {
f.Badf(x.Pos(), "return copies lock value: %v", path)
}
}
}
// checkCopyLocksCallExpr detects lock copy in function call
func checkCopyLocksCallExpr(f *File, ce *ast.CallExpr) {
for _, x := range ce.Args {
if path := lockPathRhs(f, x); path != nil {
f.Badf(x.Pos(), "function call copies lock value: %v", path)
}
}
}
// checkCopyLocksFunc checks whether a function might // checkCopyLocksFunc checks whether a function might
// inadvertently copy a lock, by checking whether // inadvertently copy a lock, by checking whether
// its receiver, parameters, or return values // its receiver, parameters, or return values
......
...@@ -139,6 +139,7 @@ var ( ...@@ -139,6 +139,7 @@ var (
genDecl *ast.GenDecl genDecl *ast.GenDecl
interfaceType *ast.InterfaceType interfaceType *ast.InterfaceType
rangeStmt *ast.RangeStmt rangeStmt *ast.RangeStmt
returnStmt *ast.ReturnStmt
// checkers is a two-level map. // checkers is a two-level map.
// The outer level is keyed by a nil pointer, one of the AST vars above. // The outer level is keyed by a nil pointer, one of the AST vars above.
...@@ -476,6 +477,8 @@ func (f *File) Visit(node ast.Node) ast.Visitor { ...@@ -476,6 +477,8 @@ func (f *File) Visit(node ast.Node) ast.Visitor {
key = interfaceType key = interfaceType
case *ast.RangeStmt: case *ast.RangeStmt:
key = rangeStmt key = rangeStmt
case *ast.ReturnStmt:
key = returnStmt
} }
for _, fn := range f.checkers[key] { for _, fn := range f.checkers[key] {
fn(f, node) fn(f, node)
......
...@@ -78,6 +78,35 @@ func (*CustomLock) Unlock() {} ...@@ -78,6 +78,35 @@ func (*CustomLock) Unlock() {}
func Ok(*CustomLock) {} func Ok(*CustomLock) {}
func Bad(CustomLock) {} // ERROR "Bad passes lock by value: testdata.CustomLock" func Bad(CustomLock) {} // ERROR "Bad passes lock by value: testdata.CustomLock"
// Passing lock values into interface function arguments
func FuncCallInterfaceArg(f func(a int, b interface{})) {
var m sync.Mutex
var t struct{ lock sync.Mutex }
f(1, "foo")
f(2, &t)
f(3, &sync.Mutex{})
f(4, m) // ERROR "function call copies lock value: sync.Mutex"
f(5, t) // ERROR "function call copies lock value: struct{lock sync.Mutex} contains sync.Mutex"
}
// Returning lock via interface value
func ReturnViaInterface(x int) (int, interface{}) {
var m sync.Mutex
var t struct{ lock sync.Mutex }
switch x % 4 {
case 0:
return 0, "qwe"
case 1:
return 1, &sync.Mutex{}
case 2:
return 2, m // ERROR "return copies lock value: sync.Mutex"
default:
return 3, t // ERROR "return copies lock value: struct{lock sync.Mutex} contains sync.Mutex"
}
}
// TODO: Unfortunate cases // TODO: Unfortunate cases
// Non-ideal error message: // Non-ideal error message:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment