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 <el...@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 cadff841b5ddc812e1ae44bcd4f87960173ef7c8 Mon Sep 17 00:00:00 2001 From: Marco Elver <el...@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..bfffb58038bc0 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. Returns nullopt if reentrancy depth limit was reached. + [[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..a1b6fa37a1053 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__));", _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits