Open peterbourgon opened 5 years ago
There are two separate issues here: mutexes that aren't stored in variables, and a combination of RLock + defer Lock.
Addressing the second issue first, I don't think it is something we should fix. The likelihood of someone making this mistake seems low to me (feel free to prove me wrong, though), and it seems more likely to cause a false positive in some bizarrely written code.
Addressing the first issue, what follows is the technical explanation of why it happens, and how we'll fix it. Feel free to skip over it.
The underlying cause of the first issue is the current, SSA-based implementation of the check. We're checking for a sequence of two instructions, a call to Lock/RLock, immediately followed by a defer of the same. This does not work for mutexes that aren't stored in (SSA-registerized) variables, because additional instructions for loading the value will be emitted.
In the case of struct fields, we'd see a sequence like this:
t1 = &t.rwmtx [#0] *sync.RWMutex
t3 = (*sync.RWMutex).RLock(t1) ()
t5 = &t.rwmtx [#0] *sync.RWMutex
defer (*sync.RWMutex).RLock(t5)
while for a local variable, it'd instead look like this:
t1 = new sync.RWMutex (rwmtx) *sync.RWMutex
t2 = (*sync.RWMutex).RLock(t1) ()
defer (*sync.RWMutex).RLock(t1)
There are other possible sequences for mutexes stored in maps, slices, when using new
for allocating variables and likely at least one other scenario I'm forgetting about. Rather than handling all of these sequences, we may be much better off implementing this check by looking at the AST instead, where the call and the defer would truly be two consecutive statements¹.
It's unfortunate that we chose to use SSA for this, as it greatly diminished the value of the check for a long time. In fact, we haven't always used SSA for it. Before commit 0156b8217b6e76d483ff843f93b909483db7d6bb, it was implemented by looking at the AST. We were too eager to use SSA, and our test cases weren't exhaustive enough, or they would've caught this regression.
¹: That is, unless we're concerned about code like this:
mu.Lock()
{
defer mu.Lock()
}
Personally, I am not. Though it would be possible to handle this, too.
. . . a combination of RLock + defer Lock . . , I don't think it is something we should fix. The likelihood of someone making this mistake seems low to me (feel free to prove me wrong, though), and it seems more likely to cause a false positive in some bizarrely written code.
I'm not sure I understand, can you clarify which case(s) this refers to?
As for the rest, it follows and I'm 👍
I'm not sure I understand, can you clarify which case(s) this refers to?
I'm not surprised, because I misread the code… For completeness sake, I was thinking of the following code:
var rwmtx sync.RWMutex
rwmtx.RLock()
defer rwmtx.Lock()
but apparently you never wrote that.
I see now that you wrote this instead:
var rwmtx sync.RWMutex
rwmtx.Lock()
defer rwmtx.Lock()
which should be flagged by staticcheck, but isn't. The reason for that is another bug in the check, which only looks for RLock on RWMutex, not Lock.
Following on from this Twitter thread. Given this Go file:
staticcheck SA2003 has several false negatives.
I would expect warnings on lines 12, 14, 16, 21, 23, and 25. I only get warnings on lines 21 and 25.
(Hat tip to @athomason for tracking this down.)