Re-purpose this revision to make negative capability typeless for clang's thread safety analysis. This is a larger effort so it will take a little bit more time on me.
Diff Detail
- Repository
- rL LLVM
Event Timeline
clang/lib/Analysis/ThreadSafety.cpp | ||
---|---|---|
2219–2221 | The double check of LDat1->kind() == LK_Generic is fishy to me. Particularly the case where LDat1->kind() == LK_Generic is false but LDat2->kind() == LK_Generic is true. This might be clearer as: if (LDat2->kind() == LK_Generic) continue; else if (LDat1->kind() == LK_Generic && Modify) *Iter1 = Fact; else { ... Or is there something else to this logic I'm missing? | |
clang/test/SemaCXX/thread-safety-annotations.h | ||
47 | Is this test suite run with other compilers? If not, I think we can remove the case? |
What distinguishes a shared from an exclusive negative capability? Negative capabilities (as I understand them) express the mutex not being held at all, meaning neither in shared nor in exclusive mode.
clang/lib/Analysis/ThreadSafety.cpp | ||
---|---|---|
2188–2190 | What do these lines mean? That we accept if a lock is shared in one branch and exclusive in the other, and that we make it exclusive after the merge point? | |
clang/test/SemaCXX/warn-thread-safety-negative.cpp | ||
135–140 | Why would I want these warnings here? This code seems fine to me. However, I don't see why we don't get acquiring mutex 'mu' requires negative capability '!mu' at line 138, or does that disappear because of the assertion? |
clang/lib/Analysis/ThreadSafety.cpp | ||
---|---|---|
2188–2190 | Yes. If at CFG merge point, one path holds type1 lock and the other path holds type2 lock. We do a type1 + type2 = merged_type and issue warnings if we are doing shared + exclusive merge. | |
2219–2221 | I think your suggestion is to refactor the if statements. What I am thinking is that there are two cases.
My if statement is trying express that. I am afraid the refactoring is going to lose the logic (as stated in my comment above). | |
clang/test/SemaCXX/thread-safety-annotations.h | ||
47 | Yeah, you are right. I just copied the header definitions from clang thread safety analysis doc. | |
clang/test/SemaCXX/warn-thread-safety-negative.cpp | ||
135–140 | Showing acquiring mutex 'mu' requires negative capability '!mu' is not in the scope of this patch. Please notice thread safety analysis is still under development. The reason is that, in one path we have ASSERT_SHARED_CAPABILITY(!mu), and in the other path we have RELEASE(mu). The assertion leads to negative shared capability but the release leads to negative exclusive capability. A merge of the two capabilities (merging "I am not trying to read" versus "I am not trying to write") leads to a warning. Without my patch, clang will issue a warning for the merge point in test1() but not the merge point in test2(). |
clang/test/SemaCXX/thread-safety-annotations.h | ||
---|---|---|
47 | Why aren't you using the existing macros? The idea was to run the tests with both old and new terminology, and for the time being, I think we should maintain both. | |
clang/test/SemaCXX/warn-thread-safety-negative.cpp | ||
135–140 | But ASSERT_SHARED_CAPABILITY(!mu) implies that we also don't have an exclusive lock (an exclusive lock is stronger than a shared lock), and RELEASE(mu) without ACQUIRE_SHARED(mu) implies that we have neither a shared nor an exclusive lock as well. In the end, I have the same question as above: Why do we want two kinds of negative capabilities? Isn't the idea that negative capabilities express the lock not being held at all? |
clang/test/SemaCXX/thread-safety-annotations.h | ||
---|---|---|
47 | There are already tests on existing macros. I want to introduce tests about the new macros as well. |
clang/test/SemaCXX/warn-thread-safety-negative.cpp | ||
---|---|---|
135–140 | The problem is: by the current state of the thread safety analysis, ASSERT_SHARED_CAPABILTIY(!mu) introduces a shared negative capability, whereas RELEASE(mu) and RELEASE_SHARED(mu) introduce an exclusive negative capability, and UNLOCK_FUNCTION(mu) introduces a generic negative capability. All three are different. At merge points, warnings will be issued if different types of negative capability are merged. The current thread safety analysis produces bogus false positive in our code base. The solution I propose is that we should at least make RELEASE_SHARED(mu) produce a shared negative capability. Regarding why we should have types for negative capability, thinking about "exclusive !mu" in a reader-writer lock situation, which means "I am not trying to write". However, the code can still read. In other words, "exclusive !mu" does not imply "shared !mu", and the code can still hold the lock in shared state. Without the types of negative capability, we wouldn't be able to express the situation described above. |
clang/test/SemaCXX/thread-safety-annotations.h | ||
---|---|---|
47 | There are no old and new macros, there are only old and new attributes. The existing macros use both sets of attributes, depending on the value of USE_CAPABILITY with which the tests are run. Using other macro names is just window dressing. | |
clang/test/SemaCXX/warn-thread-safety-negative.cpp | ||
135–140 | I'm not doubting that there is a bug, but I don't think this is the right solution.
We should disallow using "shared" attributes with negative capabilities, and I'm surprised this isn't already the case.
Clearly both leave mu in the same (unlocked) state, so why distinguish between them? After the lock is released, whichever mode it has been in before is ancient history. We want to track the current state of the lock.
Negative capabilites aren't about what you are not doing with protected resources, in fact nothing is checked about that. Their purpose is to answer this question: can I draw this lock, or might it already been held? If you are holding a negative capability, that means you can acquire the lock. And when you can acquire the lock, you can do so in either mode. If the lock is currently held in any mode (and hence you're not holding a negative capability), then you can't acquire it in any mode, you have to release it first. So there is no use in having two kinds of negative capabilities. An "exclusive !mu" should be either "!mu" or "shared(mu)", depending on what the functions expects. The attributes encode state transitions, as I mentioned in D49355#1191544, and there is simply no way to go from "either having no lock or a shared lock" to anywhere else, because all attributes assume a certain previous state. |
clang/test/SemaCXX/thread-safety-annotations.h | ||
---|---|---|
47 | Gotcha. | |
clang/test/SemaCXX/warn-thread-safety-negative.cpp | ||
135–140 | IIUC, if the only type of negative capability we are introducing is exclusive negative capability, ASSERT_SHARED_CAPABILITY(!mu) should introduce an exclusive negative capability as well, right? The current state of the thread safety analysis is:
A fix would be to let all three produce exclusive negative capability, which essentially means there is no type associated with negative capability. This fix could also fix my bug. The reason why I am still calling it "exclusive" is that there is a flag IsShared() associated with capability objects no matter whether it is positive or negative. |
The primary purpose of Thread Safety Analysis is to make sure that accesses to shared resources are protected. That's why we only track whether a lock is available by default (i.e. without -Wthread-safety-negative), locks that we don't know anything about are assumed to be unlocked. As a side effect, we can detect double unlocks and lock/unlock-kind mismatches.
But we cannot detect double locks (and deadlocks) over function boundaries. This is why negative capabilities were introduced. But because not everybody cares about that enough to warrant more annotations, it's not on by default.
Regarding why we should have types for negative capability, thinking about "exclusive !mu" in a reader-writer lock situation, which means "I am not trying to write". However, the code can still read.
Negative capabilities don't declare that we're not reading/writing: we can't do that without holding the actual (positive) capability anyway!
Having the capability !mu just means that we can assume mu is not locked. We will then usually acquire the mu (otherwise the annotation wouldn't be needed).
clang/test/SemaCXX/warn-thread-safety-negative.cpp | ||
---|---|---|
135–140 | Yes, that sounds right. In the long-term I would want negative capabilities to be neither exclusive nor shared, but for now I would take them to be exclusive, to be consistent with the kinds of attributes we use them with. In my opinion we should also warn about ASSERT_SHARED_CAPABILITY(!mu) in -Wthread-safety-attributes. |
This is a larger effort so it will take a little bit more time on me.
This is fine, but could you add a "Plan changes" action then? This will make the revision disappear from the active review queue.
A fix would be to let all three produce exclusive negative capability, which essentially means there is no type associated with negative capability. This fix could also fix my bug.
As discussed, this is how it should be solved. I'm changing the state so that it's clear there is nothing to review here currently.
What do these lines mean? That we accept if a lock is shared in one branch and exclusive in the other, and that we make it exclusive after the merge point?