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 cff267ebdb413a4c85d61c941eca08ff4b13a191 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..710f660dccf32 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 different access privileges
+(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..f2809906b01fd 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 different access privileges 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

Reply via email to