Suppose the program uses mutex for synchronization:

package main import "sync" type T struct {

lock sync.Mutex

} func (t *T) Lock() {

t.lock.Lock()

} func (t T) Unlock() {

t.lock.Unlock()

} func main() {

t := T{lock: sync.Mutex{}}

t.Lock()

t.Unlock()

t.Lock()

}

If v is addressable and &v’s method set contains m, v.m() is shorthand for (&v).m()

Think for a moment what might be the result of running what is implemented above…

Program falls into a deadlock:

fatal error: all goroutines are asleep — deadlock! goroutine 1 [semacquire]:

sync.runtime_Semacquire(0x4201162ac)

/usr/local/go/src/runtime/sema.go:47 +0x30

sync.(*Mutex).Lock(0x4201162a8)

/usr/local/go/src/sync/mutex.go:85 +0xd0

main.(*T).Lock(0x4201162a8)

...

It’s not good and the root cause is passing receiver by value to Unlock method so t.lock.Unlock() is actually called on a copy of the lock. It’s very easy to overlook, especially in bigger programs. It isn’t detected by the compiler since this might be an intention of the programmer. This is where vet steps in…

> go tool vet vet.go

vet.go:13: Unlock passes lock by value: main.T

Option copylocks (enabled by default) checks if passed by value is something of a type having Lock method with pointer receiver. If this is the case then it throws a warning.

Example use of this mechanism is in the sync package itself. There is a special type named noCopy. To protect a type from copying by value (actually make it detectable by the vet tool), single field needs to be added to a struct like for WaitGroup:

package main import "sync" type T struct {

wg sync.WaitGroup

} func fun(T) {} func main() {

t := T{sync.WaitGroup{}}

fun(t)

} > go tool vet lab.go

lab.go:9: fun passes lock by value: main.T contains sync.WaitGroup contains sync.noCopy

lab.go:13: function call copies lock value: main.T contains sync.WaitGroup contains sync.noCopy

Under the hood

Sources are placed in /src/cmd/vet. Every option for vet registers itself using register function which takes (among others) a variadic parameter of types of AST nodes that option is interested in and a callback. That callback function will be fired for every node of specified types. For copylocks nodes to investigate are i.e. return statements. Ultimately it all goes to lockPath which verifies if passed value is of type which has a pointer receiver method named Lock. During the whole process go/ast package is used extensively. A gentle introduction to that package can be found in Go’s Testable Examples under the hood.