[clang] Thread Safety Analysis: Support reentrant capabilities (PR #137133)
Marco Elver via cfe-commits
cfe-commits at lists.llvm.org
Sat Apr 26 00:40:09 PDT 2025
https://github.com/melver updated https://github.com/llvm/llvm-project/pull/137133
>From d3324c1023533bf784a3c3c3ef095d07c865e6f9 Mon Sep 17 00:00:00 2001
From: Marco Elver <elver at google.com>
Date: Wed, 23 Apr 2025 11:31:25 +0200
Subject: [PATCH 1/2] Thread Safety Analysis: Convert CapabilityExpr::CapExpr
to hold flags
Rather than holding a single bool, switch it to contain flags, which is
both more descriptive and simplifies adding more flags in subsequent
changes.
NFC.
---
.../clang/Analysis/Analyses/ThreadSafetyCommon.h | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h b/clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h
index e99c5b2466334..6e46a2d721463 100644
--- a/clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h
+++ b/clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h
@@ -271,26 +271,28 @@ class CFGWalker {
// translateAttrExpr needs it, but that should be moved too.
class CapabilityExpr {
private:
- /// The capability expression and whether it's negated.
- llvm::PointerIntPair<const til::SExpr *, 1, bool> CapExpr;
+ static constexpr unsigned FlagNegative = 1u << 0;
+
+ /// The capability expression and flags.
+ llvm::PointerIntPair<const til::SExpr *, 1, unsigned> CapExpr;
/// The kind of capability as specified by @ref CapabilityAttr::getName.
StringRef CapKind;
public:
- CapabilityExpr() : CapExpr(nullptr, false) {}
+ CapabilityExpr() : CapExpr(nullptr, 0) {}
CapabilityExpr(const til::SExpr *E, StringRef Kind, bool Neg)
- : CapExpr(E, Neg), CapKind(Kind) {}
+ : CapExpr(E, Neg ? FlagNegative : 0), CapKind(Kind) {}
// Don't allow implicitly-constructed StringRefs since we'll capture them.
template <typename T> CapabilityExpr(const til::SExpr *, T, bool) = delete;
const til::SExpr *sexpr() const { return CapExpr.getPointer(); }
StringRef getKind() const { return CapKind; }
- bool negative() const { return CapExpr.getInt(); }
+ bool negative() const { return CapExpr.getInt() & FlagNegative; }
CapabilityExpr operator!() const {
- return CapabilityExpr(CapExpr.getPointer(), CapKind, !CapExpr.getInt());
+ return CapabilityExpr(CapExpr.getPointer(), CapKind, !negative());
}
bool equals(const CapabilityExpr &other) const {
>From 89ceb0d45d7fb30885f793a19cfae7ee26dae3fc Mon Sep 17 00:00:00 2001
From: Marco Elver <elver at google.com>
Date: Thu, 24 Apr 2025 09:02:08 +0200
Subject: [PATCH 2/2] Thread Safety Analysis: Support reentrant capabilities
Introduce the `reentrant_capability` attribute, which may be specified
alongside the `capability(..)` attribute to denote that the defined
capability type is reentrant. Marking a capability as reentrant means
that acquiring the same capability multiple times is safe, and does not
produce warnings on attempted re-acquisition.
The most significant changes required are plumbing to propagate if the
attribute is present to a CapabilityExpr, and then introducing a
ReentrancyCount to FactEntry that can be incremented while a fact
remains in the FactSet.
Care was taken to avoid increasing the size of both CapabilityExpr and
FactEntry by carefully allocating free bits of CapabilityExpr::CapExpr
and the bitset respectively.
---
clang/docs/ReleaseNotes.rst | 1 +
clang/docs/ThreadSafetyAnalysis.rst | 18 +
.../clang/Analysis/Analyses/ThreadSafety.h | 4 +
.../Analysis/Analyses/ThreadSafetyCommon.h | 17 +-
clang/include/clang/Basic/Attr.td | 7 +
.../clang/Basic/DiagnosticSemaKinds.td | 3 +
clang/lib/Analysis/ThreadSafety.cpp | 127 ++++++--
clang/lib/Analysis/ThreadSafetyCommon.cpp | 39 +--
clang/lib/Sema/AnalysisBasedWarnings.cpp | 7 +
...a-attribute-supported-attributes-list.test | 1 +
clang/test/Sema/warn-thread-safety-analysis.c | 20 ++
.../test/SemaCXX/thread-safety-annotations.h | 1 +
.../SemaCXX/warn-thread-safety-analysis.cpp | 308 ++++++++++++++++++
clang/unittests/AST/ASTImporterTest.cpp | 7 +
14 files changed, 509 insertions(+), 51 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index d1f24fb23d44d..7afa2ef359de9 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -343,6 +343,7 @@ Improvements to Clang's diagnostics
as function arguments or return value respectively. Note that
:doc:`ThreadSafetyAnalysis` still does not perform alias analysis. The
feature will be default-enabled with ``-Wthread-safety`` in a future release.
+- The :doc:`ThreadSafetyAnalysis` now supports reentrant capabilities.
- Clang will now do a better job producing common nested names, when producing
common types for ternary operator, template argument deduction and multiple return auto deduction.
- The ``-Wsign-compare`` warning now treats expressions with bitwise not(~) and minus(-) as signed integers
diff --git a/clang/docs/ThreadSafetyAnalysis.rst b/clang/docs/ThreadSafetyAnalysis.rst
index 130069c5659d6..4fc7ff28e9931 100644
--- a/clang/docs/ThreadSafetyAnalysis.rst
+++ b/clang/docs/ThreadSafetyAnalysis.rst
@@ -434,6 +434,21 @@ class can be used as a capability. The string argument specifies the kind of
capability in error messages, e.g. ``"mutex"``. See the ``Container`` example
given above, or the ``Mutex`` class in :ref:`mutexheader`.
+REENTRANT_CAPABILITY
+--------------------
+
+``REENTRANT_CAPABILITY`` is an attribute on capability classes, denoting that
+they are reentrant. Marking a capability as reentrant means that acquiring the
+same capability multiple times is safe. Acquiring the same capability with
+different access privileges (exclusive vs. shared) again is not considered
+reentrant by the analysis.
+
+Note: In many cases this attribute is only required where a capability is
+acquired reentrant within the same function, such as via macros or other
+helpers. Otherwise, best practice is to avoid explicitly acquiring a capability
+multiple times within the same function, and letting the analysis produce
+warnings on double-acquisition attempts.
+
.. _scoped_capability:
SCOPED_CAPABILITY
@@ -846,6 +861,9 @@ implementation.
#define CAPABILITY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(capability(x))
+ #define REENTRANT_CAPABILITY \
+ THREAD_ANNOTATION_ATTRIBUTE__(reentrant_capability)
+
#define SCOPED_CAPABILITY \
THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)
diff --git a/clang/include/clang/Analysis/Analyses/ThreadSafety.h b/clang/include/clang/Analysis/Analyses/ThreadSafety.h
index 20b75c46593e0..b280d93d9c3a8 100644
--- a/clang/include/clang/Analysis/Analyses/ThreadSafety.h
+++ b/clang/include/clang/Analysis/Analyses/ThreadSafety.h
@@ -278,6 +278,10 @@ class ThreadSafetyHandler {
/// Warn that there is a cycle in acquired_before/after dependencies.
virtual void handleBeforeAfterCycle(Name L1Name, SourceLocation Loc) {}
+ /// Warn that the maximum supported reentrancy depth has been reached.
+ virtual void handleReentrancyDepthLimit(StringRef Kind, Name LockName,
+ SourceLocation Loc) {}
+
/// Called by the analysis when starting analysis of a function.
/// Used to issue suggestions for changes to annotations.
virtual void enterFunction(const FunctionDecl *FD) {}
diff --git a/clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h b/clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h
index 6e46a2d721463..081b122939c4c 100644
--- a/clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h
+++ b/clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h
@@ -272,27 +272,32 @@ class CFGWalker {
class CapabilityExpr {
private:
static constexpr unsigned FlagNegative = 1u << 0;
+ static constexpr unsigned FlagReentrant = 1u << 1;
/// The capability expression and flags.
- llvm::PointerIntPair<const til::SExpr *, 1, unsigned> CapExpr;
+ llvm::PointerIntPair<const til::SExpr *, 2, unsigned> CapExpr;
/// The kind of capability as specified by @ref CapabilityAttr::getName.
StringRef CapKind;
public:
CapabilityExpr() : CapExpr(nullptr, 0) {}
- CapabilityExpr(const til::SExpr *E, StringRef Kind, bool Neg)
- : CapExpr(E, Neg ? FlagNegative : 0), CapKind(Kind) {}
+ CapabilityExpr(const til::SExpr *E, StringRef Kind, bool Neg, bool Reentrant)
+ : CapExpr(E, (Neg ? FlagNegative : 0) | (Reentrant ? FlagReentrant : 0)),
+ CapKind(Kind) {}
// Don't allow implicitly-constructed StringRefs since we'll capture them.
- template <typename T> CapabilityExpr(const til::SExpr *, T, bool) = delete;
+ template <typename T>
+ CapabilityExpr(const til::SExpr *, T, bool, bool) = delete;
const til::SExpr *sexpr() const { return CapExpr.getPointer(); }
StringRef getKind() const { return CapKind; }
bool negative() const { return CapExpr.getInt() & FlagNegative; }
+ bool reentrant() const { return CapExpr.getInt() & FlagReentrant; }
CapabilityExpr operator!() const {
- return CapabilityExpr(CapExpr.getPointer(), CapKind, !negative());
+ return CapabilityExpr(CapExpr.getPointer(), CapKind, !negative(),
+ reentrant());
}
bool equals(const CapabilityExpr &other) const {
@@ -390,7 +395,7 @@ class SExprBuilder {
til::LiteralPtr *createVariable(const VarDecl *VD);
// Create placeholder for this: we don't know the VarDecl on construction yet.
- std::pair<til::LiteralPtr *, StringRef>
+ std::pair<til::LiteralPtr *, CapabilityExpr>
createThisPlaceholder(const Expr *Exp);
// Translate a clang statement or expression to a TIL expression.
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index d48aed5b73cf5..88be9d3d13629 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -3990,6 +3990,13 @@ def LocksExcluded : InheritableAttr {
let Documentation = [Undocumented];
}
+def ReentrantCapability : InheritableAttr {
+ let Spellings = [Clang<"reentrant_capability">];
+ let Subjects = SubjectList<[Record, TypedefName]>;
+ let Documentation = [Undocumented];
+ let SimpleHandler = 1;
+}
+
// C/C++ consumed attributes.
def Consumable : InheritableAttr {
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 8ff170520aafe..dd64aff8e8db3 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -4105,6 +4105,9 @@ def warn_expect_more_underlying_mutexes : Warning<
def warn_expect_fewer_underlying_mutexes : Warning<
"did not expect %0 '%2' to be managed by '%1'">,
InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
+def warn_reentrancy_depth_limit : Warning<
+ "reentrancy depth limit reached for %0 '%1'">,
+ InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
def note_managed_mismatch_here_for_param : Note<
"see attribute on parameter here">;
diff --git a/clang/lib/Analysis/ThreadSafety.cpp b/clang/lib/Analysis/ThreadSafety.cpp
index 42fb0fe7dcdaa..e2d758e321b4f 100644
--- a/clang/lib/Analysis/ThreadSafety.cpp
+++ b/clang/lib/Analysis/ThreadSafety.cpp
@@ -99,8 +99,6 @@ class FactSet;
/// particular point in program execution. Currently, a fact is a capability,
/// along with additional information, such as where it was acquired, whether
/// it is exclusive or shared, etc.
-///
-/// FIXME: this analysis does not currently support re-entrant locking.
class FactEntry : public CapabilityExpr {
public:
enum FactEntryKind { Lockable, ScopedLockable };
@@ -114,31 +112,39 @@ class FactEntry : public CapabilityExpr {
};
private:
- const FactEntryKind Kind : 8;
+ const FactEntryKind Kind : 4;
/// Exclusive or shared.
- LockKind LKind : 8;
+ const LockKind LKind : 4;
+
+ /// How it was acquired.
+ const SourceKind Source : 4;
- // How it was acquired.
- SourceKind Source : 8;
+ /// Reentrancy depth; 16 bits should be enough given that FactID is a short,
+ /// and thus we can't store more than 65536 facts anyway.
+ unsigned int ReentrancyDepth : 16;
/// Where it was acquired.
- SourceLocation AcquireLoc;
+ const SourceLocation AcquireLoc;
public:
FactEntry(FactEntryKind FK, const CapabilityExpr &CE, LockKind LK,
SourceLocation Loc, SourceKind Src)
- : CapabilityExpr(CE), Kind(FK), LKind(LK), Source(Src), AcquireLoc(Loc) {}
+ : CapabilityExpr(CE), Kind(FK), LKind(LK), Source(Src),
+ ReentrancyDepth(0), AcquireLoc(Loc) {}
virtual ~FactEntry() = default;
LockKind kind() const { return LKind; }
SourceLocation loc() const { return AcquireLoc; }
FactEntryKind getFactEntryKind() const { return Kind; }
+ unsigned int getReentrancyDepth() const { return ReentrancyDepth; }
bool asserted() const { return Source == Asserted; }
bool declared() const { return Source == Declared; }
bool managed() const { return Source == Managed; }
+ virtual std::unique_ptr<FactEntry> clone() const = 0;
+
virtual void
handleRemovalFromIntersection(const FactSet &FSet, FactManager &FactMan,
SourceLocation JoinLoc, LockErrorKind LEK,
@@ -155,6 +161,29 @@ class FactEntry : public CapabilityExpr {
bool isAtLeast(LockKind LK) const {
return (LKind == LK_Exclusive) || (LK == LK_Shared);
}
+
+ // Return an updated FactEntry if we can acquire this capability reentrant,
+ // nullptr otherwise.
+ [[nodiscard]] std::unique_ptr<FactEntry>
+ tryReenter(LockKind ReenterKind) const {
+ if (!reentrant())
+ return nullptr;
+ if (kind() != ReenterKind)
+ return nullptr;
+ auto NewFact = clone();
+ NewFact->ReentrancyDepth++;
+ return NewFact;
+ }
+
+ // Return an updated FactEntry if we are releasing a capability previously
+ // acquired reentrant, nullptr otherwise.
+ [[nodiscard]] std::unique_ptr<FactEntry> leaveReentrant() const {
+ if (!ReentrancyDepth)
+ return nullptr;
+ auto NewFact = clone();
+ NewFact->ReentrancyDepth--;
+ return NewFact;
+ }
};
using FactID = unsigned short;
@@ -168,6 +197,8 @@ class FactManager {
public:
FactID newFact(std::unique_ptr<FactEntry> Entry) {
Facts.push_back(std::move(Entry));
+ assert(Facts.size() - 1 <= std::numeric_limits<unsigned short>::max() &&
+ "FactID space exhausted");
return static_cast<unsigned short>(Facts.size() - 1);
}
@@ -235,6 +266,20 @@ class FactSet {
return false;
}
+ std::optional<FactID> replaceLock(FactManager &FM, iterator It,
+ std::unique_ptr<FactEntry> Entry) {
+ if (It == end())
+ return std::nullopt;
+ FactID F = FM.newFact(std::move(Entry));
+ *It = F;
+ return F;
+ }
+
+ std::optional<FactID> replaceLock(FactManager &FM, const CapabilityExpr &CapE,
+ std::unique_ptr<FactEntry> Entry) {
+ return replaceLock(FM, findLockIter(FM, CapE), std::move(Entry));
+ }
+
iterator findLockIter(FactManager &FM, const CapabilityExpr &CapE) {
return std::find_if(begin(), end(), [&](FactID ID) {
return FM[ID].matches(CapE);
@@ -864,6 +909,10 @@ class LockableFactEntry : public FactEntry {
SourceKind Src = Acquired)
: FactEntry(Lockable, CE, LK, Loc, Src) {}
+ std::unique_ptr<FactEntry> clone() const override {
+ return std::make_unique<LockableFactEntry>(*this);
+ }
+
void
handleRemovalFromIntersection(const FactSet &FSet, FactManager &FactMan,
SourceLocation JoinLoc, LockErrorKind LEK,
@@ -876,14 +925,28 @@ class LockableFactEntry : public FactEntry {
void handleLock(FactSet &FSet, FactManager &FactMan, const FactEntry &entry,
ThreadSafetyHandler &Handler) const override {
- Handler.handleDoubleLock(entry.getKind(), entry.toString(), loc(),
- entry.loc());
+ if (std::unique_ptr<FactEntry> ReentrantFact = tryReenter(entry.kind())) {
+ if (ReentrantFact->getReentrancyDepth() == 0)
+ Handler.handleReentrancyDepthLimit(entry.getKind(), entry.toString(),
+ entry.loc());
+ // This capability has been reentrantly acquired.
+ FSet.replaceLock(FactMan, entry, std::move(ReentrantFact));
+ } else {
+ Handler.handleDoubleLock(entry.getKind(), entry.toString(), loc(),
+ entry.loc());
+ }
}
void handleUnlock(FactSet &FSet, FactManager &FactMan,
const CapabilityExpr &Cp, SourceLocation UnlockLoc,
bool FullyRemove,
ThreadSafetyHandler &Handler) const override {
+ if (std::unique_ptr<FactEntry> ReentrantFact = leaveReentrant()) {
+ // This capability remains reentrantly acquired.
+ FSet.replaceLock(FactMan, Cp, std::move(ReentrantFact));
+ return;
+ }
+
FSet.removeLock(FactMan, Cp);
if (!Cp.negative()) {
FSet.addLock(FactMan, std::make_unique<LockableFactEntry>(
@@ -916,6 +979,10 @@ class ScopedLockableFactEntry : public FactEntry {
SourceKind Src)
: FactEntry(ScopedLockable, CE, LK_Exclusive, Loc, Src) {}
+ std::unique_ptr<FactEntry> clone() const override {
+ return std::make_unique<ScopedLockableFactEntry>(*this);
+ }
+
CapExprSet getUnderlyingMutexes() const {
CapExprSet UnderlyingMutexesSet;
for (const UnderlyingCapability &UnderlyingMutex : UnderlyingMutexes)
@@ -996,10 +1063,16 @@ class ScopedLockableFactEntry : public FactEntry {
void lock(FactSet &FSet, FactManager &FactMan, const CapabilityExpr &Cp,
LockKind kind, SourceLocation loc,
ThreadSafetyHandler *Handler) const {
- if (const FactEntry *Fact = FSet.findLock(FactMan, Cp)) {
- if (Handler)
- Handler->handleDoubleLock(Cp.getKind(), Cp.toString(), Fact->loc(),
- loc);
+ if (const auto It = FSet.findLockIter(FactMan, Cp); It != FSet.end()) {
+ const FactEntry &Fact = FactMan[*It];
+ if (std::unique_ptr<FactEntry> ReentrantFact = Fact.tryReenter(kind)) {
+ if (ReentrantFact->getReentrancyDepth() == 0 && Handler)
+ Handler->handleReentrancyDepthLimit(Cp.getKind(), Cp.toString(), loc);
+ // This capability has been reentrantly acquired.
+ FSet.replaceLock(FactMan, It, std::move(ReentrantFact));
+ } else if (Handler) {
+ Handler->handleDoubleLock(Cp.getKind(), Cp.toString(), Fact.loc(), loc);
+ }
} else {
FSet.removeLock(FactMan, !Cp);
FSet.addLock(FactMan,
@@ -1009,10 +1082,17 @@ class ScopedLockableFactEntry : public FactEntry {
void unlock(FactSet &FSet, FactManager &FactMan, const CapabilityExpr &Cp,
SourceLocation loc, ThreadSafetyHandler *Handler) const {
- if (FSet.findLock(FactMan, Cp)) {
+ if (const auto It = FSet.findLockIter(FactMan, Cp); It != FSet.end()) {
+ const FactEntry &Fact = FactMan[*It];
+ if (std::unique_ptr<FactEntry> ReentrantFact = Fact.leaveReentrant()) {
+ // This capability remains reentrantly acquired.
+ FSet.replaceLock(FactMan, It, std::move(ReentrantFact));
+ return;
+ }
+
FSet.removeLock(FactMan, Cp);
- FSet.addLock(FactMan, std::make_unique<LockableFactEntry>(
- !Cp, LK_Exclusive, loc));
+ FSet.addLock(FactMan,
+ std::make_unique<LockableFactEntry>(!Cp, LK_Exclusive, loc));
} else if (Handler) {
SourceLocation PrevLoc;
if (const FactEntry *Neg = FSet.findLock(FactMan, !Cp))
@@ -1306,7 +1386,6 @@ void ThreadSafetyAnalyzer::addLock(FactSet &FSet,
Entry->loc(), Entry->getKind());
}
- // FIXME: Don't always warn when we have support for reentrant locks.
if (const FactEntry *Cp = FSet.findLock(FactMan, *Entry)) {
if (!Entry->asserted())
Cp->handleLock(FSet, FactMan, *Entry, Handler);
@@ -1831,7 +1910,7 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D,
assert(!Self);
const auto *TagT = Exp->getType()->getAs<TagType>();
if (D->hasAttrs() && TagT && Exp->isPRValue()) {
- std::pair<til::LiteralPtr *, StringRef> Placeholder =
+ std::pair<til::LiteralPtr *, CapabilityExpr> Placeholder =
Analyzer->SxBuilder.createThisPlaceholder(Exp);
[[maybe_unused]] auto inserted =
Analyzer->ConstructedObjects.insert({Exp, Placeholder.first});
@@ -1839,7 +1918,7 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D,
if (isa<CXXConstructExpr>(Exp))
Self = Placeholder.first;
if (TagT->getDecl()->hasAttr<ScopedLockableAttr>())
- Scp = CapabilityExpr(Placeholder.first, Placeholder.second, false);
+ Scp = Placeholder.second;
}
assert(Loc.isInvalid());
@@ -1977,12 +2056,13 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D,
if (DeclaredLocks.empty())
continue;
CapabilityExpr Cp(Analyzer->SxBuilder.translate(Arg, nullptr),
- StringRef("mutex"), false);
+ StringRef("mutex"), /*Neg=*/false, /*Reentrant=*/false);
if (const auto *CBTE = dyn_cast<CXXBindTemporaryExpr>(Arg->IgnoreCasts());
Cp.isInvalid() && CBTE) {
if (auto Object = Analyzer->ConstructedObjects.find(CBTE->getSubExpr());
Object != Analyzer->ConstructedObjects.end())
- Cp = CapabilityExpr(Object->second, StringRef("mutex"), false);
+ Cp = CapabilityExpr(Object->second, StringRef("mutex"), /*Neg=*/false,
+ /*Reentrant=*/false);
}
const FactEntry *Fact = FSet.findLock(Analyzer->FactMan, Cp);
if (!Fact) {
@@ -2491,7 +2571,8 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) {
}
if (UnderlyingLocks.empty())
continue;
- CapabilityExpr Cp(SxBuilder.createVariable(Param), StringRef(), false);
+ CapabilityExpr Cp(SxBuilder.createVariable(Param), StringRef(),
+ /*Neg=*/false, /*Reentrant=*/false);
auto ScopedEntry = std::make_unique<ScopedLockableFactEntry>(
Cp, Param->getLocation(), FactEntry::Declared);
for (const CapabilityExpr &M : UnderlyingLocks)
diff --git a/clang/lib/Analysis/ThreadSafetyCommon.cpp b/clang/lib/Analysis/ThreadSafetyCommon.cpp
index 13cd7e26dc16f..b31cf6234c0e3 100644
--- a/clang/lib/Analysis/ThreadSafetyCommon.cpp
+++ b/clang/lib/Analysis/ThreadSafetyCommon.cpp
@@ -81,26 +81,25 @@ static bool isCalleeArrow(const Expr *E) {
return ME ? ME->isArrow() : false;
}
-static StringRef ClassifyDiagnostic(const CapabilityAttr *A) {
- return A->getName();
-}
-
-static StringRef ClassifyDiagnostic(QualType VDT) {
+static CapabilityExpr makeCapabilityExpr(const til::SExpr *E, QualType VDT,
+ bool Neg) {
// We need to look at the declaration of the type of the value to determine
// which it is. The type should either be a record or a typedef, or a pointer
// or reference thereof.
if (const auto *RT = VDT->getAs<RecordType>()) {
if (const auto *RD = RT->getDecl())
if (const auto *CA = RD->getAttr<CapabilityAttr>())
- return ClassifyDiagnostic(CA);
+ return CapabilityExpr(E, CA->getName(), Neg,
+ RD->hasAttr<ReentrantCapabilityAttr>());
} else if (const auto *TT = VDT->getAs<TypedefType>()) {
if (const auto *TD = TT->getDecl())
if (const auto *CA = TD->getAttr<CapabilityAttr>())
- return ClassifyDiagnostic(CA);
+ return CapabilityExpr(E, CA->getName(), Neg,
+ TD->hasAttr<ReentrantCapabilityAttr>());
} else if (VDT->isPointerOrReferenceType())
- return ClassifyDiagnostic(VDT->getPointeeType());
+ return makeCapabilityExpr(E, VDT->getPointeeType(), Neg);
- return "mutex";
+ return CapabilityExpr(E, StringRef("mutex"), Neg, false);
}
/// Translate a clang expression in an attribute to a til::SExpr.
@@ -169,10 +168,8 @@ CapabilityExpr SExprBuilder::translateAttrExpr(const Expr *AttrExp,
// If the attribute has no arguments, then assume the argument is "this".
if (!AttrExp)
- return CapabilityExpr(
- Self,
- ClassifyDiagnostic(
- cast<CXXMethodDecl>(D)->getFunctionObjectParameterType()),
+ return makeCapabilityExpr(
+ Self, cast<CXXMethodDecl>(D)->getFunctionObjectParameterType(),
false);
else // For most attributes.
return translateAttrExpr(AttrExp, &Ctx);
@@ -197,7 +194,7 @@ CapabilityExpr SExprBuilder::translateAttrExpr(const Expr *AttrExp,
// The "*" expr is a universal lock, which essentially turns off
// checks until it is removed from the lockset.
return CapabilityExpr(new (Arena) til::Wildcard(), StringRef("wildcard"),
- false);
+ /*Neg=*/false, /*Reentrant=*/false);
else
// Ignore other string literals for now.
return CapabilityExpr();
@@ -217,31 +214,29 @@ CapabilityExpr SExprBuilder::translateAttrExpr(const Expr *AttrExp,
}
}
- til::SExpr *E = translate(AttrExp, Ctx);
+ const til::SExpr *E = translate(AttrExp, Ctx);
// Trap mutex expressions like nullptr, or 0.
// Any literal value is nonsense.
if (!E || isa<til::Literal>(E))
return CapabilityExpr();
- StringRef Kind = ClassifyDiagnostic(AttrExp->getType());
-
// Hack to deal with smart pointers -- strip off top-level pointer casts.
if (const auto *CE = dyn_cast<til::Cast>(E)) {
if (CE->castOpcode() == til::CAST_objToPtr)
- return CapabilityExpr(CE->expr(), Kind, Neg);
+ E = CE->expr();
}
- return CapabilityExpr(E, Kind, Neg);
+ return makeCapabilityExpr(E, AttrExp->getType(), Neg);
}
til::LiteralPtr *SExprBuilder::createVariable(const VarDecl *VD) {
return new (Arena) til::LiteralPtr(VD);
}
-std::pair<til::LiteralPtr *, StringRef>
+std::pair<til::LiteralPtr *, CapabilityExpr>
SExprBuilder::createThisPlaceholder(const Expr *Exp) {
- return {new (Arena) til::LiteralPtr(nullptr),
- ClassifyDiagnostic(Exp->getType())};
+ auto *L = new (Arena) til::LiteralPtr(nullptr);
+ return {L, makeCapabilityExpr(L, Exp->getType(), false)};
}
// Translate a clang statement or expression to a TIL expression.
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 2418aaf8de8e6..207ddfbb5f56e 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2090,6 +2090,13 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
Warnings.emplace_back(std::move(Warning), getNotes());
}
+ void handleReentrancyDepthLimit(StringRef Kind, Name LockName,
+ SourceLocation Loc) override {
+ PartialDiagnosticAt Warning(Loc, S.PDiag(diag::warn_reentrancy_depth_limit)
+ << Kind << LockName);
+ Warnings.emplace_back(std::move(Warning), getNotes());
+ }
+
void enterFunction(const FunctionDecl* FD) override {
CurrentFunction = FD;
}
diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
index 55f196625770a..4510c0b0c89c6 100644
--- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test
+++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
@@ -174,6 +174,7 @@
// CHECK-NEXT: PreserveNone (SubjectMatchRule_hasType_functionType)
// CHECK-NEXT: RandomizeLayout (SubjectMatchRule_record)
// CHECK-NEXT: ReadOnlyPlacement (SubjectMatchRule_record)
+// CHECK-NEXT: ReentrantCapability (SubjectMatchRule_record, SubjectMatchRule_type_alias)
// CHECK-NEXT: ReleaseHandle (SubjectMatchRule_variable_is_parameter)
// CHECK-NEXT: ReqdWorkGroupSize (SubjectMatchRule_function)
// CHECK-NEXT: Restrict (SubjectMatchRule_function)
diff --git a/clang/test/Sema/warn-thread-safety-analysis.c b/clang/test/Sema/warn-thread-safety-analysis.c
index c58b7bed61183..a974b5e49eafe 100644
--- a/clang/test/Sema/warn-thread-safety-analysis.c
+++ b/clang/test/Sema/warn-thread-safety-analysis.c
@@ -2,6 +2,7 @@
// RUN: %clang_cc1 -fsyntax-only -verify -Wthread-safety -Wthread-safety-pointer -Wthread-safety-beta -fexperimental-late-parse-attributes -DLATE_PARSING %s
#define LOCKABLE __attribute__ ((lockable))
+#define REENTRANT_CAPABILITY __attribute__ ((reentrant_capability))
#define SCOPED_LOCKABLE __attribute__ ((scoped_lockable))
#define GUARDED_BY(...) __attribute__ ((guarded_by(__VA_ARGS__)))
#define GUARDED_VAR __attribute__ ((guarded_var))
@@ -216,6 +217,25 @@ int main(void) {
return 0;
}
+/*** Reentrancy test ***/
+struct REENTRANT_CAPABILITY LOCKABLE ReentrantMutex {};
+void reentrant_mutex_lock(struct ReentrantMutex *mu) EXCLUSIVE_LOCK_FUNCTION(mu);
+void reentrant_mutex_unlock(struct ReentrantMutex *mu) UNLOCK_FUNCTION(mu);
+
+struct ReentrantMutex rmu;
+int r_ GUARDED_BY(&rmu);
+
+void test_reentrant(void) {
+ reentrant_mutex_lock(&rmu);
+ r_ = 1;
+ reentrant_mutex_lock(&rmu);
+ r_ = 1;
+ reentrant_mutex_unlock(&rmu);
+ r_ = 1;
+ reentrant_mutex_unlock(&rmu);
+ r_ = 1; // expected-warning{{writing variable 'r_' requires holding mutex 'rmu' exclusively}}
+}
+
// We had a problem where we'd skip all attributes that follow a late-parsed
// attribute in a single __attribute__.
void run(void) __attribute__((guarded_by(mu1), guarded_by(mu1))); // expected-warning 2{{only applies to non-static data members and global variables}}
diff --git a/clang/test/SemaCXX/thread-safety-annotations.h b/clang/test/SemaCXX/thread-safety-annotations.h
index d89bcf8ff4706..00d432da4b6f0 100644
--- a/clang/test/SemaCXX/thread-safety-annotations.h
+++ b/clang/test/SemaCXX/thread-safety-annotations.h
@@ -35,6 +35,7 @@
#define PT_GUARDED_BY(x) __attribute__((pt_guarded_by(x)))
// Common
+#define REENTRANT_CAPABILITY __attribute__((reentrant_capability))
#define SCOPED_LOCKABLE __attribute__((scoped_lockable))
#define ACQUIRED_AFTER(...) __attribute__((acquired_after(__VA_ARGS__)))
#define ACQUIRED_BEFORE(...) __attribute__((acquired_before(__VA_ARGS__)))
diff --git a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
index 2a04e820eb095..2a160cfcad00b 100644
--- a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
+++ b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
@@ -6812,3 +6812,311 @@ class PointerGuard {
}
};
} // namespace Derived_Smart_Pointer
+
+namespace Reentrancy {
+
+class REENTRANT_CAPABILITY LOCKABLE ReentrantMutex {
+ public:
+ void Lock() EXCLUSIVE_LOCK_FUNCTION();
+ void ReaderLock() SHARED_LOCK_FUNCTION();
+ void Unlock() UNLOCK_FUNCTION();
+ void ExclusiveUnlock() EXCLUSIVE_UNLOCK_FUNCTION();
+ void ReaderUnlock() SHARED_UNLOCK_FUNCTION();
+ bool TryLock() EXCLUSIVE_TRYLOCK_FUNCTION(true);
+ bool ReaderTryLock() SHARED_TRYLOCK_FUNCTION(true);
+
+ // for negative capabilities
+ const ReentrantMutex& operator!() const { return *this; }
+
+ void AssertHeld() ASSERT_EXCLUSIVE_LOCK();
+ void AssertReaderHeld() ASSERT_SHARED_LOCK();
+};
+
+class SCOPED_LOCKABLE ReentrantMutexLock {
+ public:
+ ReentrantMutexLock(ReentrantMutex *mu) EXCLUSIVE_LOCK_FUNCTION(mu);
+ ~ReentrantMutexLock() UNLOCK_FUNCTION();
+};
+
+class SCOPED_LOCKABLE ReentrantReaderMutexLock {
+ public:
+ ReentrantReaderMutexLock(ReentrantMutex *mu) SHARED_LOCK_FUNCTION(mu);
+ ~ReentrantReaderMutexLock() UNLOCK_FUNCTION();
+};
+
+class SCOPED_LOCKABLE RelockableReentrantMutexLock {
+public:
+ RelockableReentrantMutexLock(ReentrantMutex *mu) EXCLUSIVE_LOCK_FUNCTION(mu);
+ ~RelockableReentrantMutexLock() EXCLUSIVE_UNLOCK_FUNCTION();
+
+ void Lock() EXCLUSIVE_LOCK_FUNCTION();
+ void Unlock() UNLOCK_FUNCTION();
+};
+
+ReentrantMutex rmu;
+int guard_var __attribute__((guarded_var)) = 0;
+int guardby_var __attribute__((guarded_by(rmu))) = 0;
+
+void testReentrantMany() {
+ rmu.Lock();
+ rmu.Lock();
+ rmu.Lock();
+ rmu.Lock();
+ rmu.Lock();
+ rmu.Lock();
+ rmu.Lock();
+ rmu.Lock();
+ rmu.Unlock();
+ rmu.Unlock();
+ rmu.Unlock();
+ rmu.Unlock();
+ rmu.Unlock();
+ rmu.Unlock();
+ rmu.Unlock();
+ rmu.Unlock();
+}
+
+void testReentrantManyReader() {
+ rmu.ReaderLock();
+ rmu.ReaderLock();
+ rmu.ReaderLock();
+ rmu.ReaderLock();
+ rmu.ReaderLock();
+ rmu.ReaderLock();
+ rmu.ReaderLock();
+ rmu.ReaderLock();
+ rmu.ReaderUnlock();
+ rmu.ReaderUnlock();
+ rmu.ReaderUnlock();
+ rmu.ReaderUnlock();
+ rmu.ReaderUnlock();
+ rmu.ReaderUnlock();
+ rmu.ReaderUnlock();
+ rmu.ReaderUnlock();
+}
+
+void testReentrantLock1() {
+ rmu.Lock();
+ guard_var = 2;
+ rmu.Lock();
+ guard_var = 2;
+ rmu.Unlock();
+ guard_var = 2;
+ rmu.Unlock();
+ guard_var = 2; // expected-warning{{writing variable 'guard_var' requires holding any mutex exclusively}}
+}
+
+void testReentrantReaderLock1() {
+ rmu.ReaderLock();
+ int x = guard_var;
+ rmu.ReaderLock();
+ int y = guard_var;
+ rmu.ReaderUnlock();
+ int z = guard_var;
+ rmu.ReaderUnlock();
+ int a = guard_var; // expected-warning{{reading variable 'guard_var' requires holding any mutex}}
+}
+
+void testReentrantLock2() {
+ rmu.Lock();
+ guardby_var = 2;
+ rmu.Lock();
+ guardby_var = 2;
+ rmu.Unlock();
+ guardby_var = 2;
+ rmu.Unlock();
+ guardby_var = 2; // expected-warning{{writing variable 'guardby_var' requires holding mutex 'rmu' exclusively}}
+}
+
+void testReentrantReaderLock2() {
+ rmu.ReaderLock();
+ int x = guardby_var;
+ rmu.ReaderLock();
+ int y = guardby_var;
+ rmu.ReaderUnlock();
+ int z = guardby_var;
+ rmu.ReaderUnlock();
+ int a = guardby_var; // expected-warning{{reading variable 'guardby_var' requires holding mutex 'rmu'}}
+}
+
+void testReentrantTryLock1() {
+ if (rmu.TryLock()) {
+ guardby_var = 1;
+ if (rmu.TryLock()) {
+ guardby_var = 1;
+ rmu.Unlock();
+ }
+ guardby_var = 1;
+ rmu.Unlock();
+ }
+ guardby_var = 1; // expected-warning{{writing variable 'guardby_var' requires holding mutex 'rmu' exclusively}}
+}
+
+void testReentrantTryLock2() {
+ rmu.Lock();
+ guardby_var = 1;
+ if (rmu.TryLock()) {
+ guardby_var = 1;
+ rmu.Unlock();
+ }
+ guardby_var = 1;
+ rmu.Unlock();
+ guardby_var = 1; // expected-warning{{writing variable 'guardby_var' requires holding mutex 'rmu' exclusively}}
+}
+
+void testReentrantNotHeld() {
+ rmu.Unlock(); // \
+ // expected-warning{{releasing mutex 'rmu' that was not held}}
+}
+
+void testReentrantMissingUnlock() {
+ rmu.Lock(); // expected-note{{mutex acquired here}}
+ rmu.Lock(); // reenter
+ rmu.Unlock();
+} // expected-warning{{mutex 'rmu' is still held at the end of function}}
+
+// Acquiring the same capability with different access privileges is not
+// considered reentrant.
+void testMixedSharedExclusive() {
+ rmu.ReaderLock(); // expected-note{{mutex acquired here}}
+ rmu.Lock(); // expected-warning{{acquiring mutex 'rmu' that is already held}}
+ rmu.Unlock(); // expected-note{{mutex released here}}
+ rmu.ReaderUnlock(); // expected-warning{{releasing mutex 'rmu' that was not held}}
+}
+
+void testLocksRequiredReentrant() EXCLUSIVE_LOCKS_REQUIRED(rmu) {
+ guardby_var = 1;
+ rmu.Lock();
+ rmu.Lock();
+ guardby_var = 1;
+ rmu.Unlock();
+ rmu.Unlock();
+ guardby_var = 1;
+}
+
+void testAssertReentrant() {
+ rmu.AssertHeld();
+ guardby_var = 1;
+ rmu.Lock();
+ guardby_var = 1;
+ rmu.Unlock();
+ guardby_var = 1;
+}
+
+void testAssertReaderReentrant() {
+ rmu.AssertReaderHeld();
+ int x = guardby_var;
+ rmu.ReaderLock();
+ int y = guardby_var;
+ rmu.ReaderUnlock();
+ int z = guardby_var;
+}
+
+struct TestScopedReentrantLockable {
+ ReentrantMutex mu1;
+ ReentrantMutex mu2;
+ int a __attribute__((guarded_by(mu1)));
+ int b __attribute__((guarded_by(mu2)));
+
+ bool getBool();
+
+ void foo1() {
+ ReentrantMutexLock mulock1(&mu1);
+ a = 5;
+ ReentrantMutexLock mulock2(&mu1);
+ a = 5;
+ }
+
+ void foo2() {
+ ReentrantMutexLock mulock1(&mu1);
+ a = 5;
+ mu1.Lock();
+ a = 5;
+ mu1.Unlock();
+ a = 5;
+ }
+
+#ifdef __cpp_guaranteed_copy_elision
+ void const_lock() {
+ const ReentrantMutexLock mulock1 = ReentrantMutexLock(&mu1);
+ a = 5;
+ const ReentrantMutexLock mulock2 = ReentrantMutexLock(&mu1);
+ a = 3;
+ }
+#endif
+
+ void temporary() {
+ ReentrantMutexLock{&mu1}, a = 1, ReentrantMutexLock{&mu1}, a = 5;
+ }
+
+ void lifetime_extension() {
+ const ReentrantMutexLock &mulock1 = ReentrantMutexLock(&mu1);
+ a = 5;
+ const ReentrantMutexLock &mulock2 = ReentrantMutexLock(&mu1);
+ a = 5;
+ }
+
+ void foo3() {
+ ReentrantReaderMutexLock mulock1(&mu1);
+ if (getBool()) {
+ ReentrantMutexLock mulock2a(&mu2);
+ b = a + 1;
+ }
+ else {
+ ReentrantMutexLock mulock2b(&mu2);
+ b = a + 2;
+ }
+ }
+
+ void foo4() {
+ ReentrantMutexLock mulock_a(&mu1);
+ ReentrantMutexLock mulock_b(&mu1);
+ }
+
+ void temporary_double_lock() {
+ ReentrantMutexLock mulock_a(&mu1);
+ ReentrantMutexLock{&mu1};
+ }
+
+ void foo5() {
+ ReentrantMutexLock mulock1(&mu1), mulock2(&mu2);
+ {
+ ReentrantMutexLock mulock3(&mu1), mulock4(&mu2);
+ a = b+1;
+ }
+ b = a+1;
+ }
+};
+
+void scopedDoubleUnlock() {
+ RelockableReentrantMutexLock scope(&rmu);
+ scope.Unlock(); // expected-note{{mutex released here}}
+ scope.Unlock(); // expected-warning {{releasing mutex 'rmu' that was not held}}
+}
+
+void scopedDoubleLock1() {
+ RelockableReentrantMutexLock scope(&rmu);
+ scope.Lock();
+ scope.Unlock();
+}
+
+void scopedDoubleLock2() {
+ RelockableReentrantMutexLock scope(&rmu);
+ scope.Unlock();
+ scope.Lock();
+ scope.Lock();
+ scope.Unlock();
+}
+
+typedef int REENTRANT_CAPABILITY __attribute__((capability("bitlock"))) *bitlock_t;
+void bit_lock(bitlock_t l) EXCLUSIVE_LOCK_FUNCTION(l);
+void bit_unlock(bitlock_t l) UNLOCK_FUNCTION(l);
+bitlock_t bl;
+void testReentrantTypedef() {
+ bit_lock(bl);
+ bit_lock(bl);
+ bit_unlock(bl);
+ bit_unlock(bl);
+}
+
+} // namespace Reentrancy
diff --git a/clang/unittests/AST/ASTImporterTest.cpp b/clang/unittests/AST/ASTImporterTest.cpp
index 4192faee1af80..dd9e35477a8a6 100644
--- a/clang/unittests/AST/ASTImporterTest.cpp
+++ b/clang/unittests/AST/ASTImporterTest.cpp
@@ -7993,6 +7993,13 @@ TEST_P(ImportAttributes, ImportLocksExcluded) {
checkImportVariadicArg(FromAttr->args(), ToAttr->args());
}
+TEST_P(ImportAttributes, ImportReentrantCapability) {
+ ReentrantCapabilityAttr *FromAttr, *ToAttr;
+ importAttr<CXXRecordDecl>(
+ "struct __attribute__((reentrant_capability)) test {};", FromAttr,
+ ToAttr);
+}
+
TEST_P(ImportAttributes, ImportC99NoThrowAttr) {
NoThrowAttr *FromAttr, *ToAttr;
importAttr<FunctionDecl>("void test () __attribute__ ((__nothrow__));",
More information about the cfe-commits
mailing list