[llvm-branch-commits] [clang] Thread Safety Analysis: Track try-acquired capabilities as a ternary try-held state (PR #220635)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Wed Sep 2 10:47:05 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Jameson Nash (vtjnash)
<details>
<summary>Changes</summary>
Context: I was a bit frustrated by using conditional capabilities in
real code, since it was giving both incorrect and incomplete analysis
results. There is already this really nice fact manager here;
conditional modelling was simply ignoring it and essentially
reimplementing a simplified duplicate model on top of it which tried to
re-derive facts and inject them into the real model. But the underlying
model already seemed really close to be able to hold these facts too,
instead of leaving them invisible and wrongly diagnosed. This PR is the
beginning of a series of about 10 PRs to reduce those correctness and
completeness gaps. Claude is already happy with the state of those other
branches, but I'm not happy with them yet, so I'll drip those out as I
get time to polish them up. Throughout this series, I have tried to
often suppress new warnings except when passing `-Wthread-safety-beta`,
so that users can opt-in explicitly to the more complete and accurate
checks. The stack of branches is:
1. (users/vtjnash/tsa-tryheld-regardless-of-result) Diagnose a try-acquire that acquires a capability regardless of its result
2. (users/vtjnash/tsa-tryheld-never-checked) Diagnose a try-acquire whose result is never checked
3. (users/vtjnash/tsa-tryheld-edge-resolve) Resolve try-acquire facts at every branch on the result
4. (users/vtjnash/tsa-tryheld-stored-results) Resolve branches on stored try-acquire results through merges and phis
5. (users/vtjnash/tsa-tryheld-same-origin-joins) Demote same-origin joins silently
6. (users/vtjnash/tsa-tryheld-value-precise) Resolve try-acquire results by exact value, not just truthiness
7. (users/vtjnash/tsa-tryheld-cond-operator) Resolve try-acquire results merged by a conditional operator
8. (users/vtjnash/tsa-tryheld-scoped) Fold scoped lockables into the try-held model
9. (users/vtjnash/tsa-tryheld-elidable-copies) Look through elidable copies of scoped lockables
A capability now has a ternary per-program-point state -- not-held,
try-held, or held -- instead of try-acquires being invisible until a
branch on their result. A call to a try_acquire_capability function
immediately records a try-held fact anchored to the CallExpr, with the
attributes' capabilities translated in the call's own context and
recorded there; a branch on the call's result resolves each fact against
that record as before, promoting it to held on its attribute's success
edge and removing it on the other, so attributes with mixed success
values resolve per capability. Leaving the try-held state any other way
is diagnosed with a warning (new "may hold"-flavors of the existing
diagnostics) or handled conservatively.
Design details:
* Capabilities are recorded once, at the call, in its own context
(before the lockset walk): a branch never re-translates attribute
arguments, so an intervening reassignment cannot misresolve facts.
* The acquisition checks (negative capabilities, acquired_before/
acquired_after ordering) run once, at the call; the success edge
consumes the negative capability.
* A held/try-held join difference is ignored only when the terminator
re-branches on the same call that created both facts. Origins merged
from different calls, or overwritten by an acquire or assert, are
cleared: such facts are conservatively never resolved by a branch
again.
* Releasing a try-held capability warns "may not be held" and leaves a
negative fact. Try-held never satisfies REQUIRES/GUARDED_BY, and
still violates LOCKS_EXCLUDED and negative requirements. Losing track
of the fact at a join or the end of the function is deliberately not
yet diagnosed (a follow-up commit adds that under
-Wthread-safety-beta).
* An acquire over a try-held capability warns ("may already be held"; asserts
exempt); a reentrant capability deepens instead, keeping its
conditional top level. A try-acquire deepens a definite hold; over a
merely try-held fact it is diagnosed and not tracked (can only track
one fact per capability at a time).
* Success values decode by constant evaluation, defaulting to false,
unchanged from before.
* Stored results (`if ((b = mu.TryLock()))`, short-circuit forms) now
resolve like a branch on the call itself.
* A scoped guard's explicit unlock of a try-held mutex diagnoses like a
release; its definite re-acquire only deepens the fact. Scoped
lockables are otherwise not yet tracked conditionally.
* A join of unequal reentrancy depths warns once, then keeps the fact
that guarantees more (a try-held top level counts half) to minimize
follow-on warnings, following the same logic as before. A mixed
held/try-held pair keeps the try-held side, after warning.
* NFC: checkAcquiredCapability split out of addLock; "may" `%select{}`
flavors on the unlock/double-lock/excludes warnings;
`FactSet::isEmpty(FactManager&)` renamed `holdsNoCapability`.
Co-Authored-By: Claude Fable 5 <noreply@<!-- -->anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@<!-- -->anthropic.com>
---
<sub>Stack created with <a href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
---
Patch is 82.66 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/220635.diff
8 Files Affected:
- (modified) clang/docs/ReleaseNotes.md (+13)
- (modified) clang/docs/ThreadSafetyAnalysis.md (+16-3)
- (modified) clang/include/clang/Analysis/Analyses/ThreadSafety.h (+11-3)
- (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+5-3)
- (modified) clang/lib/Analysis/ThreadSafety.cpp (+632-166)
- (modified) clang/lib/Sema/AnalysisBasedWarnings.cpp (+9-6)
- (modified) clang/test/SemaCXX/warn-thread-safety-analysis.cpp (+420)
- (modified) clang/test/SemaCXX/warn-thread-safety-negative.cpp (+41)
``````````diff
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index bf295981710ac..1ba0b67ae75a6 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -278,6 +278,19 @@ features cannot lower the translation-unit ABI level;
initialization, while not diagnosing parameters passed to the selected
allocation function or promise constructor. (#GH217501)
+- Thread safety analysis now tracks capabilities acquired by functions
+ annotated with `try_acquire_capability` accurately: the capability is
+ conditionally ("try") held from the call until a branch on its result, and
+ then is held only on the success path. A try-held capability does not satisfy
+ capability requirements, positive or negative, and acquiring or releasing it
+ before branching on the result warns that it may (or may not) be held. Some
+ diagnostics under `-Wthread-safety` change accordingly, although the main
+ improvements are intended to be behind `-Wthread-safety-negative` and
+ `-Wthread-safety-beta`. A try-acquire over a still-unresolved try-acquire of
+ the same capability will now warn, as the analysis cannot track both, and so
+ will an acquisition, blocking or try, that changes the kind (shared vs.
+ exclusive) of a held or try-held capability, even for a re-entrant one.
+
- Fixed bug in `-Wdocumentation` so that it correctly handles explicit
function template instantiations (#64087).
diff --git a/clang/docs/ThreadSafetyAnalysis.md b/clang/docs/ThreadSafetyAnalysis.md
index f29b5bbc55e1e..9e9ab92f78eb7 100644
--- a/clang/docs/ThreadSafetyAnalysis.md
+++ b/clang/docs/ThreadSafetyAnalysis.md
@@ -474,9 +474,22 @@ The first argument must be `true` or `false`, to specify which return value
indicates success, and the remaining arguments are interpreted in the same way
as `ACQUIRE`. See {ref}`mutexheader`, below, for example uses.
-Because the analysis doesn't support conditional locking, a capability is
-treated as acquired after the first branch on the return value of a try-acquire
-function.
+The capability is tracked as conditionally ("try") held from the call until a
+recognized branch on its return value: on the success path the capability is
+held, on the failure path it is not. A conditionally held capability does not
+satisfy requirements such as `GUARDED_BY` or `REQUIRES` and it also violates
+`LOCKS_EXCLUDED` and negative requirements (`REQUIRES(!mu)`). Acquiring a
+non-reentrant lock again before branching on the return value warns that it may
+already be held, and releasing the try-held capability warns that it may not be
+held. A try-acquire of a capability that is already held adds a further
+conditionally held level onto the hold -- the success branch may be statically
+unreachable, but the modeling doesn't assume that. An acquisition of the other
+kind (shared vs. exclusive) than the existing hold, blocking or try, generates
+a warning since the tracking for a hold has only a single kind. A try-acquire
+of a capability that is already conditionally held also warns: the analysis
+keeps one fact per capability and cannot track two unresolved try-acquires at
+once. Asserting the capability (`ASSERT_CAPABILITY`) upgrades it to held
+without a warning.
```c++
Mutex mu;
diff --git a/clang/include/clang/Analysis/Analyses/ThreadSafety.h b/clang/include/clang/Analysis/Analyses/ThreadSafety.h
index 4fb04fb16b8c0..a96a445accee7 100644
--- a/clang/include/clang/Analysis/Analyses/ThreadSafety.h
+++ b/clang/include/clang/Analysis/Analyses/ThreadSafety.h
@@ -125,9 +125,12 @@ class ThreadSafetyHandler {
/// in the error message.
/// \param Loc -- The SourceLocation of the Unlock
/// \param LocPreviousUnlock -- If valid, the location of a previous Unlock.
+ /// \param MaybeHeld -- The capability is try-held: acquired by a
+ /// try-acquire whose result was not checked, so it is only possibly held.
virtual void handleUnmatchedUnlock(StringRef Kind, Name LockName,
SourceLocation Loc,
- SourceLocation LocPreviousUnlock) {}
+ SourceLocation LocPreviousUnlock,
+ bool MaybeHeld) {}
/// Warn about an unlock function call that attempts to unlock a lock with
/// the incorrect lock kind. For instance, a shared lock being unlocked
@@ -150,9 +153,11 @@ class ThreadSafetyHandler {
/// in the error message.
/// \param LocLocked -- The location of the first lock expression.
/// \param LocDoubleLock -- The location of the second lock expression.
+ /// \param MaybeHeld -- The first acquisition was a try-acquire whose result
+ /// was not checked, so the capability is only possibly held.
virtual void handleDoubleLock(StringRef Kind, Name LockName,
SourceLocation LocLocked,
- SourceLocation LocDoubleLock) {}
+ SourceLocation LocDoubleLock, bool MaybeHeld) {}
/// Warn about situations where a mutex is sometimes held and sometimes not.
/// The three situations are:
@@ -244,8 +249,11 @@ class ThreadSafetyHandler {
/// \param LockName -- A StringRef name for the lock expression, to be printed
/// in the error message.
/// \param Loc -- The location of the function call.
+ /// \param MaybeHeld -- The capability is try-held: acquired by a
+ /// try-acquire whose result was not checked, so it is only possibly held.
virtual void handleFunExcludesLock(StringRef Kind, Name FunName,
- Name LockName, SourceLocation Loc) {}
+ Name LockName, SourceLocation Loc,
+ bool MaybeHeld) {}
/// Warn when an actual underlying mutex of a scoped lockable does not match
/// the expected.
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index b5100b67dccd7..b058588defb70 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -4365,13 +4365,15 @@ def err_attribute_argument_out_of_bounds_extra_info : Error<
":must be between 1 and %2}2">;
// Thread Safety Analysis
-def warn_unlock_but_no_lock : Warning<"releasing %0 '%1' that was not held">,
+def warn_unlock_but_no_lock : Warning<
+ "releasing %0 '%1' that %select{was not|may not be}2 held">,
InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
def warn_unlock_kind_mismatch : Warning<
"releasing %0 '%1' using %select{shared|exclusive}2 access, expected "
"%select{shared|exclusive}3 access">,
InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
-def warn_double_lock : Warning<"acquiring %0 '%1' that is already held">,
+def warn_double_lock : Warning<
+ "acquiring %0 '%1' that %select{is already|may already be}2 held">,
InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
def warn_no_unlock : Warning<
"%0 '%1' is still held at the end of function">,
@@ -4406,7 +4408,7 @@ def warn_requires_any_of_locks : Warning<
"at least one of %2">,
InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
def warn_fun_excludes_mutex : Warning<
- "cannot call function '%1' while %0 '%2' is held">,
+ "cannot call function '%1' while %0 '%2' %select{is|may be}3 held">,
InGroup<ThreadSafetyAnalysis>, DefaultIgnore;
def warn_cannot_resolve_lock : Warning<
"cannot resolve lock expression">,
diff --git a/clang/lib/Analysis/ThreadSafety.cpp b/clang/lib/Analysis/ThreadSafety.cpp
index ba432243b942d..440d5b064fd9e 100644
--- a/clang/lib/Analysis/ThreadSafety.cpp
+++ b/clang/lib/Analysis/ThreadSafety.cpp
@@ -38,6 +38,7 @@
#include "clang/Basic/Specifiers.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/ImmutableMap.h"
+#include "llvm/ADT/PointerIntPair.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
@@ -93,11 +94,14 @@ namespace {
/// attributes on a function.
class CapExprSet : public SmallVector<CapabilityExpr, 4> {
public:
+ bool contains(const CapabilityExpr &CapE) const {
+ return llvm::any_of(
+ *this, [&](const CapabilityExpr &CapE2) { return CapE.equals(CapE2); });
+ }
+
/// Push M onto list, but discard duplicates.
void push_back_nodup(const CapabilityExpr &CapE) {
- if (llvm::none_of(*this, [=](const CapabilityExpr &CapE2) {
- return CapE.equals(CapE2);
- }))
+ if (!contains(CapE))
push_back(CapE);
}
};
@@ -106,9 +110,56 @@ class FactManager;
class FactSet;
/// This is a helper class that stores a fact that is known at a
-/// particular point in program execution. Currently, a fact is a capability,
+/// particular point in program execution. Concretely, a fact is a capability,
/// along with additional information, such as where it was acquired, whether
/// it is exclusive or shared, etc.
+///
+/// Per capability the analysis tracks a ternary state: not-held (no fact in
+/// the FactSet), try-held, or held. Permitted transitions:
+///
+/// not-held --acquire-----------------------------------------> held
+/// not-held --try-acquire (BuildLockset::handleCall)----------> try-held
+/// try-held --branch on the try-acquire result: success edge--> held
+/// try-held --branch on the try-acquire result: failure edge--> not-held
+/// try-held --acquire or assert (addLock)---------------------> held
+/// held -----join with a failed path of the same try-acquire,
+/// when the join re-branches on its result
+/// (intersectAndWarn)-------------------------------> try-held
+/// held -----release------------------------------------------> not-held
+///
+/// A fact additionally tracks a reentrancy depth, and so two of those
+/// transitions may deepen that instead of colliding: a try-acquire over
+/// a held capability -- whatever its reentrancy, since at runtime such a
+/// call fails rather than deadlocks -- and, for a reentrant capability
+/// only, an unconditional acquire over a try-held capability. Either way
+/// the fact becomes try-held one level deeper with the try-acquire call as
+/// its origin: held at that depth if that call succeeded, one level shallower
+/// otherwise -- and resolved at a branch on the result and unwound one
+/// level per release.
+///
+/// Try-held means "held if the try-acquire succeeded", so it warns
+/// wherever a definite state is required: it does not satisfy capability
+/// requirements, it violates exclusions and negative requirements,
+/// releasing it warns (may not be held), and acquiring it again warns
+/// (may already be held).
+/// Asserts and same-kind reentrant acquires (which deepen instead) are
+/// exempt from the acquire warning: they legitimately acquire a
+/// possibly-held capability. An acquire of the other kind (shared vs.
+/// exclusive) warns even for a reentrant capability: the tracked hold has
+/// a single kind.
+///
+/// When the analysis loses track of a try-held fact -- at a join with a
+/// path that does not hold it, or at the end of the function -- the
+/// try-acquire result was never checked and the capability may be leaked;
+/// this is diagnosed in beta mode.
+///
+/// Any other try-acquire of a capability that is already tracked is a
+/// conflict the model cannot represent: over a try-held fact a second
+/// unresolved try-acquire cannot be tracked (one origin per fact),
+/// reentrant or not, and over a definite hold a try-acquire of the other
+/// kind (shared vs. exclusive) cannot share the fact's single lock kind.
+/// Either way the existing fact wins unchanged, the call's acquisition
+/// goes untracked, and the conflict is diagnosed at the call.
class FactEntry : public CapabilityExpr {
public:
enum FactEntryKind { Lockable, ScopedLockable };
@@ -133,6 +184,25 @@ class FactEntry : public CapabilityExpr {
/// Where it was acquired.
SourceLocation AcquireLoc;
+ /// The try-acquire call this fact originates from (or null), and whether
+ /// the capability is still only conditionally ("try") held: acquired by
+ /// that call but not yet branched on its result, so held only on the paths
+ /// where the call succeeded. Facts promoted to held on the call's success
+ /// edge keep their origin (with the flag cleared), so that a later join
+ /// with a path where the try-acquire failed can be recognized. Facts
+ /// upgraded by an unconditional acquire or assert clear this: their held
+ /// state is not proved by the call's success, so a branch on its result
+ /// must not resolve them (a reentrant acquire instead deepens the fact,
+ /// keeping the origin for its still conditional top level). A try-held
+ /// fact whose paths merged different origins loses its origin and can
+ /// never be resolved; it stays try-held until the analysis loses track
+ /// of it.
+ /// (callexpr, true) : try-held state from callexpr
+ /// (callexpr, false) : held state from callexpr
+ /// (nullptr, true) : try-held state from multiple sources
+ /// (nullptr, false) : held state from definitive sources
+ llvm::PointerIntPair<const Expr *, 1, bool> TryLock;
+
protected:
~FactEntry() = default;
@@ -149,6 +219,26 @@ class FactEntry : public CapabilityExpr {
bool declared() const { return Source == Declared; }
bool managed() const { return Source == Managed; }
+ bool tryHeld() const { return TryLock.getInt(); }
+ const Expr *tryLockCall() const { return TryLock.getPointer(); }
+
+ /// Whether the capability is definitely held at least once: it is not
+ /// try-held, or only the top level of a reentrant acquisition is
+ /// conditional while the levels below it are definite.
+ virtual bool definitelyHeld() const { return !tryHeld(); }
+
+ /// The fact's reentrancy depth; only lockable facts can be reentrant.
+ virtual unsigned int getReentrancyDepth() const { return 0; }
+
+ /// Record that this fact originates from the try-acquire call \p Call.
+ /// While \p Conditional is true the fact is try-held and is resolved
+ /// (promoted to held or removed) at a branch on the call's result; a
+ /// try-held fact with a null origin (merged from different origins) can
+ /// never be resolved.
+ void setTryLock(const Expr *Call, bool Conditional) {
+ TryLock.setPointerAndInt(Call, Conditional);
+ }
+
virtual void
handleRemovalFromIntersection(const FactSet &FSet, FactManager &FactMan,
SourceLocation JoinLoc, LockErrorKind LEK,
@@ -220,10 +310,12 @@ class FactSet {
bool isEmpty() const { return FactIDs.size() == 0; }
- // Return true if the set contains only negative facts
- bool isEmpty(FactManager &FactMan) const {
+ // Return true if the set holds no definitely-held positive capability.
+ // It may hold negative or try-held facts, unlike isEmpty, which tests
+ // the set itself.
+ bool holdsNoCapability(FactManager &FactMan) const {
for (const auto FID : *this) {
- if (!FactMan[FID].negative())
+ if (!FactMan[FID].negative() && FactMan[FID].definitelyHeld())
return false;
}
return true;
@@ -972,8 +1064,8 @@ namespace {
class LockableFactEntry final : public FactEntry {
private:
/// Reentrancy depth: incremented when a capability has been acquired
- /// reentrantly (after initial acquisition). Always 0 for non-reentrant
- /// capabilities.
+ /// again after its initial acquisition -- by a reentrant acquire, or by
+ /// a try-acquire over a definite hold.
unsigned int ReentrancyDepth = 0;
LockableFactEntry(const CapabilityExpr &CE, LockKind LK, SourceLocation Loc,
@@ -993,7 +1085,11 @@ class LockableFactEntry final : public FactEntry {
return new (Alloc) LockableFactEntry(CE, LK, Loc, Src);
}
- unsigned int getReentrancyDepth() const { return ReentrancyDepth; }
+ unsigned int getReentrancyDepth() const override { return ReentrancyDepth; }
+
+ bool definitelyHeld() const override {
+ return !tryHeld() || ReentrancyDepth > 0;
+ }
void
handleRemovalFromIntersection(const FactSet &FSet, FactManager &FactMan,
@@ -1007,12 +1103,12 @@ class LockableFactEntry final : public FactEntry {
void handleLock(FactSet &FSet, FactManager &FactMan, const FactEntry &entry,
ThreadSafetyHandler &Handler) const override {
- if (const FactEntry *RFact = tryReenter(FactMan, entry.kind())) {
+ if (const FactEntry *RFact = attemptReenter(FactMan, entry.kind())) {
// This capability has been reentrantly acquired.
FSet.replaceLock(FactMan, entry, RFact);
} else {
Handler.handleDoubleLock(entry.getKind(), entry.toString(), loc(),
- entry.loc());
+ entry.loc(), false);
}
}
@@ -1031,11 +1127,15 @@ class LockableFactEntry final : public FactEntry {
}
}
- // Return an updated FactEntry if we can acquire this capability reentrant,
- // nullptr otherwise.
- const FactEntry *tryReenter(FactManager &FactMan,
- LockKind ReenterKind) const {
- if (!reentrant())
+ // Return an updated FactEntry one level deeper, or nullptr if another
+ // acquisition cannot nest in this capability: the kinds must match, and
+ // a blocking acquire can only reacquire a reentrant capability. A
+ // conditional try-acquire can always nest -- at runtime it may simply
+ // fail. This checks only the capability, not the fact's held state:
+ // which transitions are permitted is checked by the caller.
+ const FactEntry *attemptReenter(FactManager &FactMan, LockKind ReenterKind,
+ bool Conditional = false) const {
+ if (!Conditional && !reentrant())
return nullptr;
if (kind() != ReenterKind)
return nullptr;
@@ -1045,11 +1145,10 @@ class LockableFactEntry final : public FactEntry {
}
// Return an updated FactEntry if we are releasing a capability previously
- // acquired reentrant, nullptr otherwise.
+ // acquired reentrant (or conditionally), nullptr otherwise.
const FactEntry *leaveReentrant(FactManager &FactMan) const {
if (!ReentrancyDepth)
return nullptr;
- assert(reentrant());
auto *NewFact = FactMan.createFact<LockableFactEntry>(*this);
NewFact->ReentrancyDepth--;
return NewFact;
@@ -1060,6 +1159,42 @@ class LockableFactEntry final : public FactEntry {
}
};
+static SourceLocation unmatchedUnlockNoteLoc(const FactSet &FSet,
+ FactManager &FactMan,
+ const CapabilityExpr &Cp) {
+ if (const FactEntry *Neg = FSet.findLock(FactMan, !Cp))
+ return Neg->loc();
+ return SourceLocation();
+}
+
+/// Decide if this unlock unconditionally releases a capability that is only
+/// try-held; returns true if the release was handled here.
+/// Diagnose like an unmatched unlock and leave the negative fact behind:
+/// the thread provably does not hold the capability afterwards, whether the
+/// try-acquire succeeded or failed.
+/// With a null \p Handler (a scoped guard's destructor, from FullyRemove=true)
+/// the fact is kept unchanged: it may record an acquisition the guard does not
+/// own, which the destructor's conditional release cannot pair with.
+static bool handleUncheckedTryHeldUnlock(FactSet &FSet, FactManager &FactMan,
+ const FactEntry &Fact,
+ const CapabilityExpr &Cp,
+ SourceLocation UnlockLoc,
+ ThreadSafetyHandler *Handler) {
+ if (Fact.definitelyHeld())
+ return false;
+ if (Handler) {
+ Handler->handleUnmatchedUnlock(Cp.getKind(), Cp.toString(), UnlockLoc,
+ SourceLocation(), true);
+ FSet.removeLock(FactMan, Cp);
+ // A pre-existing negative fact survives a try-acquire (it is consumed
+ // only on the success edge), so do not add a duplicate over it.
+ if (!Cp.negative() && !FSet.findLock(FactMan, !Cp))
+ FSet.addLock(FactMan, FactMan.createFact<LockableFactEntry>(
+ !Cp, LK_Exclusive, UnlockLoc));
+ }
+ return true;
+}
+
enum UnderlyingCapabilityKind {
UCK_Acquired, ///< Any kind of acquired capability.
UCK_ReleasedShared, ///< Shared capability that was released.
@@ -1192...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/220635
More information about the llvm-branch-commits
mailing list