Skip to content

Commit 6a68efc

Browse files
committed
Thread safety analysis: Allow scoped releasing of capabilities
Summary: The pattern is problematic with C++ exceptions, and not as widespread as scoped locks, but it's still used by some, for example Chromium. We are a bit stricter here at join points, patterns that are allowed for scoped locks aren't allowed here. That could still be changed in the future, but I'd argue we should only relax this if people ask for it. Fixes PR36162. Reviewers: aaron.ballman, delesley, pwnall Reviewed By: delesley, pwnall Subscribers: pwnall, cfe-commits Differential Revision: https://reviews.llvm.org/D52578 llvm-svn: 349300
1 parent 7d648f8 commit 6a68efc

File tree

2 files changed

+177
-39
lines changed

2 files changed

+177
-39
lines changed

clang/lib/Analysis/ThreadSafety.cpp

Lines changed: 73 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include "llvm/ADT/DenseMap.h"
4343
#include "llvm/ADT/ImmutableMap.h"
4444
#include "llvm/ADT/Optional.h"
45+
#include "llvm/ADT/PointerIntPair.h"
4546
#include "llvm/ADT/STLExtras.h"
4647
#include "llvm/ADT/SmallVector.h"
4748
#include "llvm/ADT/StringRef.h"
@@ -890,46 +891,61 @@ class LockableFactEntry : public FactEntry {
890891

891892
class ScopedLockableFactEntry : public FactEntry {
892893
private:
893-
SmallVector<const til::SExpr *, 4> UnderlyingMutexes;
894+
enum UnderlyingCapabilityKind {
895+
UCK_Acquired, ///< Any kind of acquired capability.
896+
UCK_ReleasedShared, ///< Shared capability that was released.
897+
UCK_ReleasedExclusive, ///< Exclusive capability that was released.
898+
};
899+
900+
using UnderlyingCapability =
901+
llvm::PointerIntPair<const til::SExpr *, 2, UnderlyingCapabilityKind>;
902+
903+
SmallVector<UnderlyingCapability, 4> UnderlyingMutexes;
894904

895905
public:
896906
ScopedLockableFactEntry(const CapabilityExpr &CE, SourceLocation Loc,
897-
const CapExprSet &Excl, const CapExprSet &Shrd)
907+
const CapExprSet &Excl, const CapExprSet &Shrd,
908+
const CapExprSet &ExclRel, const CapExprSet &ShrdRel)
898909
: FactEntry(CE, LK_Exclusive, Loc, false) {
899910
for (const auto &M : Excl)
900-
UnderlyingMutexes.push_back(M.sexpr());
911+
UnderlyingMutexes.emplace_back(M.sexpr(), UCK_Acquired);
901912
for (const auto &M : Shrd)
902-
UnderlyingMutexes.push_back(M.sexpr());
913+
UnderlyingMutexes.emplace_back(M.sexpr(), UCK_Acquired);
914+
for (const auto &M : ExclRel)
915+
UnderlyingMutexes.emplace_back(M.sexpr(), UCK_ReleasedExclusive);
916+
for (const auto &M : ShrdRel)
917+
UnderlyingMutexes.emplace_back(M.sexpr(), UCK_ReleasedShared);
903918
}
904919

905920
void
906921
handleRemovalFromIntersection(const FactSet &FSet, FactManager &FactMan,
907922
SourceLocation JoinLoc, LockErrorKind LEK,
908923
ThreadSafetyHandler &Handler) const override {
909-
for (const auto *UnderlyingMutex : UnderlyingMutexes) {
910-
if (FSet.findLock(FactMan, CapabilityExpr(UnderlyingMutex, false))) {
924+
for (const auto &UnderlyingMutex : UnderlyingMutexes) {
925+
const auto *Entry = FSet.findLock(
926+
FactMan, CapabilityExpr(UnderlyingMutex.getPointer(), false));
927+
if ((UnderlyingMutex.getInt() == UCK_Acquired && Entry) ||
928+
(UnderlyingMutex.getInt() != UCK_Acquired && !Entry)) {
911929
// If this scoped lock manages another mutex, and if the underlying
912-
// mutex is still held, then warn about the underlying mutex.
930+
// mutex is still/not held, then warn about the underlying mutex.
913931
Handler.handleMutexHeldEndOfScope(
914-
"mutex", sx::toString(UnderlyingMutex), loc(), JoinLoc, LEK);
932+
"mutex", sx::toString(UnderlyingMutex.getPointer()), loc(), JoinLoc,
933+
LEK);
915934
}
916935
}
917936
}
918937

919938
void handleLock(FactSet &FSet, FactManager &FactMan, const FactEntry &entry,
920939
ThreadSafetyHandler &Handler,
921940
StringRef DiagKind) const override {
922-
for (const auto *UnderlyingMutex : UnderlyingMutexes) {
923-
CapabilityExpr UnderCp(UnderlyingMutex, false);
941+
for (const auto &UnderlyingMutex : UnderlyingMutexes) {
942+
CapabilityExpr UnderCp(UnderlyingMutex.getPointer(), false);
924943

925-
// We're relocking the underlying mutexes. Warn on double locking.
926-
if (FSet.findLock(FactMan, UnderCp)) {
927-
Handler.handleDoubleLock(DiagKind, UnderCp.toString(), entry.loc());
928-
} else {
929-
FSet.removeLock(FactMan, !UnderCp);
930-
FSet.addLock(FactMan, llvm::make_unique<LockableFactEntry>(
931-
UnderCp, entry.kind(), entry.loc()));
932-
}
944+
if (UnderlyingMutex.getInt() == UCK_Acquired)
945+
lock(FSet, FactMan, UnderCp, entry.kind(), entry.loc(), &Handler,
946+
DiagKind);
947+
else
948+
unlock(FSet, FactMan, UnderCp, entry.loc(), &Handler, DiagKind);
933949
}
934950
}
935951

@@ -938,32 +954,49 @@ class ScopedLockableFactEntry : public FactEntry {
938954
bool FullyRemove, ThreadSafetyHandler &Handler,
939955
StringRef DiagKind) const override {
940956
assert(!Cp.negative() && "Managing object cannot be negative.");
941-
for (const auto *UnderlyingMutex : UnderlyingMutexes) {
942-
CapabilityExpr UnderCp(UnderlyingMutex, false);
943-
auto UnderEntry = llvm::make_unique<LockableFactEntry>(
944-
!UnderCp, LK_Exclusive, UnlockLoc);
945-
946-
if (FullyRemove) {
947-
// We're destroying the managing object.
948-
// Remove the underlying mutex if it exists; but don't warn.
949-
if (FSet.findLock(FactMan, UnderCp)) {
950-
FSet.removeLock(FactMan, UnderCp);
951-
FSet.addLock(FactMan, std::move(UnderEntry));
952-
}
957+
for (const auto &UnderlyingMutex : UnderlyingMutexes) {
958+
CapabilityExpr UnderCp(UnderlyingMutex.getPointer(), false);
959+
960+
// Remove/lock the underlying mutex if it exists/is still unlocked; warn
961+
// on double unlocking/locking if we're not destroying the scoped object.
962+
ThreadSafetyHandler *TSHandler = FullyRemove ? nullptr : &Handler;
963+
if (UnderlyingMutex.getInt() == UCK_Acquired) {
964+
unlock(FSet, FactMan, UnderCp, UnlockLoc, TSHandler, DiagKind);
953965
} else {
954-
// We're releasing the underlying mutex, but not destroying the
955-
// managing object. Warn on dual release.
956-
if (!FSet.findLock(FactMan, UnderCp)) {
957-
Handler.handleUnmatchedUnlock(DiagKind, UnderCp.toString(),
958-
UnlockLoc);
959-
}
960-
FSet.removeLock(FactMan, UnderCp);
961-
FSet.addLock(FactMan, std::move(UnderEntry));
966+
LockKind kind = UnderlyingMutex.getInt() == UCK_ReleasedShared
967+
? LK_Shared
968+
: LK_Exclusive;
969+
lock(FSet, FactMan, UnderCp, kind, UnlockLoc, TSHandler, DiagKind);
962970
}
963971
}
964972
if (FullyRemove)
965973
FSet.removeLock(FactMan, Cp);
966974
}
975+
976+
private:
977+
void lock(FactSet &FSet, FactManager &FactMan, const CapabilityExpr &Cp,
978+
LockKind kind, SourceLocation loc, ThreadSafetyHandler *Handler,
979+
StringRef DiagKind) const {
980+
if (!FSet.findLock(FactMan, Cp)) {
981+
FSet.removeLock(FactMan, !Cp);
982+
FSet.addLock(FactMan,
983+
llvm::make_unique<LockableFactEntry>(Cp, kind, loc));
984+
} else if (Handler) {
985+
Handler->handleDoubleLock(DiagKind, Cp.toString(), loc);
986+
}
987+
}
988+
989+
void unlock(FactSet &FSet, FactManager &FactMan, const CapabilityExpr &Cp,
990+
SourceLocation loc, ThreadSafetyHandler *Handler,
991+
StringRef DiagKind) const {
992+
if (FSet.findLock(FactMan, Cp)) {
993+
FSet.removeLock(FactMan, Cp);
994+
FSet.addLock(FactMan, llvm::make_unique<LockableFactEntry>(
995+
!Cp, LK_Exclusive, loc));
996+
} else if (Handler) {
997+
Handler->handleUnmatchedUnlock(DiagKind, Cp.toString(), loc);
998+
}
999+
}
9671000
};
9681001

9691002
/// Class which implements the core thread safety analysis routines.
@@ -1911,7 +1944,8 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D,
19111944
std::back_inserter(SharedLocksToAdd));
19121945
Analyzer->addLock(FSet,
19131946
llvm::make_unique<ScopedLockableFactEntry>(
1914-
Scp, MLoc, ExclusiveLocksToAdd, SharedLocksToAdd),
1947+
Scp, MLoc, ExclusiveLocksToAdd, SharedLocksToAdd,
1948+
ExclusiveLocksToRemove, SharedLocksToRemove),
19151949
CapDiagKind);
19161950
}
19171951
}

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

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2787,6 +2787,110 @@ void relockWeird() {
27872787
} // end namespace RelockableScopedLock
27882788

27892789

2790+
namespace ScopedUnlock {
2791+
2792+
class SCOPED_LOCKABLE MutexUnlock {
2793+
public:
2794+
MutexUnlock(Mutex *mu) EXCLUSIVE_UNLOCK_FUNCTION(mu);
2795+
~MutexUnlock() EXCLUSIVE_UNLOCK_FUNCTION();
2796+
2797+
void Lock() EXCLUSIVE_UNLOCK_FUNCTION();
2798+
void Unlock() EXCLUSIVE_LOCK_FUNCTION();
2799+
};
2800+
2801+
class SCOPED_LOCKABLE ReaderMutexUnlock {
2802+
public:
2803+
ReaderMutexUnlock(Mutex *mu) SHARED_UNLOCK_FUNCTION(mu);
2804+
~ReaderMutexUnlock() EXCLUSIVE_UNLOCK_FUNCTION();
2805+
2806+
void Lock() EXCLUSIVE_UNLOCK_FUNCTION();
2807+
void Unlock() EXCLUSIVE_LOCK_FUNCTION();
2808+
};
2809+
2810+
Mutex mu;
2811+
int x GUARDED_BY(mu);
2812+
bool c;
2813+
void print(int);
2814+
2815+
void simple() EXCLUSIVE_LOCKS_REQUIRED(mu) {
2816+
x = 1;
2817+
MutexUnlock scope(&mu);
2818+
x = 2; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
2819+
}
2820+
2821+
void simpleShared() SHARED_LOCKS_REQUIRED(mu) {
2822+
print(x);
2823+
ReaderMutexUnlock scope(&mu);
2824+
print(x); // expected-warning {{reading variable 'x' requires holding mutex 'mu'}}
2825+
}
2826+
2827+
void innerUnlock() {
2828+
MutexLock outer(&mu);
2829+
if (x == 0) {
2830+
MutexUnlock inner(&mu);
2831+
x = 1; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
2832+
}
2833+
x = 2;
2834+
}
2835+
2836+
void innerUnlockShared() {
2837+
ReaderMutexLock outer(&mu);
2838+
if (x == 0) {
2839+
ReaderMutexUnlock inner(&mu);
2840+
print(x); // expected-warning {{reading variable 'x' requires holding mutex 'mu'}}
2841+
}
2842+
print(x);
2843+
}
2844+
2845+
void manual() EXCLUSIVE_LOCKS_REQUIRED(mu) {
2846+
MutexUnlock scope(&mu);
2847+
scope.Lock();
2848+
x = 2;
2849+
scope.Unlock();
2850+
x = 3; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
2851+
}
2852+
2853+
void join() EXCLUSIVE_LOCKS_REQUIRED(mu) {
2854+
MutexUnlock scope(&mu);
2855+
if (c) {
2856+
scope.Lock(); // expected-note{{mutex acquired here}}
2857+
}
2858+
// expected-warning@+1{{mutex 'mu' is not held on every path through here}}
2859+
scope.Lock();
2860+
}
2861+
2862+
void doubleLock() EXCLUSIVE_LOCKS_REQUIRED(mu) {
2863+
MutexUnlock scope(&mu);
2864+
scope.Lock();
2865+
scope.Lock(); // expected-warning {{acquiring mutex 'mu' that is already held}}
2866+
}
2867+
2868+
void doubleUnlock() EXCLUSIVE_LOCKS_REQUIRED(mu) {
2869+
MutexUnlock scope(&mu);
2870+
scope.Unlock(); // expected-warning {{releasing mutex 'mu' that was not held}}
2871+
}
2872+
2873+
class SCOPED_LOCKABLE MutexLockUnlock {
2874+
public:
2875+
MutexLockUnlock(Mutex *mu1, Mutex *mu2) EXCLUSIVE_UNLOCK_FUNCTION(mu1) EXCLUSIVE_LOCK_FUNCTION(mu2);
2876+
~MutexLockUnlock() EXCLUSIVE_UNLOCK_FUNCTION();
2877+
2878+
void Release() EXCLUSIVE_UNLOCK_FUNCTION();
2879+
void Acquire() EXCLUSIVE_LOCK_FUNCTION();
2880+
};
2881+
2882+
Mutex other;
2883+
void fn() EXCLUSIVE_LOCKS_REQUIRED(other);
2884+
2885+
void lockUnlock() EXCLUSIVE_LOCKS_REQUIRED(mu) {
2886+
MutexLockUnlock scope(&mu, &other);
2887+
fn();
2888+
x = 1; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
2889+
}
2890+
2891+
} // end namespace ScopedUnlock
2892+
2893+
27902894
namespace TrylockFunctionTest {
27912895

27922896
class Foo {

0 commit comments

Comments
 (0)