https://github.com/steakhal updated https://github.com/llvm/llvm-project/pull/208729
From 72fc83a3e77a5fd0e957af7ac767bd58d17c947f Mon Sep 17 00:00:00 2001 From: Balazs Benics <[email protected]> Date: Tue, 16 Jun 2026 15:24:51 +0100 Subject: [PATCH 1/2] [analyzer] Prevent inlining RAII ctors/dtors BlockInCriticalSectionChecker registers the pre-call for the RAII ctors and Dtors - and also the raw 'lock' and 'unlock' handlers. However, pre-call does not prevent inlining. This means that (in the likely case of) that the body is present, the analyzer will model the effect of the lock twice. This happens on libc++ unique_lock. We really should have eval-called the ctor/dtor to avoid the inlining of those, but here we are. rdar://175814310 --- .../BlockInCriticalSectionChecker.cpp | 34 +++++++++--- .../block-in-critical-section-raii.cpp | 53 +++++++++++++++++++ 2 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 clang/test/Analysis/block-in-critical-section-raii.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp index 03c576270797b..ad26324cdab76 100644 --- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp @@ -212,7 +212,8 @@ class SuppressNonBlockingStreams : public BugReporterVisitor { } }; -class BlockInCriticalSectionChecker : public Checker<check::PostCall> { +class BlockInCriticalSectionChecker + : public Checker<check::PostCall, eval::Call> { private: const std::array<MutexDescriptor, 9> MutexDescriptors{ // NOTE: There are standard library implementations where some methods @@ -276,6 +277,9 @@ class BlockInCriticalSectionChecker : public Checker<check::PostCall> { /// Process lock. /// Process blocking functions (sleep, getc, fgets, read, recv) void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + + // Process RAII lock guard object ctors/dtors. + bool evalCall(const CallEvent &Call, CheckerContext &C) const; }; } // end anonymous namespace @@ -373,15 +377,33 @@ void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { if (isBlockingInCritSection(Call, C)) { reportBlockInCritSection(Call, C); - } else if (std::optional<MutexDescriptor> LockDesc = - checkDescriptorMatch(Call, C, /*IsLock=*/true)) { - handleLock(*LockDesc, Call, C); - } else if (std::optional<MutexDescriptor> UnlockDesc = - checkDescriptorMatch(Call, C, /*IsLock=*/false)) { + return; + } + + if (std::optional<MutexDescriptor> LockDesc = + checkDescriptorMatch(Call, C, /*IsLock=*/true)) { + if (!std::holds_alternative<RAIIMutexDescriptor>(*LockDesc)) + handleLock(*LockDesc, Call, C); + return; + } + if (std::optional<MutexDescriptor> UnlockDesc = + checkDescriptorMatch(Call, C, /*IsLock=*/false)) { handleUnlock(*UnlockDesc, Call, C); } } +bool BlockInCriticalSectionChecker::evalCall(const CallEvent &Call, + CheckerContext &C) const { + if (std::optional<MutexDescriptor> LockDesc = + checkDescriptorMatch(Call, C, /*IsLock=*/true)) { + if (std::holds_alternative<RAIIMutexDescriptor>(*LockDesc)) { + handleLock(*LockDesc, Call, C); + return true; + } + } + return false; +} + void BlockInCriticalSectionChecker::reportBlockInCritSection( const CallEvent &Call, CheckerContext &C) const { ExplodedNode *ErrNode = C.generateNonFatalErrorNode(C.getState()); diff --git a/clang/test/Analysis/block-in-critical-section-raii.cpp b/clang/test/Analysis/block-in-critical-section-raii.cpp new file mode 100644 index 0000000000000..4bc4c94c74259 --- /dev/null +++ b/clang/test/Analysis/block-in-critical-section-raii.cpp @@ -0,0 +1,53 @@ +// RUN: %clang_analyze_cc1 -verify %s \ +// RUN: -analyzer-checker=core,unix.BlockInCriticalSection + +void sleep(int x); + +namespace std { +struct mutex { + void lock(); + void unlock(); +}; +// libc++-shaped unique_lock: ctor and dtor have non-empty bodies that call +// the underlying mutex's lock()/unlock() member functions. +template <class M> +struct unique_lock { + M *m_; + bool owns_; + explicit unique_lock(M &m) : m_(&m), owns_(true) { m_->lock(); } + ~unique_lock() { + if (owns_) + m_->unlock(); + } +}; +} // namespace std + +// Recursive use of the RAII guard. Without the fix, deep recursion would +// prevent the dtor body from inlining at the inlining-stack-depth limit; +// the inner mutex unlock would not fire, used to emit a leak FP. +struct C { + std::mutex m; + bool aborted; + bool getAborted() { + std::unique_lock<std::mutex> lk(m); + return aborted; + } + void recurse() { + if (getAborted()) + recurse(); + sleep(1); // no-warning + } +}; + +// Direct case: RAII guard scoped to a function body. Constructor inlining + +// destructor inlining must not double-count, even when both bodies inline. +void callee(std::mutex &m) { + std::unique_lock<std::mutex> lk(m); + // The lock IS held here so blocking is correctly diagnosed. + sleep(1); // expected-warning {{Call to blocking function 'sleep' inside of critical section}} +} // dtor fires here, releases the lock; no further sleep should warn. + +void top(std::mutex &m) { + callee(m); + sleep(1); // no-warning: the lock from `callee` was released by ~unique_lock before this sleep. +} From a944ff7712e7731cacf4cdd7fd293c3422f2c9f0 Mon Sep 17 00:00:00 2001 From: Balazs Benics <[email protected]> Date: Fri, 10 Jul 2026 15:33:43 +0100 Subject: [PATCH 2/2] Escape the Object to model the side-effects of the ctor --- .../BlockInCriticalSectionChecker.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp index ad26324cdab76..b722bfd3390a7 100644 --- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp @@ -264,7 +264,7 @@ class BlockInCriticalSectionChecker bool IsLock) const; void handleLock(const MutexDescriptor &Mutex, const CallEvent &Call, - CheckerContext &C) const; + CheckerContext &C, ProgramStateRef State) const; void handleUnlock(const MutexDescriptor &Mutex, const CallEvent &Call, CheckerContext &C) const; @@ -325,7 +325,7 @@ static const MemRegion *getRegion(const CallEvent &Call, void BlockInCriticalSectionChecker::handleLock( const MutexDescriptor &LockDescriptor, const CallEvent &Call, - CheckerContext &C) const { + CheckerContext &C, ProgramStateRef State) const { const MemRegion *MutexRegion = getRegion(Call, LockDescriptor, /*IsLock=*/true); if (!MutexRegion) @@ -333,7 +333,7 @@ void BlockInCriticalSectionChecker::handleLock( const CritSectionMarker MarkToAdd{Call.getOriginExpr(), MutexRegion}; ProgramStateRef StateWithLockEvent = - C.getState()->add<ActiveCritSections>(MarkToAdd); + State->add<ActiveCritSections>(MarkToAdd); C.addTransition(StateWithLockEvent, createCritSectionNote(MarkToAdd, C)); } @@ -383,7 +383,7 @@ void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call, if (std::optional<MutexDescriptor> LockDesc = checkDescriptorMatch(Call, C, /*IsLock=*/true)) { if (!std::holds_alternative<RAIIMutexDescriptor>(*LockDesc)) - handleLock(*LockDesc, Call, C); + handleLock(*LockDesc, Call, C, C.getState()); return; } if (std::optional<MutexDescriptor> UnlockDesc = @@ -397,7 +397,16 @@ bool BlockInCriticalSectionChecker::evalCall(const CallEvent &Call, if (std::optional<MutexDescriptor> LockDesc = checkDescriptorMatch(Call, C, /*IsLock=*/true)) { if (std::holds_alternative<RAIIMutexDescriptor>(*LockDesc)) { - handleLock(*LockDesc, Call, C); + ProgramStateRef State = C.getState(); + // Escape the object under construction to model the side-effects of the + // constructor. + if (const auto *Ctor = dyn_cast<AnyCXXConstructorCall>(&Call)) { + const MemRegion *ObjRegion = Ctor->getCXXThisVal().getAsRegion(); + State = State->invalidateRegions(ObjRegion, C.getCFGElementRef(), + C.blockCount(), C.getStackFrame(), + /*CausesPointerEscape=*/false); + } + handleLock(*LockDesc, Call, C, State); return true; } } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
