https://github.com/melver updated 
https://github.com/llvm/llvm-project/pull/137133

>From a8319028f08192ca6140beed7f27a83a829c6d84 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 427dcd2c396fbd454129abb3d3f2d572f87eadbc 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    |  13 +-
 .../Analysis/Analyses/ThreadSafetyCommon.h    |  21 +-
 clang/include/clang/Basic/Attr.td             |   7 +
 .../clang/Basic/DiagnosticSemaKinds.td        |   6 +
 clang/lib/Analysis/ThreadSafety.cpp           | 134 +++++--
 clang/lib/Analysis/ThreadSafetyCommon.cpp     |  66 ++--
 clang/lib/Sema/AnalysisBasedWarnings.cpp      |   3 +
 clang/lib/Sema/SemaDeclAttr.cpp               |  10 +
 ...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   | 356 ++++++++++++++++++
 .../SemaCXX/warn-thread-safety-parsing.cpp    |   7 +
 clang/unittests/AST/ASTImporterTest.cpp       |   9 +
 16 files changed, 598 insertions(+), 75 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 80399e458aec3..2a6565e37e256 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -369,6 +369,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..7e0e25b9aab1d 100644
--- a/clang/include/clang/Analysis/Analyses/ThreadSafety.h
+++ b/clang/include/clang/Analysis/Analyses/ThreadSafety.h
@@ -94,16 +94,17 @@ enum AccessKind {
 
 /// This enum distinguishes between different situations where we warn due to
 /// inconsistent locking.
-/// \enum SK_LockedSomeLoopIterations -- a mutex is locked for some but not all
-/// loop iterations.
-/// \enum SK_LockedSomePredecessors -- a mutex is locked in some but not all
-/// predecessors of a CFGBlock.
-/// \enum SK_LockedAtEndOfFunction -- a mutex is still locked at the end of a
-/// function.
 enum LockErrorKind {
+  /// A capability is locked for some but not all loop iterations.
   LEK_LockedSomeLoopIterations,
+  /// A capability is locked in some but not all predecessors of a CFGBlock.
   LEK_LockedSomePredecessors,
+  /// A capability is still locked at the end of a function.
   LEK_LockedAtEndOfFunction,
+  /// A capability is locked with mismatching reentrancy depth in predecessors
+  /// of a CFGBlock.
+  LEK_LockedReentrancyMismatch,
+  /// Expecting a capability to be held at the end of function.
   LEK_NotLockedAtEndOfFunction
 };
 
diff --git a/clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h 
b/clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h
index 6e46a2d721463..bebd03deb2872 100644
--- a/clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h
+++ b/clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h
@@ -22,6 +22,7 @@
 #define LLVM_CLANG_ANALYSIS_ANALYSES_THREADSAFETYCOMMON_H
 
 #include "clang/AST/Decl.h"
+#include "clang/AST/Type.h"
 #include "clang/Analysis/Analyses/PostOrderCFGView.h"
 #include "clang/Analysis/Analyses/ThreadSafetyTIL.h"
 #include "clang/Analysis/Analyses/ThreadSafetyTraverse.h"
@@ -272,27 +273,33 @@ 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) {}
+  CapabilityExpr(const til::SExpr *E, QualType QT, bool Neg);
 
   // 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 {
@@ -389,10 +396,6 @@ class SExprBuilder {
   // Translate a variable reference.
   til::LiteralPtr *createVariable(const VarDecl *VD);
 
-  // Create placeholder for this: we don't know the VarDecl on construction 
yet.
-  std::pair<til::LiteralPtr *, StringRef>
-  createThisPlaceholder(const Expr *Exp);
-
   // Translate a clang statement or expression to a TIL expression.
   // Also performs substitution of variables; Ctx provides the context.
   // Dispatches on the type of S.
diff --git a/clang/include/clang/Basic/Attr.td 
b/clang/include/clang/Basic/Attr.td
index dcdcff8c46fe2..6e38b11a816af 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4003,6 +4003,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 ad5bf26be2590..f6bb0be71ca7e 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -4045,6 +4045,9 @@ def warn_thread_attribute_not_on_scoped_lockable_param : 
Warning<
   "%0 attribute applies to function parameters only if their type is a "
   "reference to a 'scoped_lockable'-annotated type">,
   InGroup<ThreadSafetyAttributes>, DefaultIgnore;
+def warn_reentrant_capability_without_capability : Warning<
+  "ignoring %0 attribute on %1 without 'capability' attribute">,
+  InGroup<ThreadSafetyAttributes>, DefaultIgnore;
 def err_attribute_argument_out_of_bounds_extra_info : Error<
   "%0 attribute parameter %1 is out of bounds: "
   "%plural{0:no parameters to index into|"
@@ -4073,6 +4076,9 @@ def warn_lock_some_predecessors : Warning<
 def warn_expecting_lock_held_on_loop : Warning<
   "expecting %0 '%1' to be held at start of each loop">,
   InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
+def warn_lock_reentrancy_mismatch : Warning<
+  "expecting %0 '%1' to be held with matching reentrancy depth on every path 
through here">,
+  InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
 def note_locked_here : Note<"%0 acquired here">;
 def note_unlocked_here : Note<"%0 released here">;
 def warn_lock_exclusive_and_shared : Warning<
diff --git a/clang/lib/Analysis/ThreadSafety.cpp 
b/clang/lib/Analysis/ThreadSafety.cpp
index 7e86af6b4a317..e02443a09ff50 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 };
@@ -151,6 +149,19 @@ class FactEntry : public CapabilityExpr {
                             bool FullyRemove,
                             ThreadSafetyHandler &Handler) const = 0;
 
+  // Return an updated FactEntry if we can acquire this capability reentrant,
+  // nullptr otherwise.
+  virtual std::unique_ptr<FactEntry> tryReenter(LockKind ReenterKind) const {
+    return nullptr;
+  }
+
+  // Return an updated FactEntry if we are releasing a capability previously
+  // acquired reentrant, nullptr otherwise.
+  virtual std::unique_ptr<FactEntry> leaveReentrant() const { return nullptr; }
+
+  // Return the reentrancy depth.
+  virtual unsigned int getReentrancyDepth() const { return 0; }
+
   // Return true if LKind >= LK, where exclusive > shared
   bool isAtLeast(LockKind LK) const {
     return  (LKind == LK_Exclusive) || (LK == LK_Shared);
@@ -168,6 +179,8 @@ class FactManager {
 public:
   FactID newFact(std::unique_ptr<FactEntry> Entry) {
     Facts.push_back(std::move(Entry));
+    assert(Facts.size() - 1 <= std::numeric_limits<FactID>::max() &&
+           "FactID space exhausted");
     return static_cast<unsigned short>(Facts.size() - 1);
   }
 
@@ -235,6 +248,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);
@@ -859,6 +886,10 @@ static void findBlockLocations(CFG *CFGraph,
 namespace {
 
 class LockableFactEntry : public FactEntry {
+private:
+  /// Reentrancy depth.
+  unsigned int ReentrancyDepth = 0;
+
 public:
   LockableFactEntry(const CapabilityExpr &CE, LockKind LK, SourceLocation Loc,
                     SourceKind Src = Acquired)
@@ -876,14 +907,25 @@ 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> RFact = tryReenter(entry.kind())) {
+      // This capability has been reentrantly acquired.
+      FSet.replaceLock(FactMan, entry, std::move(RFact));
+    } 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> RFact = leaveReentrant()) {
+      // This capability remains reentrantly acquired.
+      FSet.replaceLock(FactMan, Cp, std::move(RFact));
+      return;
+    }
+
     FSet.removeLock(FactMan, Cp);
     if (!Cp.negative()) {
       FSet.addLock(FactMan, std::make_unique<LockableFactEntry>(
@@ -894,6 +936,30 @@ class LockableFactEntry : public FactEntry {
   static bool classof(const FactEntry *A) {
     return A->getFactEntryKind() == Lockable;
   }
+
+  std::unique_ptr<FactEntry> tryReenter(LockKind ReenterKind) const override {
+    if (!reentrant())
+      return nullptr;
+    if (kind() != ReenterKind)
+      return nullptr;
+    auto NewFact = std::make_unique<LockableFactEntry>(*this);
+    NewFact->ReentrancyDepth++;
+    // With ReentrancyDepth being an unsigned int, and FactID being an unsigned
+    // short, it should currently not be possible that ReentrancyDepth would
+    // overflow before FactID overflows.
+    assert(NewFact->ReentrancyDepth != 0 && "Reentrancy depth overflow");
+    return NewFact;
+  }
+
+  std::unique_ptr<FactEntry> leaveReentrant() const override {
+    if (!ReentrancyDepth)
+      return nullptr;
+    auto NewFact = std::make_unique<LockableFactEntry>(*this);
+    NewFact->ReentrancyDepth--;
+    return NewFact;
+  }
+
+  unsigned int getReentrancyDepth() const override { return ReentrancyDepth; }
 };
 
 class ScopedLockableFactEntry : public FactEntry {
@@ -996,10 +1062,14 @@ 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> RFact = Fact.tryReenter(kind)) {
+        // This capability has been reentrantly acquired.
+        FSet.replaceLock(FactMan, It, std::move(RFact));
+      } else if (Handler) {
+        Handler->handleDoubleLock(Cp.getKind(), Cp.toString(), Fact.loc(), 
loc);
+      }
     } else {
       FSet.removeLock(FactMan, !Cp);
       FSet.addLock(FactMan,
@@ -1009,7 +1079,14 @@ 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> RFact = Fact.leaveReentrant()) {
+        // This capability remains reentrantly acquired.
+        FSet.replaceLock(FactMan, It, std::move(RFact));
+        return;
+      }
+
       FSet.removeLock(FactMan, Cp);
       FSet.addLock(FactMan,
                    std::make_unique<LockableFactEntry>(!Cp, LK_Exclusive, 
loc));
@@ -1287,7 +1364,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);
@@ -1812,15 +1888,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);
+      til::LiteralPtr *Placeholder =
+          Analyzer->SxBuilder.createVariable(nullptr);
       [[maybe_unused]] auto inserted =
-          Analyzer->ConstructedObjects.insert({Exp, Placeholder.first});
+          Analyzer->ConstructedObjects.insert({Exp, Placeholder});
       assert(inserted.second && "Are we visiting the same expression again?");
       if (isa<CXXConstructExpr>(Exp))
-        Self = Placeholder.first;
+        Self = Placeholder;
       if (TagT->getDecl()->hasAttr<ScopedLockableAttr>())
-        Scp = CapabilityExpr(Placeholder.first, Placeholder.second, false);
+        Scp = CapabilityExpr(Placeholder, Exp->getType(), /*Neg=*/false);
     }
 
     assert(Loc.isInvalid());
@@ -1958,12 +2034,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) {
@@ -2303,14 +2380,24 @@ void ThreadSafetyAnalyzer::intersectAndWarn(FactSet 
&EntrySet,
   FactSet EntrySetOrig = EntrySet;
 
   // Find locks in ExitSet that conflict or are not in EntrySet, and warn.
-  for (const auto &Fact : ExitSet) {
-    const FactEntry &ExitFact = FactMan[Fact];
+  for (const auto &ExitID : ExitSet) {
+    const FactEntry &ExitFact = FactMan[ExitID];
 
     FactSet::iterator EntryIt = EntrySet.findLockIter(FactMan, ExitFact);
     if (EntryIt != EntrySet.end()) {
-      if (join(FactMan[*EntryIt], ExitFact,
-               EntryLEK != LEK_LockedSomeLoopIterations))
-        *EntryIt = Fact;
+      const FactEntry &EntryFact = FactMan[*EntryIt];
+      if (join(EntryFact, ExitFact, EntryLEK != LEK_LockedSomeLoopIterations))
+        *EntryIt = ExitID;
+
+      if (EntryFact.getReentrancyDepth() != ExitFact.getReentrancyDepth()) {
+        // Pick the FactEntry with the greater reentrancy depth as the "good"
+        // fact to reduce potential later warnings.
+        if (EntryFact.getReentrancyDepth() < ExitFact.getReentrancyDepth())
+          *EntryIt = ExitID;
+        Handler.handleMutexHeldEndOfScope(
+            ExitFact.getKind(), ExitFact.toString(), ExitFact.loc(), JoinLoc,
+            LEK_LockedReentrancyMismatch);
+      }
     } else if (!ExitFact.managed() || EntryLEK == LEK_LockedAtEndOfFunction) {
       ExitFact.handleRemovalFromIntersection(ExitSet, FactMan, JoinLoc,
                                              EntryLEK, Handler);
@@ -2472,7 +2559,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..623c81b44f488 100644
--- a/clang/lib/Analysis/ThreadSafetyCommon.cpp
+++ b/clang/lib/Analysis/ThreadSafetyCommon.cpp
@@ -81,28 +81,6 @@ 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) {
-  // 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);
-  } else if (const auto *TT = VDT->getAs<TypedefType>()) {
-    if (const auto *TD = TT->getDecl())
-      if (const auto *CA = TD->getAttr<CapabilityAttr>())
-        return ClassifyDiagnostic(CA);
-  } else if (VDT->isPointerOrReferenceType())
-    return ClassifyDiagnostic(VDT->getPointeeType());
-
-  return "mutex";
-}
-
 /// Translate a clang expression in an attribute to a til::SExpr.
 /// Constructs the context from D, DeclExp, and SelfDecl.
 ///
@@ -170,9 +148,7 @@ 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()),
+          Self, cast<CXXMethodDecl>(D)->getFunctionObjectParameterType(),
           false);
     else  // For most attributes.
       return translateAttrExpr(AttrExp, &Ctx);
@@ -197,7 +173,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,33 +193,25 @@ 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 CapabilityExpr(E, AttrExp->getType(), Neg);
 }
 
 til::LiteralPtr *SExprBuilder::createVariable(const VarDecl *VD) {
   return new (Arena) til::LiteralPtr(VD);
 }
 
-std::pair<til::LiteralPtr *, StringRef>
-SExprBuilder::createThisPlaceholder(const Expr *Exp) {
-  return {new (Arena) til::LiteralPtr(nullptr),
-          ClassifyDiagnostic(Exp->getType())};
-}
-
 // Translate a clang statement or expression to a TIL expression.
 // Also performs substitution of variables; Ctx provides the context.
 // Dispatches on the type of S.
@@ -1011,6 +979,30 @@ void SExprBuilder::exitCFG(const CFGBlock *Last) {
   IncompleteArgs.clear();
 }
 
+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 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 CapabilityExpr(E, CA->getName(), Neg,
+                              TD->hasAttr<ReentrantCapabilityAttr>());
+  } else if (VDT->isPointerOrReferenceType())
+    return makeCapabilityExpr(E, VDT->getPointeeType(), Neg);
+
+  return CapabilityExpr(E, StringRef("mutex"), Neg, false);
+}
+
+CapabilityExpr::CapabilityExpr(const til::SExpr *E, QualType QT, bool Neg)
+    : CapabilityExpr(makeCapabilityExpr(E, QT, Neg)) {}
+
 #ifndef NDEBUG
 namespace {
 
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp 
b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 2418aaf8de8e6..9cbe3e8b615b6 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -1918,6 +1918,9 @@ class ThreadSafetyReporter : public 
clang::threadSafety::ThreadSafetyHandler {
       case LEK_LockedAtEndOfFunction:
         DiagID = diag::warn_no_unlock;
         break;
+      case LEK_LockedReentrancyMismatch:
+        DiagID = diag::warn_lock_reentrancy_mismatch;
+        break;
       case LEK_NotLockedAtEndOfFunction:
         DiagID = diag::warn_expecting_locked;
         break;
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 413999b95b998..92ae99177948c 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -7784,6 +7784,16 @@ void Sema::ProcessDeclAttributeList(
     Diag(D->getLocation(), diag::err_designated_init_attr_non_init);
     D->dropAttr<ObjCDesignatedInitializerAttr>();
   }
+
+  // Do not permit 'reentrant_capability' without 'capability' attribute.
+  if (const auto *A = D->getAttr<ReentrantCapabilityAttr>()) {
+    if (!D->hasAttr<CapabilityAttr>()) {
+      Diag(AttrList.begin()->getLoc(),
+           diag::warn_reentrant_capability_without_capability)
+          << A << cast<NamedDecl>(D);
+      D->dropAttr<ReentrantCapabilityAttr>();
+    }
+  }
 }
 
 void Sema::ProcessDeclAttributeDelayed(Decl *D,
diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test 
b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
index 55f196625770a..4510c0b0c89c6 100644
--- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test
+++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
@@ -174,6 +174,7 @@
 // CHECK-NEXT: PreserveNone (SubjectMatchRule_hasType_functionType)
 // CHECK-NEXT: RandomizeLayout (SubjectMatchRule_record)
 // CHECK-NEXT: ReadOnlyPlacement (SubjectMatchRule_record)
+// CHECK-NEXT: ReentrantCapability (SubjectMatchRule_record, 
SubjectMatchRule_type_alias)
 // CHECK-NEXT: ReleaseHandle (SubjectMatchRule_variable_is_parameter)
 // CHECK-NEXT: ReqdWorkGroupSize (SubjectMatchRule_function)
 // CHECK-NEXT: Restrict (SubjectMatchRule_function)
diff --git a/clang/test/Sema/warn-thread-safety-analysis.c 
b/clang/test/Sema/warn-thread-safety-analysis.c
index c58b7bed61183..a974b5e49eafe 100644
--- a/clang/test/Sema/warn-thread-safety-analysis.c
+++ b/clang/test/Sema/warn-thread-safety-analysis.c
@@ -2,6 +2,7 @@
 // RUN: %clang_cc1 -fsyntax-only -verify -Wthread-safety 
-Wthread-safety-pointer -Wthread-safety-beta 
-fexperimental-late-parse-attributes -DLATE_PARSING %s
 
 #define LOCKABLE            __attribute__ ((lockable))
+#define REENTRANT_CAPABILITY __attribute__ ((reentrant_capability))
 #define SCOPED_LOCKABLE     __attribute__ ((scoped_lockable))
 #define GUARDED_BY(...)     __attribute__ ((guarded_by(__VA_ARGS__)))
 #define GUARDED_VAR         __attribute__ ((guarded_var))
@@ -216,6 +217,25 @@ int main(void) {
   return 0;
 }
 
+/*** Reentrancy test ***/
+struct REENTRANT_CAPABILITY LOCKABLE ReentrantMutex {};
+void reentrant_mutex_lock(struct ReentrantMutex *mu) 
EXCLUSIVE_LOCK_FUNCTION(mu);
+void reentrant_mutex_unlock(struct ReentrantMutex *mu) UNLOCK_FUNCTION(mu);
+
+struct ReentrantMutex rmu;
+int r_ GUARDED_BY(&rmu);
+
+void test_reentrant(void) {
+  reentrant_mutex_lock(&rmu);
+  r_ = 1;
+  reentrant_mutex_lock(&rmu);
+  r_ = 1;
+  reentrant_mutex_unlock(&rmu);
+  r_ = 1;
+  reentrant_mutex_unlock(&rmu);
+  r_ = 1; // expected-warning{{writing variable 'r_' requires holding mutex 
'rmu' exclusively}}
+}
+
 // We had a problem where we'd skip all attributes that follow a late-parsed
 // attribute in a single __attribute__.
 void run(void) __attribute__((guarded_by(mu1), guarded_by(mu1))); // 
expected-warning 2{{only applies to non-static data members and global 
variables}}
diff --git a/clang/test/SemaCXX/thread-safety-annotations.h 
b/clang/test/SemaCXX/thread-safety-annotations.h
index d89bcf8ff4706..00d432da4b6f0 100644
--- a/clang/test/SemaCXX/thread-safety-annotations.h
+++ b/clang/test/SemaCXX/thread-safety-annotations.h
@@ -35,6 +35,7 @@
 #define PT_GUARDED_BY(x)                __attribute__((pt_guarded_by(x)))
 
 // Common
+#define REENTRANT_CAPABILITY            __attribute__((reentrant_capability))
 #define SCOPED_LOCKABLE                 __attribute__((scoped_lockable))
 #define ACQUIRED_AFTER(...)             
__attribute__((acquired_after(__VA_ARGS__)))
 #define ACQUIRED_BEFORE(...)            
__attribute__((acquired_before(__VA_ARGS__)))
diff --git a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp 
b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
index 2a04e820eb095..1683c5911cfb7 100644
--- a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
+++ b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
@@ -6812,3 +6812,359 @@ 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 testReentrantIntersection() {
+  rmu.Lock();
+  if (guardby_var) {
+    rmu.Lock();
+    rmu.Lock();
+    rmu.Unlock();
+  } else {
+    rmu.Lock();
+    guardby_var = 1;
+  }
+  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 testReentrantIntersectionBad() {
+  rmu.Lock(); // expected-note{{mutex acquired here}}
+  if (guardby_var) {
+    rmu.Lock();
+    rmu.Lock();
+  } else {
+    rmu.Lock();
+    guardby_var = 1;
+  }
+  guardby_var = 1; // expected-warning{{expecting mutex 'rmu' to be held with 
matching reentrancy depth on every path through here}}
+  rmu.Unlock();
+  guardby_var = 1;
+  rmu.Unlock();
+  guardby_var = 1;
+  rmu.Unlock();
+}
+
+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);
+}
+
+class TestNegativeWithReentrantMutex {
+  ReentrantMutex rmu;
+  int a GUARDED_BY(rmu);
+
+public:
+  void baz() EXCLUSIVE_LOCKS_REQUIRED(!rmu) {
+    rmu.Lock();
+    rmu.Lock();
+    a = 0;
+    rmu.Unlock();
+    rmu.Unlock();
+  }
+};
+
+} // namespace Reentrancy
diff --git a/clang/test/SemaCXX/warn-thread-safety-parsing.cpp 
b/clang/test/SemaCXX/warn-thread-safety-parsing.cpp
index 752803e4a0543..f75b82ada323c 100644
--- a/clang/test/SemaCXX/warn-thread-safety-parsing.cpp
+++ b/clang/test/SemaCXX/warn-thread-safety-parsing.cpp
@@ -3,6 +3,7 @@
 // RUN: %clang_cc1 -fsyntax-only -verify -Wthread-safety -std=c++11 %s -D CPP11
 
 #define LOCKABLE            __attribute__ ((lockable))
+#define REENTRANT_CAPABILITY __attribute__ ((reentrant_capability))
 #define SCOPED_LOCKABLE     __attribute__ ((scoped_lockable))
 #define GUARDED_BY(x)       __attribute__ ((guarded_by(x)))
 #define GUARDED_VAR         __attribute__ ((guarded_var))
@@ -260,6 +261,12 @@ class LFoo {
 void l_function_params(int lvar LOCKABLE); // \
   // expected-warning {{'lockable' attribute only applies to}}
 
+class REENTRANT_CAPABILITY LOCKABLE LTestReentrant { // order does not matter
+};
+
+class REENTRANT_CAPABILITY LTestReentrantOnly { // \
+  // expected-warning {{ignoring 'reentrant_capability' attribute on 
'LTestReentrantOnly' without 'capability' attribute}}
+};
 
 //-----------------------------------------//
 //  Scoped Lockable Attribute (sl)
diff --git a/clang/unittests/AST/ASTImporterTest.cpp 
b/clang/unittests/AST/ASTImporterTest.cpp
index 4192faee1af80..843c44a2a1a4a 100644
--- a/clang/unittests/AST/ASTImporterTest.cpp
+++ b/clang/unittests/AST/ASTImporterTest.cpp
@@ -7993,6 +7993,15 @@ TEST_P(ImportAttributes, ImportLocksExcluded) {
   checkImportVariadicArg(FromAttr->args(), ToAttr->args());
 }
 
+TEST_P(ImportAttributes, ImportReentrantCapability) {
+  ReentrantCapabilityAttr *FromAttr, *ToAttr;
+  importAttr<CXXRecordDecl>(
+      R"(
+      struct __attribute__((capability("x"), 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