-
Notifications
You must be signed in to change notification settings - Fork 36.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Clang lock debug #6287
Clang lock debug #6287
Conversation
…acros This allows us to use function/variable/class attributes to specify locking requisites, allowing problems to be detected during static analysis. This works perfectly with newer Clang versions (tested with 3.3-3.7). For older versions (tested 3.2), it compiles fine but spews lots of false-positives.
- rpcwallet: No need to lock twice here - openssl: Clang doesn't understand selective lock/unlock here. Ignore it. - CNode: Fix a legitimate (though very unlikely) locking bug.
This was chosen not because it's necessarily helpful, but because its locking assumptions were already correct.
This looks very nice and it's great to be able to use such static analyzers with our codebase. Looking forward to see what issues can be uncovered with this. |
utACK |
Tested building with -Wthread-safety. Warning is generated as expected when commenting out the LOCK() in FinalizeNode()
|
The other warnings fixed by 2b890dd
|
Concept ACK, still need to review in detail. |
@laanwj There are a few details that aren't quite right, but I didn't want to change too much in this PR until there was some interest. I can take a stab at fixing them up before/after pull, whichever you'd prefer:
For examples of real usage, see my play tree here: https://github.com/theuni/bitcoin/commits/clang-lock-debug-more. It fleshed out several missing locks already. |
Could this be an issue if a lock is held multiple times along one code path, but only once along another code path? If you then remove the locking (according to clang's warning) in one place it may result in the lock not held anymore at all in one of the cases. |
@laanwj I've looked deeper into this, here's a good sample of some snags we hit: static RecursiveMutex cs_main;
static int myint = 3;
static std::vector<int*> GUARDED_BY(cs_main) vec(1,&myint);
int* func2()
{
LOCK(cs_main);
return vec[0];
}
void func1() EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
func2();
vec.clear();
}
int main()
{
int* unguarded = func2();
*unguarded = 0;
LOCK(cs_main);
func1();
unguarded = func2();
return unguarded;
} Things clang doesn't complain about:
Its analysis seems to be entirely within the scope of each function. If you don't lock in func2, it complains about accessing vec, because it doesn't know that it should already be locked. You can tell it that it should assume cs_main is locked by specifying EXCLUSIVE_LOCKS_REQUIRED(cs_main), but then it complains about locking twice. After going through and marking a few functions/variables, it becomes very clear how it all fits together. Since our mutexes are recursive anyway, the ignored double-locks aren't a problem anyway. If you really want to prevent double-locks (treat as a non-recursive mutex) you can notate the function with EXCLUSIVE_LOCKS_REQUIRED(!cs_main). However, it does point out functions where we needlessly lock twice. rpcwallet in a794284 is a good example of that. If you have 2 scoped locks in the same function, or if you specify that a lock is required then lock again, it'll warn. So to answer your question: if you remove a lock because it's locked twice in one path, and that leaves another path exposed, it'll warn about the new exposed path. The basic work-flow is:
So the big question is: For this to be useful, tons of functions need to be notated. Would you be ok with that? I'm happy to do the work (i've already done much of it locally), but I'm afraid that it will get pretty noisy. For what it's worth, once the current issues are cleaned up, travis could be set to fail when new problems are detected. |
I vote for noisy annotations-- locking bugs are NASTY. |
For anyone wanting to try out the semantics, here's an easy test that simulates our environment and conventions: https://gist.github.com/theuni/f5d6d3db9434ca546422. |
Is this ready for merging? If not, can we get a useful subset in? |
Yes, this is useful as a starting point. |
Bitcoin 0.12 test PRs 1 Cherry-picked from the following upstream PRs: - bitcoin/bitcoin#6337 - bitcoin/bitcoin#5881 - bitcoin/bitcoin#6365 - bitcoin/bitcoin#6390 - bitcoin/bitcoin#6414 - bitcoin/bitcoin#5515 - bitcoin/bitcoin#6287 (partial, remainder included in bitcoin/bitcoin#6703) - bitcoin/bitcoin#6465 Part of #2074.
Bitcoin 0.12 test PRs 1 Cherry-picked from the following upstream PRs: - bitcoin/bitcoin#6337 - bitcoin/bitcoin#5881 - bitcoin/bitcoin@3f16971 - bitcoin/bitcoin#6365 - bitcoin/bitcoin@56dc704 - bitcoin/bitcoin#6390 - bitcoin/bitcoin#6414 - bitcoin/bitcoin#5515 - bitcoin/bitcoin#6287 (partial, remainder included in bitcoin/bitcoin#6703) - bitcoin/bitcoin#6465 Part of #2074.
Bitcoin 0.12 test PRs 1 Cherry-picked from the following upstream PRs: - bitcoin/bitcoin#6337 - bitcoin/bitcoin#6390 - bitcoin/bitcoin#5515 - bitcoin/bitcoin#6287 (partial, remainder included in bitcoin/bitcoin#6703) - bitcoin/bitcoin#6465 Part of #2074.
This is part of a much larger networking refactor.
After spending a while looking at different approaches for cleaning up the networking code, it's become apparent that some functions/classes are going to have to drop cs_main in favor of using their own fine-grained locks.
Breaking up cs_main would be a monstrous task, and would likely be even harder to review.
As a first step, I've fixed up our locking so that LOCK() and friends play nicely with clang's -Wthread-safety static-analysis option. This will allow us to document locking assumptions while verifying them at the same time. I added a794284 as an example of how this is useful.
My goal is to add the EXCLUSIVE_LOCKS_REQUIRED(cs_main)/GUARDED_BY(cs_main) as needed for functions that need to be broken out of the cs_main lock, that way locking changes can be verified much more easily.
To test:
./configure CXXFLAGS="-O2 -g -Wthread-safety" CXX=clang++ CC=clang
To see an example of a failure in action, comment out
LOCK(cs_main);
inFinalizeNode()
. The result should be something like: