https://github.com/melver updated https://github.com/llvm/llvm-project/pull/137133
>From c60ccbc31de8e81e6a4af915a83b8271f58f8e7e 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. --- .../Analysis/Analyses/ThreadSafetyCommon.h | 20 +++++++++++-------- clang/lib/Analysis/ThreadSafety.cpp | 4 ++-- clang/lib/Analysis/ThreadSafetyCommon.cpp | 14 ++++++------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h b/clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h index e99c5b2466334..f328d4c7f481a 100644 --- a/clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h +++ b/clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h @@ -271,26 +271,30 @@ 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; + /// 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(const til::SExpr *E, StringRef Kind, bool Neg) - : CapExpr(E, Neg), CapKind(Kind) {} + static constexpr unsigned FlagNegative = 1u << 0; + + CapabilityExpr() : CapExpr(nullptr, 0) {} + CapabilityExpr(const til::SExpr *E, StringRef Kind, unsigned Flags) + : CapExpr(E, Flags), 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, unsigned) = 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, + CapExpr.getInt() ^ FlagNegative); } bool equals(const CapabilityExpr &other) const { diff --git a/clang/lib/Analysis/ThreadSafety.cpp b/clang/lib/Analysis/ThreadSafety.cpp index 42fb0fe7dcdaa..96e79bc4dcfcc 100644 --- a/clang/lib/Analysis/ThreadSafety.cpp +++ b/clang/lib/Analysis/ThreadSafety.cpp @@ -1839,7 +1839,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 = CapabilityExpr(Placeholder.first, Placeholder.second, 0); } assert(Loc.isInvalid()); @@ -1982,7 +1982,7 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D, 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"), 0); } const FactEntry *Fact = FSet.findLock(Analyzer->FactMan, Cp); if (!Fact) { diff --git a/clang/lib/Analysis/ThreadSafetyCommon.cpp b/clang/lib/Analysis/ThreadSafetyCommon.cpp index 13cd7e26dc16f..255e6413344da 100644 --- a/clang/lib/Analysis/ThreadSafetyCommon.cpp +++ b/clang/lib/Analysis/ThreadSafetyCommon.cpp @@ -173,7 +173,7 @@ CapabilityExpr SExprBuilder::translateAttrExpr(const Expr *AttrExp, Self, ClassifyDiagnostic( cast<CXXMethodDecl>(D)->getFunctionObjectParameterType()), - false); + 0); else // For most attributes. return translateAttrExpr(AttrExp, &Ctx); } @@ -197,22 +197,22 @@ 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); + 0); else // Ignore other string literals for now. return CapabilityExpr(); } - bool Neg = false; + unsigned ExprFlags = 0; if (const auto *OE = dyn_cast<CXXOperatorCallExpr>(AttrExp)) { if (OE->getOperator() == OO_Exclaim) { - Neg = true; + ExprFlags |= CapabilityExpr::FlagNegative; AttrExp = OE->getArg(0); } } else if (const auto *UO = dyn_cast<UnaryOperator>(AttrExp)) { if (UO->getOpcode() == UO_LNot) { - Neg = true; + ExprFlags |= CapabilityExpr::FlagNegative; AttrExp = UO->getSubExpr()->IgnoreImplicit(); } } @@ -229,9 +229,9 @@ CapabilityExpr SExprBuilder::translateAttrExpr(const Expr *AttrExp, // 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); + return CapabilityExpr(CE->expr(), Kind, ExprFlags); } - return CapabilityExpr(E, Kind, Neg); + return CapabilityExpr(E, Kind, ExprFlags); } til::LiteralPtr *SExprBuilder::createVariable(const VarDecl *VD) { >From 63aca0c3434b861382d8228498af5503ffbac4c3 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 | 13 + .../Analysis/Analyses/ThreadSafetyCommon.h | 6 +- clang/include/clang/Basic/Attr.td | 7 + clang/lib/Analysis/ThreadSafety.cpp | 158 +++++---- clang/lib/Analysis/ThreadSafetyCommon.cpp | 38 ++- ...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 | 312 +++++++++++++++++- clang/unittests/AST/ASTImporterTest.cpp | 7 + 11 files changed, 481 insertions(+), 83 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index cf90218c562e2..6d2b3b288d506 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -340,6 +340,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..f1945bee4b661 100644 --- a/clang/docs/ThreadSafetyAnalysis.rst +++ b/clang/docs/ThreadSafetyAnalysis.rst @@ -434,6 +434,16 @@ 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 +--------- + +``REENTRANT`` 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. + +Note that acquiring the same capability with a different kind (exclusive vs. +shared) again is not considered reentrant by the analysis. + .. _scoped_capability: SCOPED_CAPABILITY @@ -846,6 +856,9 @@ implementation. #define CAPABILITY(x) \ THREAD_ANNOTATION_ATTRIBUTE__(capability(x)) + #define REENTRANT \ + THREAD_ANNOTATION_ATTRIBUTE__(reentrant_capability) + #define SCOPED_CAPABILITY \ THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable) diff --git a/clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h b/clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h index f328d4c7f481a..29d464f867367 100644 --- a/clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h +++ b/clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h @@ -272,13 +272,14 @@ class CFGWalker { class CapabilityExpr { private: /// 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: static constexpr unsigned FlagNegative = 1u << 0; + static constexpr unsigned FlagReentrant = 1u << 1; CapabilityExpr() : CapExpr(nullptr, 0) {} CapabilityExpr(const til::SExpr *E, StringRef Kind, unsigned Flags) @@ -291,6 +292,7 @@ class CapabilityExpr { 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, @@ -392,7 +394,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::tuple<til::LiteralPtr *, StringRef, unsigned> 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/lib/Analysis/ThreadSafety.cpp b/clang/lib/Analysis/ThreadSafety.cpp index 96e79bc4dcfcc..5b1883a0a4b15 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,21 +112,25 @@ 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 count. + unsigned int ReentrancyCount : 20; /// 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), + ReentrancyCount(0), AcquireLoc(Loc) {} virtual ~FactEntry() = default; LockKind kind() const { return LKind; } @@ -139,22 +141,41 @@ class FactEntry : public CapabilityExpr { bool declared() const { return Source == Declared; } bool managed() const { return Source == Managed; } - virtual void - handleRemovalFromIntersection(const FactSet &FSet, FactManager &FactMan, - SourceLocation JoinLoc, LockErrorKind LEK, - ThreadSafetyHandler &Handler) const = 0; + virtual void handleRemovalFromIntersection(FactSet &FSet, + FactManager &FactMan, + SourceLocation JoinLoc, + LockErrorKind LEK, + ThreadSafetyHandler &Handler) = 0; virtual void handleLock(FactSet &FSet, FactManager &FactMan, const FactEntry &entry, - ThreadSafetyHandler &Handler) const = 0; + ThreadSafetyHandler &Handler) = 0; virtual void handleUnlock(FactSet &FSet, FactManager &FactMan, const CapabilityExpr &Cp, SourceLocation UnlockLoc, - bool FullyRemove, - ThreadSafetyHandler &Handler) const = 0; + bool FullyRemove, ThreadSafetyHandler &Handler) = 0; // Return true if LKind >= LK, where exclusive > shared bool isAtLeast(LockKind LK) const { return (LKind == LK_Exclusive) || (LK == LK_Shared); } + + // Return true if we can acquire a capability reentrant. + [[nodiscard]] bool tryReenter(LockKind ReenterKind) { + if (!reentrant()) + return false; + if (kind() != ReenterKind) + return false; + if (++ReentrancyCount == 0) + llvm::report_fatal_error("Maximum reentrancy reached"); + return true; + } + + // Return true if we are releasing a capability previously acquired reentrant. + [[nodiscard]] bool leaveReentrant() { + if (!ReentrancyCount) + return false; + ReentrancyCount--; + return true; + } }; using FactID = unsigned short; @@ -163,7 +184,7 @@ using FactID = unsigned short; /// the analysis of a single routine. class FactManager { private: - std::vector<std::unique_ptr<const FactEntry>> Facts; + std::vector<std::unique_ptr<FactEntry>> Facts; public: FactID newFact(std::unique_ptr<FactEntry> Entry) { @@ -171,7 +192,7 @@ class FactManager { return static_cast<unsigned short>(Facts.size() - 1); } - const FactEntry &operator[](FactID F) const { return *Facts[F]; } + FactEntry &operator[](FactID F) { return *Facts[F]; } }; /// A FactSet is the set of facts that are known to be true at a @@ -241,23 +262,21 @@ class FactSet { }); } - const FactEntry *findLock(FactManager &FM, const CapabilityExpr &CapE) const { + FactEntry *findLock(FactManager &FM, const CapabilityExpr &CapE) { auto I = std::find_if(begin(), end(), [&](FactID ID) { return FM[ID].matches(CapE); }); return I != end() ? &FM[*I] : nullptr; } - const FactEntry *findLockUniv(FactManager &FM, - const CapabilityExpr &CapE) const { + FactEntry *findLockUniv(FactManager &FM, const CapabilityExpr &CapE) { auto I = std::find_if(begin(), end(), [&](FactID ID) -> bool { return FM[ID].matchesUniv(CapE); }); return I != end() ? &FM[*I] : nullptr; } - const FactEntry *findPartialMatch(FactManager &FM, - const CapabilityExpr &CapE) const { + FactEntry *findPartialMatch(FactManager &FM, const CapabilityExpr &CapE) { auto I = std::find_if(begin(), end(), [&](FactID ID) -> bool { return FM[ID].partiallyMatches(CapE); }); @@ -864,10 +883,9 @@ class LockableFactEntry : public FactEntry { SourceKind Src = Acquired) : FactEntry(Lockable, CE, LK, Loc, Src) {} - void - handleRemovalFromIntersection(const FactSet &FSet, FactManager &FactMan, - SourceLocation JoinLoc, LockErrorKind LEK, - ThreadSafetyHandler &Handler) const override { + void handleRemovalFromIntersection(FactSet &FSet, FactManager &FactMan, + SourceLocation JoinLoc, LockErrorKind LEK, + ThreadSafetyHandler &Handler) override { if (!asserted() && !negative() && !isUniversal()) { Handler.handleMutexHeldEndOfScope(getKind(), toString(), loc(), JoinLoc, LEK); @@ -875,15 +893,18 @@ class LockableFactEntry : public FactEntry { } void handleLock(FactSet &FSet, FactManager &FactMan, const FactEntry &entry, - ThreadSafetyHandler &Handler) const override { + ThreadSafetyHandler &Handler) override { + if (tryReenter(entry.kind())) + return; 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 { + bool FullyRemove, ThreadSafetyHandler &Handler) override { + if (leaveReentrant()) + return; FSet.removeLock(FactMan, Cp); if (!Cp.negative()) { FSet.addLock(FactMan, std::make_unique<LockableFactEntry>( @@ -935,10 +956,9 @@ class ScopedLockableFactEntry : public FactEntry { UnderlyingMutexes.push_back(UnderlyingCapability{M, UCK_ReleasedShared}); } - void - handleRemovalFromIntersection(const FactSet &FSet, FactManager &FactMan, - SourceLocation JoinLoc, LockErrorKind LEK, - ThreadSafetyHandler &Handler) const override { + void handleRemovalFromIntersection(FactSet &FSet, FactManager &FactMan, + SourceLocation JoinLoc, LockErrorKind LEK, + ThreadSafetyHandler &Handler) override { if (LEK == LEK_LockedAtEndOfFunction || LEK == LEK_NotLockedAtEndOfFunction) return; @@ -956,7 +976,7 @@ class ScopedLockableFactEntry : public FactEntry { } void handleLock(FactSet &FSet, FactManager &FactMan, const FactEntry &entry, - ThreadSafetyHandler &Handler) const override { + ThreadSafetyHandler &Handler) override { for (const auto &UnderlyingMutex : UnderlyingMutexes) { if (UnderlyingMutex.Kind == UCK_Acquired) lock(FSet, FactMan, UnderlyingMutex.Cap, entry.kind(), entry.loc(), @@ -968,8 +988,7 @@ class ScopedLockableFactEntry : public FactEntry { void handleUnlock(FactSet &FSet, FactManager &FactMan, const CapabilityExpr &Cp, SourceLocation UnlockLoc, - bool FullyRemove, - ThreadSafetyHandler &Handler) const override { + bool FullyRemove, ThreadSafetyHandler &Handler) override { assert(!Cp.negative() && "Managing object cannot be negative."); for (const auto &UnderlyingMutex : UnderlyingMutexes) { // Remove/lock the underlying mutex if it exists/is still unlocked; warn @@ -996,8 +1015,8 @@ 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) + if (FactEntry *Fact = FSet.findLock(FactMan, Cp)) { + if (!Fact->tryReenter(kind) && Handler) Handler->handleDoubleLock(Cp.getKind(), Cp.toString(), Fact->loc(), loc); } else { @@ -1009,7 +1028,9 @@ class ScopedLockableFactEntry : public FactEntry { void unlock(FactSet &FSet, FactManager &FactMan, const CapabilityExpr &Cp, SourceLocation loc, ThreadSafetyHandler *Handler) const { - if (FSet.findLock(FactMan, Cp)) { + if (FactEntry *Fact = FSet.findLock(FactMan, Cp)) { + if (Fact->leaveReentrant()) + return; FSet.removeLock(FactMan, Cp); FSet.addLock(FactMan, std::make_unique<LockableFactEntry>( !Cp, LK_Exclusive, loc)); @@ -1071,28 +1092,28 @@ class ThreadSafetyAnalyzer { bool join(const FactEntry &a, const FactEntry &b, bool CanModify); - void intersectAndWarn(FactSet &EntrySet, const FactSet &ExitSet, + void intersectAndWarn(FactSet &EntrySet, FactSet &ExitSet, SourceLocation JoinLoc, LockErrorKind EntryLEK, LockErrorKind ExitLEK); - void intersectAndWarn(FactSet &EntrySet, const FactSet &ExitSet, + void intersectAndWarn(FactSet &EntrySet, FactSet &ExitSet, SourceLocation JoinLoc, LockErrorKind LEK) { intersectAndWarn(EntrySet, ExitSet, JoinLoc, LEK, LEK); } void runAnalysis(AnalysisDeclContext &AC); - void warnIfMutexNotHeld(const FactSet &FSet, const NamedDecl *D, - const Expr *Exp, AccessKind AK, Expr *MutexExp, + void warnIfMutexNotHeld(FactSet &FSet, const NamedDecl *D, const Expr *Exp, + AccessKind AK, Expr *MutexExp, ProtectedOperationKind POK, til::LiteralPtr *Self, SourceLocation Loc); - void warnIfMutexHeld(const FactSet &FSet, const NamedDecl *D, const Expr *Exp, + void warnIfMutexHeld(FactSet &FSet, const NamedDecl *D, const Expr *Exp, Expr *MutexExp, til::LiteralPtr *Self, SourceLocation Loc); - void checkAccess(const FactSet &FSet, const Expr *Exp, AccessKind AK, + void checkAccess(FactSet &FSet, const Expr *Exp, AccessKind AK, ProtectedOperationKind POK); - void checkPtAccess(const FactSet &FSet, const Expr *Exp, AccessKind AK, + void checkPtAccess(FactSet &FSet, const Expr *Exp, AccessKind AK, ProtectedOperationKind POK); }; @@ -1306,8 +1327,7 @@ 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 (FactEntry *Cp = FSet.findLock(FactMan, *Entry)) { if (!Entry->asserted()) Cp->handleLock(FSet, FactMan, *Entry, Handler); } else { @@ -1323,7 +1343,7 @@ void ThreadSafetyAnalyzer::removeLock(FactSet &FSet, const CapabilityExpr &Cp, if (Cp.shouldIgnore()) return; - const FactEntry *LDat = FSet.findLock(FactMan, Cp); + FactEntry *LDat = FSet.findLock(FactMan, Cp); if (!LDat) { SourceLocation PrevLoc; if (const FactEntry *Neg = FSet.findLock(FactMan, !Cp)) @@ -1546,7 +1566,7 @@ class BuildLockset : public ConstStmtVisitor<BuildLockset> { ThreadSafetyAnalyzer *Analyzer; FactSet FSet; // The fact set for the function on exit. - const FactSet &FunctionExitFSet; + FactSet &FunctionExitFSet; LocalVariableMap::Context LVarCtx; unsigned CtxIndex; @@ -1571,7 +1591,7 @@ class BuildLockset : public ConstStmtVisitor<BuildLockset> { public: BuildLockset(ThreadSafetyAnalyzer *Anlzr, CFGBlockInfo &Info, - const FactSet &FunctionExitFSet) + FactSet &FunctionExitFSet) : ConstStmtVisitor<BuildLockset>(), Analyzer(Anlzr), FSet(Info.EntrySet), FunctionExitFSet(FunctionExitFSet), LVarCtx(Info.EntryContext), CtxIndex(Info.EntryIndex) {} @@ -1590,10 +1610,12 @@ class BuildLockset : public ConstStmtVisitor<BuildLockset> { /// Warn if the LSet does not contain a lock sufficient to protect access /// of at least the passed in AccessKind. -void ThreadSafetyAnalyzer::warnIfMutexNotHeld( - const FactSet &FSet, const NamedDecl *D, const Expr *Exp, AccessKind AK, - Expr *MutexExp, ProtectedOperationKind POK, til::LiteralPtr *Self, - SourceLocation Loc) { +void ThreadSafetyAnalyzer::warnIfMutexNotHeld(FactSet &FSet, const NamedDecl *D, + const Expr *Exp, AccessKind AK, + Expr *MutexExp, + ProtectedOperationKind POK, + til::LiteralPtr *Self, + SourceLocation Loc) { LockKind LK = getLockKindFromAccessKind(AK); CapabilityExpr Cp = SxBuilder.translateAttrExpr(MutexExp, D, Exp, Self); if (Cp.isInvalid()) { @@ -1649,9 +1671,8 @@ void ThreadSafetyAnalyzer::warnIfMutexNotHeld( } /// Warn if the LSet contains the given lock. -void ThreadSafetyAnalyzer::warnIfMutexHeld(const FactSet &FSet, - const NamedDecl *D, const Expr *Exp, - Expr *MutexExp, +void ThreadSafetyAnalyzer::warnIfMutexHeld(FactSet &FSet, const NamedDecl *D, + const Expr *Exp, Expr *MutexExp, til::LiteralPtr *Self, SourceLocation Loc) { CapabilityExpr Cp = SxBuilder.translateAttrExpr(MutexExp, D, Exp, Self); @@ -1674,7 +1695,7 @@ void ThreadSafetyAnalyzer::warnIfMutexHeld(const FactSet &FSet, /// marked with guarded_by, we must ensure the appropriate mutexes are held. /// Similarly, we check if the access is to an expression that dereferences /// a pointer marked with pt_guarded_by. -void ThreadSafetyAnalyzer::checkAccess(const FactSet &FSet, const Expr *Exp, +void ThreadSafetyAnalyzer::checkAccess(FactSet &FSet, const Expr *Exp, AccessKind AK, ProtectedOperationKind POK) { Exp = Exp->IgnoreImplicit()->IgnoreParenCasts(); @@ -1741,7 +1762,7 @@ void ThreadSafetyAnalyzer::checkAccess(const FactSet &FSet, const Expr *Exp, /// Checks pt_guarded_by and pt_guarded_var attributes. /// POK is the same operationKind that was passed to checkAccess. -void ThreadSafetyAnalyzer::checkPtAccess(const FactSet &FSet, const Expr *Exp, +void ThreadSafetyAnalyzer::checkPtAccess(FactSet &FSet, const Expr *Exp, AccessKind AK, ProtectedOperationKind POK) { // Strip off paren- and cast-expressions, checking if we encounter any other @@ -1831,15 +1852,15 @@ 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 = - Analyzer->SxBuilder.createThisPlaceholder(Exp); + auto Placeholder = Analyzer->SxBuilder.createThisPlaceholder(Exp); [[maybe_unused]] auto inserted = - Analyzer->ConstructedObjects.insert({Exp, Placeholder.first}); + Analyzer->ConstructedObjects.insert({Exp, std::get<0>(Placeholder)}); assert(inserted.second && "Are we visiting the same expression again?"); if (isa<CXXConstructExpr>(Exp)) - Self = Placeholder.first; + Self = std::get<0>(Placeholder); if (TagT->getDecl()->hasAttr<ScopedLockableAttr>()) - Scp = CapabilityExpr(Placeholder.first, Placeholder.second, 0); + Scp = CapabilityExpr(std::get<0>(Placeholder), std::get<1>(Placeholder), + std::get<2>(Placeholder)); } assert(Loc.isInvalid()); @@ -2314,8 +2335,7 @@ bool ThreadSafetyAnalyzer::join(const FactEntry &A, const FactEntry &B, /// \param JoinLoc The location of the join point for error reporting /// \param EntryLEK The warning if a mutex is missing from \p EntrySet. /// \param ExitLEK The warning if a mutex is missing from \p ExitSet. -void ThreadSafetyAnalyzer::intersectAndWarn(FactSet &EntrySet, - const FactSet &ExitSet, +void ThreadSafetyAnalyzer::intersectAndWarn(FactSet &EntrySet, FactSet &ExitSet, SourceLocation JoinLoc, LockErrorKind EntryLEK, LockErrorKind ExitLEK) { @@ -2323,7 +2343,7 @@ void ThreadSafetyAnalyzer::intersectAndWarn(FactSet &EntrySet, // Find locks in ExitSet that conflict or are not in EntrySet, and warn. for (const auto &Fact : ExitSet) { - const FactEntry &ExitFact = FactMan[Fact]; + FactEntry &ExitFact = FactMan[Fact]; FactSet::iterator EntryIt = EntrySet.findLockIter(FactMan, ExitFact); if (EntryIt != EntrySet.end()) { @@ -2338,7 +2358,7 @@ void ThreadSafetyAnalyzer::intersectAndWarn(FactSet &EntrySet, // Find locks in EntrySet that are not in ExitSet, and remove them. for (const auto &Fact : EntrySetOrig) { - const FactEntry *EntryFact = &FactMan[Fact]; + FactEntry *EntryFact = &FactMan[Fact]; const FactEntry *ExitFact = ExitSet.findLock(FactMan, *EntryFact); if (!ExitFact) { diff --git a/clang/lib/Analysis/ThreadSafetyCommon.cpp b/clang/lib/Analysis/ThreadSafetyCommon.cpp index 255e6413344da..ca97d6a0a48af 100644 --- a/clang/lib/Analysis/ThreadSafetyCommon.cpp +++ b/clang/lib/Analysis/ThreadSafetyCommon.cpp @@ -103,6 +103,23 @@ static StringRef ClassifyDiagnostic(QualType VDT) { return "mutex"; } +static unsigned getCapabilityExprFlags(QualType VDT) { + unsigned Flags = 0; + + if (const auto *RT = VDT->getAs<RecordType>()) { + if (const auto *RD = RT->getDecl()) + if (RD->hasAttr<ReentrantCapabilityAttr>()) + Flags |= CapabilityExpr::FlagReentrant; + } else if (const auto *TT = VDT->getAs<TypedefType>()) { + if (const auto *TD = TT->getDecl()) + if (TD->hasAttr<ReentrantCapabilityAttr>()) + Flags |= CapabilityExpr::FlagReentrant; + } else if (VDT->isPointerOrReferenceType()) + return getCapabilityExprFlags(VDT->getPointeeType()); + + return Flags; +} + /// Translate a clang expression in an attribute to a til::SExpr. /// Constructs the context from D, DeclExp, and SelfDecl. /// @@ -168,14 +185,13 @@ CapabilityExpr SExprBuilder::translateAttrExpr(const Expr *AttrExp, Ctx.FunArgs = Self; // If the attribute has no arguments, then assume the argument is "this". - if (!AttrExp) - return CapabilityExpr( - Self, - ClassifyDiagnostic( - cast<CXXMethodDecl>(D)->getFunctionObjectParameterType()), - 0); - else // For most attributes. + if (!AttrExp) { + QualType Ty = cast<CXXMethodDecl>(D)->getFunctionObjectParameterType(); + return CapabilityExpr(Self, ClassifyDiagnostic(Ty), + getCapabilityExprFlags(Ty)); + } else { // For most attributes. return translateAttrExpr(AttrExp, &Ctx); + } } // If the attribute has no arguments, then assume the argument is "this". @@ -203,7 +219,8 @@ CapabilityExpr SExprBuilder::translateAttrExpr(const Expr *AttrExp, return CapabilityExpr(); } - unsigned ExprFlags = 0; + unsigned ExprFlags = getCapabilityExprFlags(AttrExp->getType()); + if (const auto *OE = dyn_cast<CXXOperatorCallExpr>(AttrExp)) { if (OE->getOperator() == OO_Exclaim) { ExprFlags |= CapabilityExpr::FlagNegative; @@ -238,10 +255,11 @@ til::LiteralPtr *SExprBuilder::createVariable(const VarDecl *VD) { return new (Arena) til::LiteralPtr(VD); } -std::pair<til::LiteralPtr *, StringRef> +std::tuple<til::LiteralPtr *, StringRef, unsigned> SExprBuilder::createThisPlaceholder(const Expr *Exp) { return {new (Arena) til::LiteralPtr(nullptr), - ClassifyDiagnostic(Exp->getType())}; + ClassifyDiagnostic(Exp->getType()), + getCapabilityExprFlags(Exp->getType())}; } // Translate a clang statement or expression to a TIL expression. 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..dde0f481709f9 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 __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 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..2faeffdb7fdc0 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 __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 ac3ca5e0c12a8..dbcc1ddde44b6 100644 --- a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp +++ b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp @@ -6708,7 +6708,7 @@ int testAdoptShared() { } // namespace ReturnScopedLockable -#endif +#endif // __cpp_guaranteed_copy_elision namespace PR38640 { void f() { @@ -6716,7 +6716,7 @@ void f() { // safety analysis was enabled. int &i = i; // expected-warning {{reference 'i' is not yet bound to a value when used within its own initialization}} } -} +} // namespace PR38640 namespace Derived_Smart_Pointer { template <class T> @@ -6811,4 +6811,312 @@ class PointerGuard { mu1.Unlock(); } }; +} // namespace Derived_Smart_Pointer + +namespace Reentrancy { + +class REENTRANT 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 a different kind is not considered +// reentrant. +void testMixedReaderExclusive() { + 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 __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