[clang] [clang][analyzer] Introduce modeling for threading related checkers (PR #109636)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Oct 7 01:43:21 PDT 2024
Endre =?utf-8?q?Fülöp?= <endre.fulop at sigmatechnology.com>,
Endre =?utf-8?q?Fülöp?= <endre.fulop at sigmatechnology.com>,
Endre =?utf-8?q?Fülöp?= <endre.fulop at sigmatechnology.com>,
Endre =?utf-8?q?Fülöp?= <endre.fulop at sigmatechnology.com>
Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/109636 at github.com>
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-static-analyzer-1
@llvm/pr-subscribers-clang
Author: Endre Fülöp (gamesh411)
<details>
<summary>Changes</summary>
This patch's primary driving force is reducing code duplication in BlockInCriticalSectionChecker and PthreadLockChecker.
This is mainly NFC. The user-facing API is the same, but at least one false positive has been fixed, and the internal state representation has changed.
---
Patch is 115.54 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/109636.diff
14 Files Affected:
- (modified) clang/include/clang/StaticAnalyzer/Checkers/Checkers.td (+8-1)
- (modified) clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp (+55-318)
- (modified) clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt (+1)
- (added) clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp (+773)
- (added) clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingAPI.h (+244)
- (added) clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h (+126)
- (added) clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingGDM.h (+169)
- (added) clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexRegionExtractor.h (+139)
- (modified) clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp (+148-609)
- (modified) clang/test/Analysis/analyzer-enabled-checkers.c (+1)
- (modified) clang/test/Analysis/block-in-critical-section.cpp (+8-13)
- (modified) clang/test/Analysis/pthreadlock_state.c (+48-5)
- (modified) clang/test/Analysis/pthreadlock_state_nottracked.c (+5)
- (modified) clang/test/Analysis/std-c-library-functions-arg-enabled-checkers.c (+1)
``````````diff
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index 349040c15eeb83..b41580dcfba575 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -219,6 +219,11 @@ def DereferenceChecker : Checker<"NullDereference">,
]>,
Documentation<HasDocumentation>;
+def MutexModeling : Checker<"MutexModeling">,
+ HelpText<"Model mutexes lock and unlock events">,
+ Documentation<NotDocumented>,
+ Hidden;
+
def NonNullParamChecker : Checker<"NonNullParamChecker">,
HelpText<"Check for null pointers passed as arguments to a function whose "
"arguments are references or marked with the 'nonnull' attribute">,
@@ -307,6 +312,7 @@ def StackAddrAsyncEscapeChecker : Checker<"StackAddressAsyncEscape">,
def PthreadLockBase : Checker<"PthreadLockBase">,
HelpText<"Helper registering multiple checks.">,
+ Dependencies<[MutexModeling]>,
Documentation<NotDocumented>,
Hidden;
@@ -505,6 +511,7 @@ def UnixAPIMisuseChecker : Checker<"API">,
def BlockInCriticalSectionChecker : Checker<"BlockInCriticalSection">,
HelpText<"Check for calls to blocking functions inside a critical section">,
+ Dependencies<[MutexModeling]>,
Documentation<HasDocumentation>;
def DynamicMemoryModeling: Checker<"DynamicMemoryModeling">,
@@ -610,7 +617,7 @@ def ChrootChecker : Checker<"Chroot">,
def PthreadLockChecker : Checker<"PthreadLock">,
HelpText<"Simple lock -> unlock checker">,
- Dependencies<[PthreadLockBase]>,
+ Dependencies<[PthreadLockBase, MutexModeling]>,
Documentation<HasDocumentation>;
def SimpleStreamChecker : Checker<"SimpleStream">,
diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
index 7460781799d08a..e5be2dfc19ff42 100644
--- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
@@ -14,165 +14,24 @@
//
//===----------------------------------------------------------------------===//
+#include "MutexModeling/MutexModelingAPI.h"
+
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
-#include "llvm/ADT/STLExtras.h"
-#include "llvm/ADT/SmallString.h"
-#include "llvm/ADT/StringExtras.h"
-#include <iterator>
#include <utility>
-#include <variant>
using namespace clang;
using namespace ento;
+using namespace mutex_modeling;
namespace {
-
-struct CritSectionMarker {
- const Expr *LockExpr{};
- const MemRegion *LockReg{};
-
- void Profile(llvm::FoldingSetNodeID &ID) const {
- ID.Add(LockExpr);
- ID.Add(LockReg);
- }
-
- [[nodiscard]] constexpr bool
- operator==(const CritSectionMarker &Other) const noexcept {
- return LockExpr == Other.LockExpr && LockReg == Other.LockReg;
- }
- [[nodiscard]] constexpr bool
- operator!=(const CritSectionMarker &Other) const noexcept {
- return !(*this == Other);
- }
-};
-
-class CallDescriptionBasedMatcher {
- CallDescription LockFn;
- CallDescription UnlockFn;
-
-public:
- CallDescriptionBasedMatcher(CallDescription &&LockFn,
- CallDescription &&UnlockFn)
- : LockFn(std::move(LockFn)), UnlockFn(std::move(UnlockFn)) {}
- [[nodiscard]] bool matches(const CallEvent &Call, bool IsLock) const {
- if (IsLock) {
- return LockFn.matches(Call);
- }
- return UnlockFn.matches(Call);
- }
-};
-
-class FirstArgMutexDescriptor : public CallDescriptionBasedMatcher {
-public:
- FirstArgMutexDescriptor(CallDescription &&LockFn, CallDescription &&UnlockFn)
- : CallDescriptionBasedMatcher(std::move(LockFn), std::move(UnlockFn)) {}
-
- [[nodiscard]] const MemRegion *getRegion(const CallEvent &Call, bool) const {
- return Call.getArgSVal(0).getAsRegion();
- }
-};
-
-class MemberMutexDescriptor : public CallDescriptionBasedMatcher {
-public:
- MemberMutexDescriptor(CallDescription &&LockFn, CallDescription &&UnlockFn)
- : CallDescriptionBasedMatcher(std::move(LockFn), std::move(UnlockFn)) {}
-
- [[nodiscard]] const MemRegion *getRegion(const CallEvent &Call, bool) const {
- return cast<CXXMemberCall>(Call).getCXXThisVal().getAsRegion();
- }
-};
-
-class RAIIMutexDescriptor {
- mutable const IdentifierInfo *Guard{};
- mutable bool IdentifierInfoInitialized{};
- mutable llvm::SmallString<32> GuardName{};
-
- void initIdentifierInfo(const CallEvent &Call) const {
- if (!IdentifierInfoInitialized) {
- // In case of checking C code, or when the corresponding headers are not
- // included, we might end up query the identifier table every time when
- // this function is called instead of early returning it. To avoid this, a
- // bool variable (IdentifierInfoInitialized) is used and the function will
- // be run only once.
- const auto &ASTCtx = Call.getState()->getStateManager().getContext();
- Guard = &ASTCtx.Idents.get(GuardName);
- }
- }
-
- template <typename T> bool matchesImpl(const CallEvent &Call) const {
- const T *C = dyn_cast<T>(&Call);
- if (!C)
- return false;
- const IdentifierInfo *II =
- cast<CXXRecordDecl>(C->getDecl()->getParent())->getIdentifier();
- return II == Guard;
- }
-
-public:
- RAIIMutexDescriptor(StringRef GuardName) : GuardName(GuardName) {}
- [[nodiscard]] bool matches(const CallEvent &Call, bool IsLock) const {
- initIdentifierInfo(Call);
- if (IsLock) {
- return matchesImpl<CXXConstructorCall>(Call);
- }
- return matchesImpl<CXXDestructorCall>(Call);
- }
- [[nodiscard]] const MemRegion *getRegion(const CallEvent &Call,
- bool IsLock) const {
- const MemRegion *LockRegion = nullptr;
- if (IsLock) {
- if (std::optional<SVal> Object = Call.getReturnValueUnderConstruction()) {
- LockRegion = Object->getAsRegion();
- }
- } else {
- LockRegion = cast<CXXDestructorCall>(Call).getCXXThisVal().getAsRegion();
- }
- return LockRegion;
- }
-};
-
-using MutexDescriptor =
- std::variant<FirstArgMutexDescriptor, MemberMutexDescriptor,
- RAIIMutexDescriptor>;
-
class BlockInCriticalSectionChecker : public Checker<check::PostCall> {
private:
- const std::array<MutexDescriptor, 8> MutexDescriptors{
- // NOTE: There are standard library implementations where some methods
- // of `std::mutex` are inherited from an implementation detail base
- // class, and those aren't matched by the name specification {"std",
- // "mutex", "lock"}.
- // As a workaround here we omit the class name and only require the
- // presence of the name parts "std" and "lock"/"unlock".
- // TODO: Ensure that CallDescription understands inherited methods.
- MemberMutexDescriptor(
- {/*MatchAs=*/CDM::CXXMethod,
- /*QualifiedName=*/{"std", /*"mutex",*/ "lock"},
- /*RequiredArgs=*/0},
- {CDM::CXXMethod, {"std", /*"mutex",*/ "unlock"}, 0}),
- FirstArgMutexDescriptor({CDM::CLibrary, {"pthread_mutex_lock"}, 1},
- {CDM::CLibrary, {"pthread_mutex_unlock"}, 1}),
- FirstArgMutexDescriptor({CDM::CLibrary, {"mtx_lock"}, 1},
- {CDM::CLibrary, {"mtx_unlock"}, 1}),
- FirstArgMutexDescriptor({CDM::CLibrary, {"pthread_mutex_trylock"}, 1},
- {CDM::CLibrary, {"pthread_mutex_unlock"}, 1}),
- FirstArgMutexDescriptor({CDM::CLibrary, {"mtx_trylock"}, 1},
- {CDM::CLibrary, {"mtx_unlock"}, 1}),
- FirstArgMutexDescriptor({CDM::CLibrary, {"mtx_timedlock"}, 1},
- {CDM::CLibrary, {"mtx_unlock"}, 1}),
- RAIIMutexDescriptor("lock_guard"),
- RAIIMutexDescriptor("unique_lock")};
-
const CallDescriptionSet BlockingFunctions{{CDM::CLibrary, {"sleep"}},
{CDM::CLibrary, {"getc"}},
{CDM::CLibrary, {"fgets"}},
@@ -182,147 +41,25 @@ class BlockInCriticalSectionChecker : public Checker<check::PostCall> {
const BugType BlockInCritSectionBugType{
this, "Call to blocking function in critical section", "Blocking Error"};
- void reportBlockInCritSection(const CallEvent &call, CheckerContext &C) const;
-
- [[nodiscard]] const NoteTag *createCritSectionNote(CritSectionMarker M,
- CheckerContext &C) const;
-
- [[nodiscard]] std::optional<MutexDescriptor>
- checkDescriptorMatch(const CallEvent &Call, CheckerContext &C,
- bool IsLock) const;
-
- void handleLock(const MutexDescriptor &Mutex, const CallEvent &Call,
- CheckerContext &C) const;
-
- void handleUnlock(const MutexDescriptor &Mutex, const CallEvent &Call,
- CheckerContext &C) const;
-
[[nodiscard]] bool isBlockingInCritSection(const CallEvent &Call,
CheckerContext &C) const;
+ void reportBlockInCritSection(const CallEvent &call, CheckerContext &C) const;
+
public:
- /// Process unlock.
- /// Process lock.
- /// Process blocking functions (sleep, getc, fgets, read, recv)
+ BlockInCriticalSectionChecker();
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
};
} // end anonymous namespace
-REGISTER_LIST_WITH_PROGRAMSTATE(ActiveCritSections, CritSectionMarker)
-
-// Iterator traits for ImmutableList data structure
-// that enable the use of STL algorithms.
-// TODO: Move these to llvm::ImmutableList when overhauling immutable data
-// structures for proper iterator concept support.
-template <>
-struct std::iterator_traits<
- typename llvm::ImmutableList<CritSectionMarker>::iterator> {
- using iterator_category = std::forward_iterator_tag;
- using value_type = CritSectionMarker;
- using difference_type = std::ptrdiff_t;
- using reference = CritSectionMarker &;
- using pointer = CritSectionMarker *;
-};
-
-std::optional<MutexDescriptor>
-BlockInCriticalSectionChecker::checkDescriptorMatch(const CallEvent &Call,
- CheckerContext &C,
- bool IsLock) const {
- const auto Descriptor =
- llvm::find_if(MutexDescriptors, [&Call, IsLock](auto &&Descriptor) {
- return std::visit(
- [&Call, IsLock](auto &&DescriptorImpl) {
- return DescriptorImpl.matches(Call, IsLock);
- },
- Descriptor);
- });
- if (Descriptor != MutexDescriptors.end())
- return *Descriptor;
- return std::nullopt;
-}
-
-static const MemRegion *skipStdBaseClassRegion(const MemRegion *Reg) {
- while (Reg) {
- const auto *BaseClassRegion = dyn_cast<CXXBaseObjectRegion>(Reg);
- if (!BaseClassRegion || !isWithinStdNamespace(BaseClassRegion->getDecl()))
- break;
- Reg = BaseClassRegion->getSuperRegion();
- }
- return Reg;
-}
-
-static const MemRegion *getRegion(const CallEvent &Call,
- const MutexDescriptor &Descriptor,
- bool IsLock) {
- return std::visit(
- [&Call, IsLock](auto &Descr) -> const MemRegion * {
- return skipStdBaseClassRegion(Descr.getRegion(Call, IsLock));
- },
- Descriptor);
-}
-
-void BlockInCriticalSectionChecker::handleLock(
- const MutexDescriptor &LockDescriptor, const CallEvent &Call,
- CheckerContext &C) const {
- const MemRegion *MutexRegion =
- getRegion(Call, LockDescriptor, /*IsLock=*/true);
- if (!MutexRegion)
- return;
-
- const CritSectionMarker MarkToAdd{Call.getOriginExpr(), MutexRegion};
- ProgramStateRef StateWithLockEvent =
- C.getState()->add<ActiveCritSections>(MarkToAdd);
- C.addTransition(StateWithLockEvent, createCritSectionNote(MarkToAdd, C));
-}
-
-void BlockInCriticalSectionChecker::handleUnlock(
- const MutexDescriptor &UnlockDescriptor, const CallEvent &Call,
- CheckerContext &C) const {
- const MemRegion *MutexRegion =
- getRegion(Call, UnlockDescriptor, /*IsLock=*/false);
- if (!MutexRegion)
- return;
-
- ProgramStateRef State = C.getState();
- const auto ActiveSections = State->get<ActiveCritSections>();
- const auto MostRecentLock =
- llvm::find_if(ActiveSections, [MutexRegion](auto &&Marker) {
- return Marker.LockReg == MutexRegion;
- });
- if (MostRecentLock == ActiveSections.end())
- return;
-
- // Build a new ImmutableList without this element.
- auto &Factory = State->get_context<ActiveCritSections>();
- llvm::ImmutableList<CritSectionMarker> NewList = Factory.getEmptyList();
- for (auto It = ActiveSections.begin(), End = ActiveSections.end(); It != End;
- ++It) {
- if (It != MostRecentLock)
- NewList = Factory.add(*It, NewList);
- }
-
- State = State->set<ActiveCritSections>(NewList);
- C.addTransition(State);
+BlockInCriticalSectionChecker::BlockInCriticalSectionChecker() {
+ RegisterBugTypeForMutexModeling(&BlockInCritSectionBugType);
}
bool BlockInCriticalSectionChecker::isBlockingInCritSection(
const CallEvent &Call, CheckerContext &C) const {
- return BlockingFunctions.contains(Call) &&
- !C.getState()->get<ActiveCritSections>().isEmpty();
-}
-
-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)) {
- handleUnlock(*UnlockDesc, Call, C);
- }
+ return BlockingFunctions.contains(Call) && AreAnyCritsectionsActive(C);
}
void BlockInCriticalSectionChecker::reportBlockInCritSection(
@@ -342,56 +79,56 @@ void BlockInCriticalSectionChecker::reportBlockInCritSection(
C.emitReport(std::move(R));
}
-const NoteTag *
-BlockInCriticalSectionChecker::createCritSectionNote(CritSectionMarker M,
- CheckerContext &C) const {
- const BugType *BT = &this->BlockInCritSectionBugType;
- return C.getNoteTag([M, BT](PathSensitiveBugReport &BR,
- llvm::raw_ostream &OS) {
- if (&BR.getBugType() != BT)
- return;
-
- // Get the lock events for the mutex of the current line's lock event.
- const auto CritSectionBegins =
- BR.getErrorNode()->getState()->get<ActiveCritSections>();
- llvm::SmallVector<CritSectionMarker, 4> LocksForMutex;
- llvm::copy_if(
- CritSectionBegins, std::back_inserter(LocksForMutex),
- [M](const auto &Marker) { return Marker.LockReg == M.LockReg; });
- if (LocksForMutex.empty())
- return;
-
- // As the ImmutableList builds the locks by prepending them, we
- // reverse the list to get the correct order.
- std::reverse(LocksForMutex.begin(), LocksForMutex.end());
-
- // Find the index of the lock expression in the list of all locks for a
- // given mutex (in acquisition order).
- const auto Position =
- llvm::find_if(std::as_const(LocksForMutex), [M](const auto &Marker) {
- return Marker.LockExpr == M.LockExpr;
- });
- if (Position == LocksForMutex.end())
- return;
-
- // If there is only one lock event, we don't need to specify how many times
- // the critical section was entered.
- if (LocksForMutex.size() == 1) {
- OS << "Entering critical section here";
- return;
- }
-
- const auto IndexOfLock =
- std::distance(std::as_const(LocksForMutex).begin(), Position);
-
- const auto OrdinalOfLock = IndexOfLock + 1;
- OS << "Entering critical section for the " << OrdinalOfLock
- << llvm::getOrdinalSuffix(OrdinalOfLock) << " time here";
- });
+void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call,
+ CheckerContext &C) const {
+ if (!isBlockingInCritSection(Call, C))
+ return;
+ reportBlockInCritSection(Call, C);
}
+// Checker registration
void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) {
mgr.registerChecker<BlockInCriticalSectionChecker>();
+
+ // Register events for std::mutex lock and unlock
+ // NOTE: There are standard library implementations where some methods
+ // of `std::mutex` are inherited from an implementation detail base
+ // class, and those aren't matched by the name specification {"std",
+ // "mutex", "lock"}.
+ // As a workaround here we omit the class name and only require the
+ // presence of the name parts "std" and "lock"/"unlock".
+ // TODO: Ensure that CallDescription understands inherited methods.
+ RegisterEvent(EventDescriptor{
+ mutex_modeling::MakeMemberExtractor({"std", /*"mutex"*/ "lock"}),
+ EventKind::Acquire, LibraryKind::NotApplicable,
+ SemanticsKind::XNUSemantics});
+ RegisterEvent(EventDescriptor{
+ mutex_modeling::MakeMemberExtractor({"std", /*"mutex"*/ "unlock"}),
+ EventKind::Release});
+
+ // Register events for std::lock_guard
+ RegisterEvent(EventDescriptor{
+ mutex_modeling::MakeRAIILockExtractor("lock_guard"), EventKind::Acquire,
+ LibraryKind::NotApplicable, SemanticsKind::XNUSemantics});
+ RegisterEvent(
+ EventDescriptor{mutex_modeling::MakeRAIIReleaseExtractor("lock_guard"),
+ EventKind::Release});
+
+ // Register events for std::unique_lock
+ RegisterEvent(EventDescriptor{
+ mutex_modeling::MakeRAIILockExtractor("unique_lock"), EventKind::Acquire,
+ LibraryKind::NotApplicable, SemanticsKind::XNUSemantics});
+ RegisterEvent(
+ EventDescriptor{mutex_modeling::MakeRAIIReleaseExtractor("unique_lock"),
+ EventKind::Release});
+
+ // Register events for std::scoped_lock
+ RegisterEvent(EventDescriptor{
+ mutex_modeling::MakeRAIILockExtractor("scoped_lock"), EventKind::Acquire,
+ LibraryKind::NotApplicable, SemanticsKind::XNUSemantics});
+ RegisterEvent(
+ EventDescriptor{mutex_modeling::MakeRAIIReleaseExtractor("scoped_lock"),
+ EventKind::Release});
}
bool ento::shouldRegisterBlockInCriticalSectionChecker(
diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
index 6da3665ab9a4df..c3987ba76529db 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -68,6 +68,7 @@ add_clang_library(clangStaticAnalyzerCheckers
MmapWriteExecChecker.cpp
MIGChecker.cpp
MoveChecker.cpp
+ MutexModeling.cpp
MPI-Checker/MPIBugReporter.cpp
MPI-Checker/MPIChecker.cpp
MPI-Checker/MPIFunctionClassifier.cpp
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
new file mode 100644
index 00000000000000..f0e13c5c95e432
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
@@ -0,0 +1,773 @@
+//===--- MutexModeling.cpp - Modeling of mutexes --------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Defines modeling checker for tracking mutex states.
+//
+//===----------------------------------------------------------------------===//
+
+#include "MutexModeling/MutexM...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/109636
More information about the cfe-commits
mailing list