Skip to content

Commit d0f0b5b

Browse files
aaronpucherttstellar
authored andcommitted
Thread safety analysis: Warn when demoting locks on back edges
Previously in D104261 we warned about dropping locks from back edges, this is the corresponding change for exclusive/shared joins. If we're entering the loop with an exclusive change, which is then relaxed to a shared lock before we loop back, we have already analyzed the loop body with the stronger exclusive lock and thus might have false positives. There is a minor non-observable change: we modify the exit lock set of a function, but since that isn't used further it doesn't change anything. Reviewed By: aaron.ballman Differential Revision: https://reviews.llvm.org/D106713 (cherry picked from commit 9b889f8)
1 parent 80f974e commit d0f0b5b

File tree

2 files changed

+59
-14
lines changed

2 files changed

+59
-14
lines changed

clang/lib/Analysis/ThreadSafety.cpp

+17-14
Original file line numberDiff line numberDiff line change
@@ -1050,7 +1050,7 @@ class ThreadSafetyAnalyzer {
10501050
const CFGBlock* PredBlock,
10511051
const CFGBlock *CurrBlock);
10521052

1053-
bool join(const FactEntry &a, const FactEntry &b);
1053+
bool join(const FactEntry &a, const FactEntry &b, bool CanModify);
10541054

10551055
void intersectAndWarn(FactSet &EntrySet, const FactSet &ExitSet,
10561056
SourceLocation JoinLoc, LockErrorKind EntryLEK,
@@ -2188,25 +2188,28 @@ void BuildLockset::VisitDeclStmt(const DeclStmt *S) {
21882188
}
21892189
}
21902190

2191-
/// Given two facts merging on a join point, decide whether to warn and which
2192-
/// one to keep.
2191+
/// Given two facts merging on a join point, possibly warn and decide whether to
2192+
/// keep or replace.
21932193
///
2194-
/// \return false if we should keep \p A, true if we should keep \p B.
2195-
bool ThreadSafetyAnalyzer::join(const FactEntry &A, const FactEntry &B) {
2194+
/// \param CanModify Whether we can replace \p A by \p B.
2195+
/// \return false if we should keep \p A, true if we should take \p B.
2196+
bool ThreadSafetyAnalyzer::join(const FactEntry &A, const FactEntry &B,
2197+
bool CanModify) {
21962198
if (A.kind() != B.kind()) {
21972199
// For managed capabilities, the destructor should unlock in the right mode
21982200
// anyway. For asserted capabilities no unlocking is needed.
21992201
if ((A.managed() || A.asserted()) && (B.managed() || B.asserted())) {
2200-
// The shared capability subsumes the exclusive capability.
2201-
return B.kind() == LK_Shared;
2202-
} else {
2203-
Handler.handleExclusiveAndShared("mutex", B.toString(), B.loc(), A.loc());
2204-
// Take the exclusive capability to reduce further warnings.
2205-
return B.kind() == LK_Exclusive;
2202+
// The shared capability subsumes the exclusive capability, if possible.
2203+
bool ShouldTakeB = B.kind() == LK_Shared;
2204+
if (CanModify || !ShouldTakeB)
2205+
return ShouldTakeB;
22062206
}
2207+
Handler.handleExclusiveAndShared("mutex", B.toString(), B.loc(), A.loc());
2208+
// Take the exclusive capability to reduce further warnings.
2209+
return CanModify && B.kind() == LK_Exclusive;
22072210
} else {
22082211
// The non-asserted capability is the one we want to track.
2209-
return A.asserted() && !B.asserted();
2212+
return CanModify && A.asserted() && !B.asserted();
22102213
}
22112214
}
22122215

@@ -2237,8 +2240,8 @@ void ThreadSafetyAnalyzer::intersectAndWarn(FactSet &EntrySet,
22372240

22382241
FactSet::iterator EntryIt = EntrySet.findLockIter(FactMan, ExitFact);
22392242
if (EntryIt != EntrySet.end()) {
2240-
if (join(FactMan[*EntryIt], ExitFact) &&
2241-
EntryLEK == LEK_LockedSomePredecessors)
2243+
if (join(FactMan[*EntryIt], ExitFact,
2244+
EntryLEK != LEK_LockedSomeLoopIterations))
22422245
*EntryIt = Fact;
22432246
} else if (!ExitFact.managed()) {
22442247
ExitFact.handleRemovalFromIntersection(ExitSet, FactMan, JoinLoc,

clang/test/SemaCXX/warn-thread-safety-analysis.cpp

+42
Original file line numberDiff line numberDiff line change
@@ -2789,6 +2789,25 @@ void loopRelease() {
27892789
}
27902790
}
27912791

2792+
void loopPromote() {
2793+
RelockableMutexLock scope(&mu, SharedTraits{});
2794+
for (unsigned i = 1; i < 10; ++i) {
2795+
x = 1; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
2796+
if (i == 5)
2797+
scope.PromoteShared();
2798+
}
2799+
}
2800+
2801+
void loopDemote() {
2802+
RelockableMutexLock scope(&mu, ExclusiveTraits{}); // expected-note {{the other acquisition of mutex 'mu' is here}}
2803+
// We have to warn on this join point despite the lock being managed ...
2804+
for (unsigned i = 1; i < 10; ++i) {
2805+
x = 1; // ... because we might miss that this doesn't always happen under exclusive lock.
2806+
if (i == 5)
2807+
scope.DemoteExclusive(); // expected-warning {{mutex 'mu' is acquired exclusively and shared in the same scope}}
2808+
}
2809+
}
2810+
27922811
void loopAcquireContinue() {
27932812
RelockableMutexLock scope(&mu, DeferTraits{});
27942813
for (unsigned i = 1; i < 10; ++i) {
@@ -2812,6 +2831,29 @@ void loopReleaseContinue() {
28122831
}
28132832
}
28142833

2834+
void loopPromoteContinue() {
2835+
RelockableMutexLock scope(&mu, SharedTraits{});
2836+
for (unsigned i = 1; i < 10; ++i) {
2837+
x = 1; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
2838+
if (i == 5) {
2839+
scope.PromoteShared();
2840+
continue;
2841+
}
2842+
}
2843+
}
2844+
2845+
void loopDemoteContinue() {
2846+
RelockableMutexLock scope(&mu, ExclusiveTraits{}); // expected-note {{the other acquisition of mutex 'mu' is here}}
2847+
// We have to warn on this join point despite the lock being managed ...
2848+
for (unsigned i = 1; i < 10; ++i) {
2849+
x = 1; // ... because we might miss that this doesn't always happen under exclusive lock.
2850+
if (i == 5) {
2851+
scope.DemoteExclusive(); // expected-warning {{mutex 'mu' is acquired exclusively and shared in the same scope}}
2852+
continue;
2853+
}
2854+
}
2855+
}
2856+
28152857
void exclusiveSharedJoin() {
28162858
RelockableMutexLock scope(&mu, DeferTraits{});
28172859
if (b)

0 commit comments

Comments
 (0)