[clang] [clang][analyzer][WIP] Introduce modeling for threading related checkers (PR #109636)
Endre Fülöp via cfe-commits
cfe-commits at lists.llvm.org
Mon Sep 23 05:43:44 PDT 2024
https://github.com/gamesh411 updated https://github.com/llvm/llvm-project/pull/109636
>From 423714812ecfc9677f9b35227fbeb10950193bb8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Mon, 3 Jun 2024 16:05:51 +0200
Subject: [PATCH 01/30] introduce mutex modeling checker
move mutex modeling to non-alpha
add checker to bookkeeping lists
---
.../clang/StaticAnalyzer/Checkers/Checkers.td | 6 +++++
.../BlockInCriticalSectionChecker.cpp | 2 ++
.../StaticAnalyzer/Checkers/MutexModeling.h | 23 +++++++++++++++++++
.../test/Analysis/analyzer-enabled-checkers.c | 1 +
...c-library-functions-arg-enabled-checkers.c | 1 +
5 files changed, 33 insertions(+)
create mode 100644 clang/lib/StaticAnalyzer/Checkers/MutexModeling.h
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index 585246547b3dce..d29b40833f0962 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -509,8 +509,14 @@ def UnixAPIMisuseChecker : Checker<"API">,
HelpText<"Check calls to various UNIX/Posix functions">,
Documentation<HasDocumentation>;
+def MutexModeling : Checker<"MutexModeling">,
+ HelpText<"Model mutexes lock and unlock events">,
+ Documentation<NotDocumented>,
+ Hidden;
+
def BlockInCriticalSectionChecker : Checker<"BlockInCriticalSection">,
HelpText<"Check for calls to blocking functions inside a critical section">,
+ Dependencies<[MutexModeling]>,
Documentation<HasDocumentation>;
def DynamicMemoryModeling: Checker<"DynamicMemoryModeling">,
diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
index 7460781799d08a..485dda61983aa0 100644
--- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
@@ -28,6 +28,8 @@
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
+#include "MutexModeling.h"
+
#include <iterator>
#include <utility>
#include <variant>
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.h
new file mode 100644
index 00000000000000..9d7e58e56eff6f
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.h
@@ -0,0 +1,23 @@
+//===--- MutexModeling.h - 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 inter-checker API for tracking and manipulating the
+// modeled state of locked mutexes in the GDM.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELING_H
+#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELING_H
+
+namespace clang {
+namespace ento {
+namespace mutex_modeling {} // namespace mutex_modeling
+} // namespace ento
+} // namespace clang
+
+#endif
diff --git a/clang/test/Analysis/analyzer-enabled-checkers.c b/clang/test/Analysis/analyzer-enabled-checkers.c
index e605c62a66ad0e..5fe24318a807b3 100644
--- a/clang/test/Analysis/analyzer-enabled-checkers.c
+++ b/clang/test/Analysis/analyzer-enabled-checkers.c
@@ -42,6 +42,7 @@
// CHECK-NEXT: security.insecureAPI.mktemp
// CHECK-NEXT: security.insecureAPI.vfork
// CHECK-NEXT: unix.API
+// CHECK-NEXT: unix.MutexModeling
// CHECK-NEXT: unix.BlockInCriticalSection
// CHECK-NEXT: unix.cstring.CStringModeling
// CHECK-NEXT: unix.DynamicMemoryModeling
diff --git a/clang/test/Analysis/std-c-library-functions-arg-enabled-checkers.c b/clang/test/Analysis/std-c-library-functions-arg-enabled-checkers.c
index 345a4e8f44efd1..c3bacb0706782b 100644
--- a/clang/test/Analysis/std-c-library-functions-arg-enabled-checkers.c
+++ b/clang/test/Analysis/std-c-library-functions-arg-enabled-checkers.c
@@ -50,6 +50,7 @@
// CHECK-NEXT: security.insecureAPI.mktemp
// CHECK-NEXT: security.insecureAPI.vfork
// CHECK-NEXT: unix.API
+// CHECK-NEXT: unix.MutexModeling
// CHECK-NEXT: unix.BlockInCriticalSection
// CHECK-NEXT: unix.cstring.CStringModeling
// CHECK-NEXT: unix.DynamicMemoryModeling
>From 8fe1cd4faba512cd95783d508455a556e0ab4416 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Thu, 6 Jun 2024 09:39:29 +0200
Subject: [PATCH 02/30] copy the modeling part of BlockInCriticalSection
checker to mutex modeling
move the GDM trait out of anonymous namespace to solve state sharing between checkers
add programstate header for the base template definition of ProgramStateTrait
---
.../StaticAnalyzer/Checkers/CMakeLists.txt | 1 +
.../StaticAnalyzer/Checkers/MutexModeling.cpp | 237 ++++++++++++++++++
.../StaticAnalyzer/Checkers/MutexModeling.h | 50 +++-
.../Checkers/MutexModelingGDM.h | 95 +++++++
4 files changed, 382 insertions(+), 1 deletion(-)
create mode 100644 clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
create mode 100644 clang/lib/StaticAnalyzer/Checkers/MutexModelingGDM.h
diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
index 414282d58f779f..92568f45935b6c 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..d0224133592838
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
@@ -0,0 +1,237 @@
+//===--- 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.h"
+
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
+#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"
+
+#include <variant>
+
+using namespace clang;
+using namespace ento;
+using namespace mutex_modeling;
+
+namespace {
+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>;
+
+const MemRegion *getRegion(const CallEvent &Call,
+ const MutexDescriptor &Descriptor, bool IsLock) {
+ return std::visit(
+ [&Call, IsLock](auto &&Descriptor) {
+ return Descriptor.getRegion(Call, IsLock);
+ },
+ Descriptor);
+}
+
+class MutexModeling : public Checker<check::PostCall> {
+ const std::array<MutexDescriptor, 8> MutexDescriptors{
+ 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")};
+ void handleLock(const MutexDescriptor &Mutex, const CallEvent &Call,
+ CheckerContext &C) const;
+
+ void handleUnlock(const MutexDescriptor &Mutex, const CallEvent &Call,
+ CheckerContext &C) const;
+
+ [[nodiscard]] std::optional<MutexDescriptor>
+ checkDescriptorMatch(const CallEvent &Call, CheckerContext &C,
+ bool IsLock) const;
+
+public:
+ void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+};
+
+void MutexModeling::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 MutexModeling::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);
+}
+
+std::optional<MutexDescriptor>
+MutexModeling::checkDescriptorMatch(const CallEvent &Call, CheckerContext &C,
+ bool IsLock) const {
+ std::optional<MutexDescriptor> Descriptor;
+ const auto DescriptorIt =
+ llvm::find_if(MutexDescriptors, [&Call, IsLock](auto &&Descriptor) {
+ return std::visit(
+ [&Call, IsLock](auto &&DescriptorImpl) {
+ return DescriptorImpl.matches(Call, IsLock);
+ },
+ Descriptor);
+ });
+ if (DescriptorIt != MutexDescriptors.end())
+ Descriptor.emplace(*DescriptorIt);
+ return Descriptor;
+}
+
+void MutexModeling::checkPostCall(const CallEvent &Call,
+ CheckerContext &C) const {
+ 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);
+ }
+}
+
+} // namespace
+
+namespace clang {
+namespace ento {
+// Checker registration
+void registerMutexModeling(CheckerManager &mgr) {
+ mgr.registerChecker<MutexModeling>();
+}
+bool shouldRegisterMutexModeling(const CheckerManager &) { return true; }
+} // namespace ento
+} // namespace clang
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.h
index 9d7e58e56eff6f..2653dc22d2de33 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.h
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.h
@@ -14,9 +14,57 @@
#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELING_H
#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELING_H
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "llvm/ADT/StringExtras.h"
+
+#include "MutexModelingGDM.h"
+
namespace clang {
namespace ento {
-namespace mutex_modeling {} // namespace mutex_modeling
+namespace mutex_modeling {
+inline const NoteTag *createCritSectionNote(CritSectionMarker M,
+ CheckerContext &C) {
+ return C.getNoteTag([M](const PathSensitiveBugReport &BR,
+ llvm::raw_ostream &OS) {
+ 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 CritSectionMarker *const 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";
+ });
+}
+
+} // namespace mutex_modeling
} // namespace ento
} // namespace clang
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModelingGDM.h b/clang/lib/StaticAnalyzer/Checkers/MutexModelingGDM.h
new file mode 100644
index 00000000000000..6065fa829f270d
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModelingGDM.h
@@ -0,0 +1,95 @@
+//===--- MutexModelingGDM.h - 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 the GDM definitions for tracking mutex states.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELINGGDM_H
+#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELINGGDM_H
+
+#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
+
+namespace clang {
+
+class Expr;
+
+namespace ento {
+
+class MemRegion;
+
+namespace mutex_modeling {
+struct CritSectionMarker {
+ const clang::Expr *LockExpr{};
+ const clang::ento::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);
+ }
+};
+
+// GDM-related handle-types for tracking mutex states.
+class ActiveCritSections {};
+using ActiveCritSectionsTy = llvm ::ImmutableList<CritSectionMarker>;
+
+} // namespace mutex_modeling
+} // namespace ento
+} // namespace clang
+
+// shorthand for the type of the GDM handle.
+namespace {
+using MutexModelingCritSectionMarker =
+ clang::ento::mutex_modeling::CritSectionMarker;
+} // namespace
+
+// Iterator traits for ImmutableList data structure
+// that enable the use of STL algorithms.
+namespace std {
+// TODO: Move these to llvm::ImmutableList when overhauling immutable data
+// structures for proper iterator concept support.
+
+template <>
+struct iterator_traits<
+ typename llvm::ImmutableList<MutexModelingCritSectionMarker>::iterator> {
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = MutexModelingCritSectionMarker;
+ using difference_type = std::ptrdiff_t;
+ using reference = MutexModelingCritSectionMarker &;
+ using pointer = MutexModelingCritSectionMarker *;
+};
+} // namespace std
+
+// FIXME: ProgramState macros are not used here, because the visibility of these
+// GDM entries must span multiple translation units (multiple checkers).
+namespace clang {
+namespace ento {
+template <>
+struct ProgramStateTrait<clang::ento::mutex_modeling::ActiveCritSections>
+ : public ProgramStatePartialTrait<
+ clang::ento::mutex_modeling::ActiveCritSectionsTy> {
+ static void *GDMIndex() {
+ static int Index;
+ return &Index;
+ }
+};
+} // namespace ento
+} // namespace clang
+
+#endif
>From 7916c8b422120f682658b609366b91f2a6b3a1db Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Thu, 6 Jun 2024 09:40:13 +0200
Subject: [PATCH 03/30] strip modeling logic out of
BlockInCriticalSectionChecker
---
.../BlockInCriticalSectionChecker.cpp | 331 +-----------------
1 file changed, 11 insertions(+), 320 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
index 485dda61983aa0..70e289686512d3 100644
--- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
@@ -14,167 +14,24 @@
//
//===----------------------------------------------------------------------===//
+#include "MutexModeling.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 "MutexModeling.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"}},
@@ -184,149 +41,23 @@ 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)
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);
-}
-
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);
- }
-}
-
void BlockInCriticalSectionChecker::reportBlockInCritSection(
const CallEvent &Call, CheckerContext &C) const {
ExplodedNode *ErrNode = C.generateNonFatalErrorNode(C.getState());
@@ -344,54 +75,14 @@ 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>();
}
>From ed909dc74160c68a43024344c4207d2a26b29e5c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Thu, 6 Jun 2024 22:25:44 +0200
Subject: [PATCH 04/30] add minimal interchecker api for modeling
BlockInCriticalSection usecases and add checker registration capability
---
.../BlockInCriticalSectionChecker.cpp | 3 +-
.../StaticAnalyzer/Checkers/MutexModeling.cpp | 2 +-
.../StaticAnalyzer/Checkers/MutexModeling.h | 29 ++++++++++++++++---
3 files changed, 27 insertions(+), 7 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
index 70e289686512d3..11433516bad065 100644
--- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
@@ -54,8 +54,7 @@ class BlockInCriticalSectionChecker : public Checker<check::PostCall> {
bool BlockInCriticalSectionChecker::isBlockingInCritSection(
const CallEvent &Call, CheckerContext &C) const {
- return BlockingFunctions.contains(Call) &&
- !C.getState()->get<ActiveCritSections>().isEmpty();
+ return BlockingFunctions.contains(Call) && AreAnyCritsectionsActive(C);
}
void BlockInCriticalSectionChecker::reportBlockInCritSection(
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
index d0224133592838..b745cdead68167 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
@@ -163,7 +163,7 @@ void MutexModeling::handleLock(const MutexDescriptor &LockDescriptor,
const CritSectionMarker MarkToAdd{Call.getOriginExpr(), MutexRegion};
ProgramStateRef StateWithLockEvent =
C.getState()->add<ActiveCritSections>(MarkToAdd);
- C.addTransition(StateWithLockEvent, createCritSectionNote(MarkToAdd, C));
+ C.addTransition(StateWithLockEvent, CreateMutexCritSectionNote(MarkToAdd, C));
}
void MutexModeling::handleUnlock(const MutexDescriptor &UnlockDescriptor,
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.h
index 2653dc22d2de33..690e8473323f4f 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.h
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.h
@@ -14,18 +14,39 @@
#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELING_H
#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELING_H
+#include "MutexModelingGDM.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/StringExtras.h"
-#include "MutexModelingGDM.h"
-
namespace clang {
+
namespace ento {
+class BugType;
namespace mutex_modeling {
-inline const NoteTag *createCritSectionNote(CritSectionMarker M,
- CheckerContext &C) {
+
+inline llvm::SmallSet<const BugType *, 8> RegisteredCheckers{};
+
+inline void RegisterCheckerForMutexModeling(const BugType *BT) {
+ RegisteredCheckers.insert(BT);
+}
+
+inline bool IsCheckerRegisteredForMutexModeling(const BugType *BT) {
+ return RegisteredCheckers.contains(BT);
+}
+
+inline bool AreAnyCritsectionsActive(CheckerContext &C) {
+ return !C.getState()->get<ActiveCritSections>().isEmpty();
+}
+
+inline const NoteTag *CreateMutexCritSectionNote(CritSectionMarker M,
+ CheckerContext &C) {
return C.getNoteTag([M](const PathSensitiveBugReport &BR,
llvm::raw_ostream &OS) {
+ if (!IsCheckerRegisteredForMutexModeling(&BR.getBugType()))
+ return;
const auto CritSectionBegins =
BR.getErrorNode()->getState()->get<ActiveCritSections>();
llvm::SmallVector<CritSectionMarker, 4> LocksForMutex;
>From ff51ad25e23439c852f0ecf14629d95c6a61d1a0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Thu, 6 Jun 2024 22:26:21 +0200
Subject: [PATCH 05/30] register BlockInCriticalSectionCheckers bugtype for
mutex related notes
---
.../Checkers/BlockInCriticalSectionChecker.cpp | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
index 11433516bad065..5a15c0321ddc11 100644
--- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
@@ -22,7 +22,6 @@
#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/ProgramState_Fwd.h"
#include <utility>
using namespace clang;
@@ -47,11 +46,16 @@ class BlockInCriticalSectionChecker : public Checker<check::PostCall> {
void reportBlockInCritSection(const CallEvent &call, CheckerContext &C) const;
public:
+ BlockInCriticalSectionChecker();
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
};
} // end anonymous namespace
+BlockInCriticalSectionChecker::BlockInCriticalSectionChecker() {
+ RegisterCheckerForMutexModeling(&BlockInCritSectionBugType);
+}
+
bool BlockInCriticalSectionChecker::isBlockingInCritSection(
const CallEvent &Call, CheckerContext &C) const {
return BlockingFunctions.contains(Call) && AreAnyCritsectionsActive(C);
>From 6e0dbda9e2195641817118db986a8312468b7d53 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Tue, 9 Jul 2024 03:37:33 +0200
Subject: [PATCH 06/30] move mutex modeling headers into a subdirectory
---
.../{ => MutexModeling}/MutexModeling.h | 0
.../{ => MutexModeling}/MutexModelingGDM.h | 54 ++++++++++++++++---
2 files changed, 47 insertions(+), 7 deletions(-)
rename clang/lib/StaticAnalyzer/Checkers/{ => MutexModeling}/MutexModeling.h (100%)
rename clang/lib/StaticAnalyzer/Checkers/{ => MutexModeling}/MutexModelingGDM.h (66%)
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModeling.h
similarity index 100%
rename from clang/lib/StaticAnalyzer/Checkers/MutexModeling.h
rename to clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModeling.h
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModelingGDM.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingGDM.h
similarity index 66%
rename from clang/lib/StaticAnalyzer/Checkers/MutexModelingGDM.h
rename to clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingGDM.h
index 6065fa829f270d..0e20ecc5941ca6 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModelingGDM.h
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingGDM.h
@@ -26,28 +26,68 @@ namespace ento {
class MemRegion;
namespace mutex_modeling {
-struct CritSectionMarker {
- const clang::Expr *LockExpr{};
- const clang::ento::MemRegion *LockReg{};
+
+enum class EventKind { Init, Acquire, TryAcquire, Release, Destroy };
+
+enum class SyntaxKind { FirstArg, Member, RAII };
+
+enum class LockingSemanticsKind { PthreadSemantics, XNUSemantics };
+
+enum class LockStateKind {
+ Unlocked,
+ Locked,
+ Destroyed,
+ UntouchedAndPossiblyDestroyed,
+ UnlockedAndPossiblyDestroyed
+};
+
+struct EventDescriptor {
+ EventKind Event;
+ SyntaxKind Syntax;
+ LockingSemanticsKind Semantics;
+
+ [[nodiscard]] constexpr bool
+ operator==(const EventDescriptor &Other) const noexcept {
+ return Event == Other.Event && Syntax == Other.Syntax &&
+ Semantics == Other.Semantics;
+ }
void Profile(llvm::FoldingSetNodeID &ID) const {
+ ID.Add(Event);
+ ID.Add(Syntax);
+ ID.Add(Semantics);
+ }
+};
+
+struct EventMarker {
+ EventDescriptor Descriptor;
+ LockStateKind LockState;
+ clang::Expr *LockExpr{};
+ clang::ento::MemRegion *LockReg{};
+
+ void Profile(llvm::FoldingSetNodeID &ID) const {
+ ID.Add(Descriptor);
+ ID.Add(LockState);
ID.Add(LockExpr);
ID.Add(LockReg);
}
[[nodiscard]] constexpr bool
- operator==(const CritSectionMarker &Other) const noexcept {
- return LockExpr == Other.LockExpr && LockReg == Other.LockReg;
+ operator==(const EventMarker &Other) const noexcept {
+ return Descriptor == Other.Descriptor && LockState == Other.LockState &&
+ LockExpr == Other.LockExpr && LockReg == Other.LockReg;
}
[[nodiscard]] constexpr bool
- operator!=(const CritSectionMarker &Other) const noexcept {
+ operator!=(const EventMarker &Other) const noexcept {
return !(*this == Other);
}
};
+struct CritSectionMarker {};
+
// GDM-related handle-types for tracking mutex states.
class ActiveCritSections {};
-using ActiveCritSectionsTy = llvm ::ImmutableList<CritSectionMarker>;
+using ActiveCritSectionsTy = llvm ::ImmutableList<EventMarker>;
} // namespace mutex_modeling
} // namespace ento
>From 393168c50671a5f892d4ff0e7560ebbfbb03af48 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Tue, 9 Jul 2024 05:03:34 +0200
Subject: [PATCH 07/30] reorganize headers, add basic modeling types
---
.../BlockInCriticalSectionChecker.cpp | 2 +-
.../StaticAnalyzer/Checkers/MutexModeling.cpp | 22 +--
.../{MutexModeling.h => MutexModelingAPI.h} | 15 +-
.../MutexModeling/MutexModelingDomain.h | 91 ++++++++++++
.../Checkers/MutexModeling/MutexModelingGDM.h | 136 ++++++++----------
5 files changed, 168 insertions(+), 98 deletions(-)
rename clang/lib/StaticAnalyzer/Checkers/MutexModeling/{MutexModeling.h => MutexModelingAPI.h} (85%)
create mode 100644 clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h
diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
index 5a15c0321ddc11..91c3d154c3bbc2 100644
--- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
@@ -14,7 +14,7 @@
//
//===----------------------------------------------------------------------===//
-#include "MutexModeling.h"
+#include "MutexModeling/MutexModelingAPI.h"
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
index b745cdead68167..e4f99724836882 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
@@ -10,10 +10,12 @@
//
//===----------------------------------------------------------------------===//
-#include "MutexModeling.h"
+#include "MutexModeling/MutexModelingAPI.h"
+#include "MutexModeling/MutexModelingDomain.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"
#include <variant>
@@ -96,15 +98,15 @@ class RAIIMutexDescriptor {
}
[[nodiscard]] const MemRegion *getRegion(const CallEvent &Call,
bool IsLock) const {
- const MemRegion *LockRegion = nullptr;
+ const MemRegion *MutexRegion = nullptr;
if (IsLock) {
if (std::optional<SVal> Object = Call.getReturnValueUnderConstruction()) {
- LockRegion = Object->getAsRegion();
+ MutexRegion = Object->getAsRegion();
}
} else {
- LockRegion = cast<CXXDestructorCall>(Call).getCXXThisVal().getAsRegion();
+ MutexRegion = cast<CXXDestructorCall>(Call).getCXXThisVal().getAsRegion();
}
- return LockRegion;
+ return MutexRegion;
}
};
@@ -162,7 +164,7 @@ void MutexModeling::handleLock(const MutexDescriptor &LockDescriptor,
const CritSectionMarker MarkToAdd{Call.getOriginExpr(), MutexRegion};
ProgramStateRef StateWithLockEvent =
- C.getState()->add<ActiveCritSections>(MarkToAdd);
+ C.getState()->add<CritSections>(MarkToAdd);
C.addTransition(StateWithLockEvent, CreateMutexCritSectionNote(MarkToAdd, C));
}
@@ -175,16 +177,16 @@ void MutexModeling::handleUnlock(const MutexDescriptor &UnlockDescriptor,
return;
ProgramStateRef State = C.getState();
- const auto ActiveSections = State->get<ActiveCritSections>();
+ const auto ActiveSections = State->get<CritSections>();
const auto MostRecentLock =
llvm::find_if(ActiveSections, [MutexRegion](auto &&Marker) {
- return Marker.LockReg == MutexRegion;
+ return Marker.MutexRegion == MutexRegion;
});
if (MostRecentLock == ActiveSections.end())
return;
// Build a new ImmutableList without this element.
- auto &Factory = State->get_context<ActiveCritSections>();
+ auto &Factory = State->get_context<CritSections>();
llvm::ImmutableList<CritSectionMarker> NewList = Factory.getEmptyList();
for (auto It = ActiveSections.begin(), End = ActiveSections.end(); It != End;
++It) {
@@ -192,7 +194,7 @@ void MutexModeling::handleUnlock(const MutexDescriptor &UnlockDescriptor,
NewList = Factory.add(*It, NewList);
}
- State = State->set<ActiveCritSections>(NewList);
+ State = State->set<CritSections>(NewList);
C.addTransition(State);
}
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModeling.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingAPI.h
similarity index 85%
rename from clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModeling.h
rename to clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingAPI.h
index 690e8473323f4f..bdea2aba6470b2 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModeling.h
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingAPI.h
@@ -1,4 +1,4 @@
-//===--- MutexModeling.h - Modeling of mutexes ----------------------------===//
+//===--- MutexModelingAPI.h - API for modeling mutexes --------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@@ -11,9 +11,10 @@
//
//===----------------------------------------------------------------------===//
-#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELING_H
-#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELING_H
+#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELINGAPI_H
+#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELINGAPI_H
+#include "MutexModelingDomain.h"
#include "MutexModelingGDM.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
@@ -38,7 +39,7 @@ inline bool IsCheckerRegisteredForMutexModeling(const BugType *BT) {
}
inline bool AreAnyCritsectionsActive(CheckerContext &C) {
- return !C.getState()->get<ActiveCritSections>().isEmpty();
+ return !C.getState()->get<CritSections>().isEmpty();
}
inline const NoteTag *CreateMutexCritSectionNote(CritSectionMarker M,
@@ -48,11 +49,11 @@ inline const NoteTag *CreateMutexCritSectionNote(CritSectionMarker M,
if (!IsCheckerRegisteredForMutexModeling(&BR.getBugType()))
return;
const auto CritSectionBegins =
- BR.getErrorNode()->getState()->get<ActiveCritSections>();
+ BR.getErrorNode()->getState()->get<CritSections>();
llvm::SmallVector<CritSectionMarker, 4> LocksForMutex;
llvm::copy_if(
CritSectionBegins, std::back_inserter(LocksForMutex),
- [M](const auto &Marker) { return Marker.LockReg == M.LockReg; });
+ [M](const auto &Marker) { return Marker.MutexRegion == M.MutexRegion; });
if (LocksForMutex.empty())
return;
@@ -64,7 +65,7 @@ inline const NoteTag *CreateMutexCritSectionNote(CritSectionMarker M,
// given mutex (in acquisition order).
const CritSectionMarker *const Position =
llvm::find_if(std::as_const(LocksForMutex), [M](const auto &Marker) {
- return Marker.LockExpr == M.LockExpr;
+ return Marker.BeginExpr == M.BeginExpr;
});
if (Position == LocksForMutex.end())
return;
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h
new file mode 100644
index 00000000000000..609d4ec974a2c7
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h
@@ -0,0 +1,91 @@
+//===--- MutexModelingDomain.h - Common vocabulary for modeling 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 common types and related functions used in the mutex modeling domain.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELINGDOMAIN_H
+#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELINGDOMAIN_H
+
+namespace clang {
+
+class Expr;
+
+namespace ento {
+
+class MemRegion;
+
+namespace mutex_modeling {
+
+enum class EventKind { Init, Acquire, TryAcquire, Release, Destroy };
+
+enum class SyntaxKind { FirstArg, Member, RAII };
+
+enum class LockingSemanticsKind { PthreadSemantics, XNUSemantics };
+
+enum class LockStateKind {
+ Unlocked,
+ Locked,
+ Destroyed,
+ UntouchedAndPossiblyDestroyed,
+ UnlockedAndPossiblyDestroyed
+};
+
+struct EventDescriptor {
+ EventKind Kind{};
+ SyntaxKind Syntax{};
+ LockingSemanticsKind Semantics{};
+
+ [[nodiscard]] constexpr bool
+ operator==(const EventDescriptor &Other) const noexcept {
+ return Kind == Other.Kind && Syntax == Other.Syntax &&
+ Semantics == Other.Semantics;
+ }
+ [[nodiscard]] constexpr bool
+ operator!=(const EventDescriptor &Other) const noexcept {
+ return !(*this == Other);
+ }
+};
+
+struct EventMarker {
+ EventDescriptor Event{};
+ LockStateKind LockState{};
+ const clang::Expr *EventExpr{};
+ const clang::ento::MemRegion *MutexRegion{};
+
+ [[nodiscard]] constexpr bool
+ operator==(const EventMarker &Other) const noexcept {
+ return Event == Other.Event && LockState == Other.LockState &&
+ EventExpr == Other.EventExpr && MutexRegion == Other.MutexRegion;
+ }
+ [[nodiscard]] constexpr bool
+ operator!=(const EventMarker &Other) const noexcept {
+ return !(*this == Other);
+ }
+};
+
+struct CritSectionMarker {
+ const clang::Expr *BeginExpr;
+ const clang::ento::MemRegion *MutexRegion;
+
+ [[nodiscard]] constexpr bool
+ operator==(const CritSectionMarker &Other) const noexcept {
+ return BeginExpr == Other.BeginExpr && MutexRegion == Other.MutexRegion;
+ }
+ [[nodiscard]] constexpr bool
+ operator!=(const CritSectionMarker &Other) const noexcept {
+ return !(*this == Other);
+ }
+};
+
+} // namespace mutex_modeling
+} // namespace ento
+} // namespace clang
+
+#endif
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingGDM.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingGDM.h
index 0e20ecc5941ca6..3cd83c3c37123d 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingGDM.h
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingGDM.h
@@ -1,5 +1,4 @@
-//===--- MutexModelingGDM.h - Modeling of mutexes -------------------------===//
-//----------------------------===//
+//===--- MutexModelingGDM.h - Modeling of mutexes in GDM ------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@@ -14,116 +13,93 @@
#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELINGGDM_H
#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELINGGDM_H
+#include "MutexModelingDomain.h"
+
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
+#include "llvm/ADT/FoldingSet.h"
+// GDM-related handle-types for tracking mutex states.
namespace clang {
-
-class Expr;
-
namespace ento {
-
-class MemRegion;
-
namespace mutex_modeling {
-enum class EventKind { Init, Acquire, TryAcquire, Release, Destroy };
-
-enum class SyntaxKind { FirstArg, Member, RAII };
-
-enum class LockingSemanticsKind { PthreadSemantics, XNUSemantics };
-
-enum class LockStateKind {
- Unlocked,
- Locked,
- Destroyed,
- UntouchedAndPossiblyDestroyed,
- UnlockedAndPossiblyDestroyed
-};
+class MutexEvents {};
+using MutexEventsTy = llvm::ImmutableList<EventMarker>;
-struct EventDescriptor {
- EventKind Event;
- SyntaxKind Syntax;
- LockingSemanticsKind Semantics;
+class CritSections {};
+using CritSectionsTy = llvm::ImmutableList<CritSectionMarker>;
- [[nodiscard]] constexpr bool
- operator==(const EventDescriptor &Other) const noexcept {
- return Event == Other.Event && Syntax == Other.Syntax &&
- Semantics == Other.Semantics;
- }
+} // namespace mutex_modeling
+} // namespace ento
+} // namespace clang
- void Profile(llvm::FoldingSetNodeID &ID) const {
- ID.Add(Event);
- ID.Add(Syntax);
- ID.Add(Semantics);
+// Enable usage of mutex modeling data structures in llvm::FoldingSet.
+namespace llvm {
+template <> struct FoldingSetTrait<clang::ento::mutex_modeling::EventMarker> {
+ static void Profile(const clang::ento::mutex_modeling::EventMarker &EM,
+ llvm::FoldingSetNodeID &ID) {
+ ID.Add(EM.Event.Kind);
+ ID.Add(EM.Event.Syntax);
+ ID.Add(EM.Event.Semantics);
+ ID.Add(EM.LockState);
+ ID.Add(EM.EventExpr);
+ ID.Add(EM.MutexRegion);
}
};
-struct EventMarker {
- EventDescriptor Descriptor;
- LockStateKind LockState;
- clang::Expr *LockExpr{};
- clang::ento::MemRegion *LockReg{};
-
- void Profile(llvm::FoldingSetNodeID &ID) const {
- ID.Add(Descriptor);
- ID.Add(LockState);
- ID.Add(LockExpr);
- ID.Add(LockReg);
- }
-
- [[nodiscard]] constexpr bool
- operator==(const EventMarker &Other) const noexcept {
- return Descriptor == Other.Descriptor && LockState == Other.LockState &&
- LockExpr == Other.LockExpr && LockReg == Other.LockReg;
- }
- [[nodiscard]] constexpr bool
- operator!=(const EventMarker &Other) const noexcept {
- return !(*this == Other);
+template <>
+struct FoldingSetTrait<clang::ento::mutex_modeling::CritSectionMarker> {
+ static void Profile(const clang::ento::mutex_modeling::CritSectionMarker &CSM,
+ llvm::FoldingSetNodeID &ID) {
+ ID.Add(CSM.BeginExpr);
+ ID.Add(CSM.MutexRegion);
}
};
-
-struct CritSectionMarker {};
-
-// GDM-related handle-types for tracking mutex states.
-class ActiveCritSections {};
-using ActiveCritSectionsTy = llvm ::ImmutableList<EventMarker>;
-
-} // namespace mutex_modeling
-} // namespace ento
-} // namespace clang
-
-// shorthand for the type of the GDM handle.
-namespace {
-using MutexModelingCritSectionMarker =
- clang::ento::mutex_modeling::CritSectionMarker;
-} // namespace
+} // namespace llvm
// Iterator traits for ImmutableList data structure
// that enable the use of STL algorithms.
namespace std {
// TODO: Move these to llvm::ImmutableList when overhauling immutable data
// structures for proper iterator concept support.
-
template <>
-struct iterator_traits<
- typename llvm::ImmutableList<MutexModelingCritSectionMarker>::iterator> {
+struct iterator_traits<typename llvm::ImmutableList<
+ clang::ento::mutex_modeling::EventMarker>::iterator> {
using iterator_category = std::forward_iterator_tag;
- using value_type = MutexModelingCritSectionMarker;
+ using value_type = clang::ento::mutex_modeling::EventMarker;
using difference_type = std::ptrdiff_t;
- using reference = MutexModelingCritSectionMarker &;
- using pointer = MutexModelingCritSectionMarker *;
+ using reference = clang::ento::mutex_modeling::EventMarker &;
+ using pointer = clang::ento::mutex_modeling::EventMarker *;
+};
+template <>
+struct iterator_traits<typename llvm::ImmutableList<
+ clang::ento::mutex_modeling::CritSectionMarker>::iterator> {
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = clang::ento::mutex_modeling::CritSectionMarker;
+ using difference_type = std::ptrdiff_t;
+ using reference = clang::ento::mutex_modeling::CritSectionMarker &;
+ using pointer = clang::ento::mutex_modeling::CritSectionMarker *;
};
} // namespace std
-// FIXME: ProgramState macros are not used here, because the visibility of these
+// NOTE: ProgramState macros are not used here, because the visibility of these
// GDM entries must span multiple translation units (multiple checkers).
namespace clang {
namespace ento {
template <>
-struct ProgramStateTrait<clang::ento::mutex_modeling::ActiveCritSections>
+struct ProgramStateTrait<clang::ento::mutex_modeling::MutexEvents>
+ : public ProgramStatePartialTrait<
+ clang::ento::mutex_modeling::MutexEventsTy> {
+ static void *GDMIndex() {
+ static int Index;
+ return &Index;
+ }
+};
+template <>
+struct ProgramStateTrait<clang::ento::mutex_modeling::CritSections>
: public ProgramStatePartialTrait<
- clang::ento::mutex_modeling::ActiveCritSectionsTy> {
+ clang::ento::mutex_modeling::CritSectionsTy> {
static void *GDMIndex() {
static int Index;
return &Index;
>From 643d5401cb0382f4b5b415676c4a63f75dbbb71d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Wed, 10 Jul 2024 11:51:45 +0200
Subject: [PATCH 08/30] implement unified modeling
[WIP] stab 2 at unified modeling, just init events
fix brace error
---
.../StaticAnalyzer/Checkers/MutexModeling.cpp | 316 +++++++++---------
.../Checkers/MutexModeling/MutexModelingAPI.h | 7 +-
.../MutexModeling/MutexModelingDefs.h | 212 ++++++++++++
.../MutexModeling/MutexModelingDomain.h | 54 +--
.../Checkers/MutexModeling/MutexModelingGDM.h | 65 +++-
.../MutexModeling/MutexRegionExtractor.h | 131 ++++++++
6 files changed, 601 insertions(+), 184 deletions(-)
create mode 100644 clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDefs.h
create mode 100644 clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexRegionExtractor.h
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
index e4f99724836882..badb6ed64fd312 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
@@ -11,154 +11,77 @@
//===----------------------------------------------------------------------===//
#include "MutexModeling/MutexModelingAPI.h"
+#include "MutexModeling/MutexModelingDefs.h"
#include "MutexModeling/MutexModelingDomain.h"
+#include "MutexModeling/MutexRegionExtractor.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"
-
-#include <variant>
+#include <memory>
using namespace clang;
using namespace ento;
using namespace mutex_modeling;
namespace {
-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);
+// When a lock is destroyed, in some semantics(like PthreadSemantics) we are not
+// sure if the destroy call has succeeded or failed, and the lock enters one of
+// the 'possibly destroyed' state. There is a short time frame for the
+// programmer to check the return value to see if the lock was successfully
+// destroyed. Before we model the next operation over that lock, we call this
+// function to see if the return value was checked by now and set the lock state
+// - either to destroyed state or back to its previous state.
+
+// In PthreadSemantics, pthread_mutex_destroy() returns zero if the lock is
+// successfully destroyed and it returns a non-zero value otherwise.
+ProgramStateRef resolvePossiblyDestroyedMutex(ProgramStateRef State,
+ const MemRegion *LockReg,
+ const SymbolRef *Sym) {
+ const LockStateKind *LState = State->get<LockStates>(LockReg);
+ // Existence in DestroyRetVal ensures existence in LockMap.
+ // Existence in Destroyed also ensures that the lock state for lockR is either
+ // UntouchedAndPossiblyDestroyed or UnlockedAndPossiblyDestroyed.
+ assert(LState);
+ assert(*LState == LockStateKind::UntouchedAndPossiblyDestroyed ||
+ *LState == LockStateKind::UnlockedAndPossiblyDestroyed);
+
+ ConstraintManager &CMgr = State->getConstraintManager();
+ ConditionTruthVal RetZero = CMgr.isNull(State, *Sym);
+ if (RetZero.isConstrainedFalse()) {
+ switch (*LState) {
+ case LockStateKind::UntouchedAndPossiblyDestroyed: {
+ State = State->remove<LockStates>(LockReg);
+ break;
}
- 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);
+ case LockStateKind::UnlockedAndPossiblyDestroyed: {
+ State = State->set<LockStates>(LockReg, LockStateKind::Unlocked);
+ break;
}
- }
-
- 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);
+ default: {
+ State = State->set<LockStates>(LockReg, LockStateKind::Destroyed);
}
- return matchesImpl<CXXDestructorCall>(Call);
- }
- [[nodiscard]] const MemRegion *getRegion(const CallEvent &Call,
- bool IsLock) const {
- const MemRegion *MutexRegion = nullptr;
- if (IsLock) {
- if (std::optional<SVal> Object = Call.getReturnValueUnderConstruction()) {
- MutexRegion = Object->getAsRegion();
- }
- } else {
- MutexRegion = cast<CXXDestructorCall>(Call).getCXXThisVal().getAsRegion();
}
- return MutexRegion;
}
-};
-using MutexDescriptor =
- std::variant<FirstArgMutexDescriptor, MemberMutexDescriptor,
- RAIIMutexDescriptor>;
-
-const MemRegion *getRegion(const CallEvent &Call,
- const MutexDescriptor &Descriptor, bool IsLock) {
- return std::visit(
- [&Call, IsLock](auto &&Descriptor) {
- return Descriptor.getRegion(Call, IsLock);
- },
- Descriptor);
+ // Removing the map entry (LockReg, sym) from DestroyRetVal as the lock
+ // state is now resolved.
+ return State->remove<DestroyedRetVals>(LockReg);
}
-class MutexModeling : public Checker<check::PostCall> {
- const std::array<MutexDescriptor, 8> MutexDescriptors{
- 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")};
- void handleLock(const MutexDescriptor &Mutex, const CallEvent &Call,
- CheckerContext &C) const;
-
- void handleUnlock(const MutexDescriptor &Mutex, const CallEvent &Call,
- CheckerContext &C) const;
-
- [[nodiscard]] std::optional<MutexDescriptor>
- checkDescriptorMatch(const CallEvent &Call, CheckerContext &C,
- bool IsLock) const;
+ProgramStateRef doResolvePossiblyDestroyedMutex(ProgramStateRef State,
+ const MemRegion *MTX) {
+ assert(MTX && "should only be called with a mutex region");
-public:
- void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
-};
+ if (const SymbolRef *Sym = State->get<DestroyedRetVals>(MTX))
+ return resolvePossiblyDestroyedMutex(State, MTX, Sym);
+ return State;
+}
-void MutexModeling::handleLock(const MutexDescriptor &LockDescriptor,
- const CallEvent &Call, CheckerContext &C) const {
- const MemRegion *MutexRegion =
- getRegion(Call, LockDescriptor, /*IsLock=*/true);
+void updateCritSectionOnLock(const MutexRegionExtractor &LockDescriptor,
+ const CallEvent &Call, CheckerContext &C) {
+ const MemRegion *MutexRegion = getRegion(LockDescriptor, Call);
if (!MutexRegion)
return;
@@ -168,11 +91,9 @@ void MutexModeling::handleLock(const MutexDescriptor &LockDescriptor,
C.addTransition(StateWithLockEvent, CreateMutexCritSectionNote(MarkToAdd, C));
}
-void MutexModeling::handleUnlock(const MutexDescriptor &UnlockDescriptor,
- const CallEvent &Call,
- CheckerContext &C) const {
- const MemRegion *MutexRegion =
- getRegion(Call, UnlockDescriptor, /*IsLock=*/false);
+void updateCriticalSectionOnUnlock(const EventDescriptor &UnlockDescriptor,
+ const CallEvent &Call, CheckerContext &C) {
+ const MemRegion *MutexRegion = getRegion(UnlockDescriptor.Trigger, Call);
if (!MutexRegion)
return;
@@ -194,37 +115,122 @@ void MutexModeling::handleUnlock(const MutexDescriptor &UnlockDescriptor,
NewList = Factory.add(*It, NewList);
}
- State = State->set<CritSections>(NewList);
- C.addTransition(State);
+ C.addTransition(State->set<CritSections>(NewList));
}
-std::optional<MutexDescriptor>
-MutexModeling::checkDescriptorMatch(const CallEvent &Call, CheckerContext &C,
- bool IsLock) const {
- std::optional<MutexDescriptor> Descriptor;
- const auto DescriptorIt =
- llvm::find_if(MutexDescriptors, [&Call, IsLock](auto &&Descriptor) {
- return std::visit(
- [&Call, IsLock](auto &&DescriptorImpl) {
- return DescriptorImpl.matches(Call, IsLock);
- },
- Descriptor);
- });
- if (DescriptorIt != MutexDescriptors.end())
- Descriptor.emplace(*DescriptorIt);
- return Descriptor;
+class MutexModeling : public Checker<check::PostCall> {
+ std::vector<EventDescriptor> handledEvents = getHandledEvents();
+
+public:
+ void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+
+private:
+ std::unique_ptr<BugType> BT_initlock = std::make_unique<BugType>(
+ this->getCheckerName(), "Init invalid lock", "Lock checker");
+ ProgramStateRef handleInit(const EventDescriptor &Event, const MemRegion *MTX,
+ const Expr *MTXExpr, const CallEvent &Call,
+ ProgramStateRef State, CheckerContext &C) const;
+ ProgramStateRef handleEvent(const EventDescriptor &Event,
+ const MemRegion *MTX, const Expr *MTXExpr,
+ const CallEvent &Call, ProgramStateRef State,
+ CheckerContext &C) const;
+};
+
+} // namespace
+
+ProgramStateRef
+MutexModeling::handleInit(const EventDescriptor &Event, const MemRegion *MTX,
+ const Expr *MTXExpr, const CallEvent &Call,
+ ProgramStateRef State, CheckerContext &C) const {
+ assert(MTX && "should only be called with a mutex region");
+ assert(MTXExpr && "should only be called with a valid mutex expression");
+
+ State = State->add<MutexEvents>(EventMarker{
+ Event.Kind, Event.Semantics, Call.getCalleeIdentifier(), MTXExpr, MTX});
+
+ const LockStateKind *LState = State->get<LockStates>(MTX);
+ if (!LState || *LState == LockStateKind::Destroyed) {
+ return State->set<LockStates>(MTX, LockStateKind::Unlocked);
+ }
+
+ // We are here if three is an init event on a lock that is modelled, and it
+ // is not in destroyed state. The bugreporting should be done in the
+ // reporting checker and not it the modeling, but we still want to be
+ // efficient.
+ StringRef Message;
+ if (*LState == LockStateKind::Locked) {
+ Message = "This lock is still being held";
+ State =
+ State->set<LockStates>(MTX, LockStateKind::Error_DoubleInitWhileLocked);
+ } else {
+ Message = "This lock has already been initialized";
+ State = State->set<LockStates>(MTX, LockStateKind::Error_DoubleInit);
+ }
+
+ // TODO: put this part in the reporting checker
+
+ ExplodedNode *N = C.generateErrorNode();
+ if (!N)
+ return State;
+ auto Report =
+ std::make_unique<PathSensitiveBugReport>(BT_initlock.get(), Message, N);
+ Report->addRange(MTXExpr->getSourceRange());
+ C.emitReport(std::move(Report));
+
+ return State;
+}
+
+ProgramStateRef
+MutexModeling::handleEvent(const EventDescriptor &Event, const MemRegion *MTX,
+ const Expr *MTXExpr, const CallEvent &Call,
+ ProgramStateRef State, CheckerContext &C) const {
+ assert(MTX && "should only be called with a mutex region");
+ assert(MTXExpr && "should only be called with a valid mutex expression");
+
+ switch (Event.Kind) {
+ case EventKind::Init:
+ return handleInit(Event, MTX, MTXExpr, Call, State, C);
+ break;
+ default:
+ llvm_unreachable("Unhandled event kind!");
+#if 0
+ case EventKind::Acquire:
+ handleAcquire(Event, Call, C);
+ break;
+ case EventKind::TryAcquire:
+ handleTryAcquire(Event, Call, C);
+ break;
+ case EventKind::Release:
+ handleRelease(Event, Call, C);
+ break;
+ case EventKind::Destroy:
+ handleDestroy(Event, Call, C);
+ break;
+#endif
+ }
}
void MutexModeling::checkPostCall(const CallEvent &Call,
CheckerContext &C) const {
- 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);
+ // FIXME: Try to handle cases when the implementation was inlined rather than
+ // just giving up.
+ if (C.wasInlined)
+ return;
+
+ ProgramStateRef State = C.getState();
+ for (auto &&Event : handledEvents) {
+ if (matches(Event.Trigger, Call)) {
+ const MemRegion *MTX = getRegion(Event.Trigger, Call);
+ if (!MTX)
+ continue;
+ const Expr *MTXExpr = Call.getOriginExpr();
+ if (!MTXExpr)
+ continue;
+ State = doResolvePossiblyDestroyedMutex(State, MTX);
+ State = handleEvent(Event, MTX, MTXExpr, Call, State, C);
+ C.addTransition(State);
+ }
}
-}
} // namespace
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingAPI.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingAPI.h
index bdea2aba6470b2..58d16045366973 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingAPI.h
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingAPI.h
@@ -51,9 +51,10 @@ inline const NoteTag *CreateMutexCritSectionNote(CritSectionMarker M,
const auto CritSectionBegins =
BR.getErrorNode()->getState()->get<CritSections>();
llvm::SmallVector<CritSectionMarker, 4> LocksForMutex;
- llvm::copy_if(
- CritSectionBegins, std::back_inserter(LocksForMutex),
- [M](const auto &Marker) { return Marker.MutexRegion == M.MutexRegion; });
+ llvm::copy_if(CritSectionBegins, std::back_inserter(LocksForMutex),
+ [M](const auto &Marker) {
+ return Marker.MutexRegion == M.MutexRegion;
+ });
if (LocksForMutex.empty())
return;
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDefs.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDefs.h
new file mode 100644
index 00000000000000..936fd5fc87ea34
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDefs.h
@@ -0,0 +1,212 @@
+//===--- MutexModelingDefs.h - 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 the default set of events that are handled by the mutex modeling.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELINGMUTEXMODELINGDEFS_H
+#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELINGMUTEXMODELINGDEFS_H
+
+#include "MutexModelingDomain.h"
+#include "MutexRegionExtractor.h"
+
+#include <vector>
+
+namespace clang::ento::mutex_modeling {
+
+inline auto getHandledEvents() -> std::vector<EventDescriptor> {
+ return std::vector<EventDescriptor>{
+ // - Pthread
+ EventDescriptor{FirstArgMutexExtractor{CallDescription{
+ CDM::CLibrary, {"pthread_mutex_init"}, 2}}},
+ EventKind::Init, SemanticsKind::NotApplicable},
+ // TODO: pthread_rwlock_init(2 arguments).
+ // TODO: lck_mtx_init(3 arguments).
+ // TODO: lck_mtx_alloc_init(2 arguments) => returns the mutex.
+ // TODO: lck_rw_init(3 arguments).
+ // TODO: lck_rw_alloc_init(2 arguments) => returns the mutex.
+
+ // - Fuchsia
+ EventDescriptor{CallDescription{CDM::CLibrary, {"spin_lock_init"}, 1},
+ Event::Init, Syntax::FirstArg,
+ LockingSemantics::NotApplicable},
+
+ // - C11
+ EventDescriptor{CallDescription{CDM::CLibrary, {"mtx_init"}, 2},
+ Event::Init, Syntax::FirstArg,
+ LockingSemantics::NotApplicable},
+
+ // Acquire kind
+ // - Pthread
+ //
+ EventDescriptor{
+ CallDescription{CDM::CLibrary, {"pthread_mutex_lock"}, 1},
+ Event::Acquire, Syntax::FirstArg,
+ LockingSemantics::PthreadSemantics},
+ EventDescriptor{
+ CallDescription{CDM::CLibrary, {"pthread_rwlock_rdlock"}, 1},
+ Event::Acquire, Syntax::FirstArg,
+ LockingSemantics::PthreadSemantics},
+ EventDescriptor{
+ CallDescription{CDM::CLibrary, {"pthread_rwlock_wrlock"}, 1},
+ Event::Acquire, Syntax::FirstArg,
+ LockingSemantics::PthreadSemantics},
+ EventDescriptor{CallDescription{CDM::CLibrary, {"lck_mtx_lock"}, 1},
+ Event::Acquire, Syntax::FirstArg,
+ LockingSemantics::XNUSemantics},
+ EventDescriptor{
+ CallDescription{CDM::CLibrary, {"lck_rw_lock_exclusive"}, 1},
+ Event::Acquire, Syntax::FirstArg, LockingSemantics::XNUSemantics},
+ EventDescriptor{
+ CallDescription{CDM::CLibrary, {"lck_rw_lock_shared"}, 1},
+ Event::Acquire, Syntax::FirstArg, LockingSemantics::XNUSemantics},
+
+ // - Fuchsia
+ EventDescriptor{CallDescription{CDM::CLibrary, {"spin_lock"}, 1},
+ Event::Acquire, Syntax::FirstArg,
+ LockingSemantics::PthreadSemantics},
+ EventDescriptor{CallDescription{CDM::CLibrary, {"spin_lock_save"}, 3},
+ Event::Acquire, Syntax::FirstArg,
+ LockingSemantics::PthreadSemantics},
+ EventDescriptor{CallDescription{CDM::CLibrary, {"sync_mutex_lock"}, 1},
+ Event::Acquire, Syntax::FirstArg,
+ LockingSemantics::PthreadSemantics},
+ EventDescriptor{
+ CallDescription{CDM::CLibrary, {"sync_mutex_lock_with_waiter"}, 1},
+ Event::Acquire, Syntax::FirstArg,
+ LockingSemantics::PthreadSemantics},
+
+ // - C11
+ EventDescriptor{CallDescription{CDM::CLibrary, {"mtx_lock"}, 1},
+ Event::Acquire, Syntax::FirstArg,
+ LockingSemantics::PthreadSemantics},
+
+ // - std
+ EventDescriptor{
+ CallDescription{CDM::CXXMethod, {"std", "mutex", "lock"}, 0},
+ Event::Acquire, Syntax::Member,
+ LockingSemantics::PthreadSemantics},
+ EventDescriptor{
+ CallDescription{CDM::CXXMethod, {"std", "lock_guard"}, 1},
+ Event::Acquire, Syntax::RAII, LockingSemantics::PthreadSemantics},
+ EventDescriptor{
+ CallDescription{CDM::CXXMethod, {"std", "unique_lock"}, 1},
+ Event::Acquire, Syntax::RAII, LockingSemantics::PthreadSemantics},
+
+ // TryAcquire kind
+ // - Pthread
+ EventDescriptor{
+ CallDescription{CDM::CLibrary, {"pthread_mutex_trylock"}, 1},
+ Event::TryAcquire, Syntax::FirstArg,
+ LockingSemantics::PthreadSemantics},
+ EventDescriptor{
+ CallDescription{CDM::CLibrary, {"pthread_rwlock_tryrdlock"}, 1},
+ Event::TryAcquire, Syntax::FirstArg,
+ LockingSemantics::PthreadSemantics},
+ EventDescriptor{
+ CallDescription{CDM::CLibrary, {"pthread_rwlock_trywrlock"}, 1},
+ Event::TryAcquire, Syntax::FirstArg,
+ LockingSemantics::PthreadSemantics},
+ EventDescriptor{
+ CallDescription{CDM::CLibrary, {"lck_mtx_try_lock"}, 1},
+ Event::TryAcquire, Syntax::FirstArg,
+ LockingSemantics::XNUSemantics},
+ EventDescriptor{
+ CallDescription{CDM::CLibrary, {"lck_rw_try_lock_exclusive"}, 1},
+ Event::TryAcquire, Syntax::FirstArg,
+ LockingSemantics::XNUSemantics},
+ EventDescriptor{
+ CallDescription{CDM::CLibrary, {"lck_rw_try_lock_shared"}, 1},
+ Event::TryAcquire, Syntax::FirstArg,
+ LockingSemantics::XNUSemantics},
+
+ // - Fuchsia
+ EventDescriptor{CallDescription{CDM::CLibrary, {"spin_trylock"}, 1},
+ Event::TryAcquire, Syntax::FirstArg,
+ LockingSemantics::PthreadSemantics},
+ EventDescriptor{
+ CallDescription{CDM::CLibrary, {"sync_mutex_trylock"}, 1},
+ Event::TryAcquire, Syntax::FirstArg,
+ LockingSemantics::PthreadSemantics},
+ EventDescriptor{
+ CallDescription{CDM::CLibrary, {"sync_mutex_timedlock"}, 2},
+ Event::TryAcquire, Syntax::FirstArg,
+ LockingSemantics::PthreadSemantics},
+
+ // - C11
+ EventDescriptor{CallDescription{CDM::CLibrary, {"mtx_trylock"}, 1},
+ Event::TryAcquire, Syntax::FirstArg,
+ LockingSemantics::PthreadSemantics},
+ EventDescriptor{CallDescription{CDM::CLibrary, {"mtx_timedlock"}, 2},
+ Event::TryAcquire, Syntax::FirstArg,
+ LockingSemantics::PthreadSemantics},
+
+ // Release kind
+ // - Pthread
+ EventDescriptor{
+ CallDescription{CDM::CLibrary, {"pthread_mutex_unlock"}, 1},
+ Event::Release, Syntax::FirstArg, LockingSemantics::NotApplicable},
+ EventDescriptor{
+ CallDescription{CDM::CLibrary, {"pthread_rwlock_unlock"}, 1},
+ Event::Release, Syntax::FirstArg, LockingSemantics::NotApplicable},
+ EventDescriptor{CallDescription{CDM::CLibrary, {"lck_mtx_unlock"}, 1},
+ Event::Release, Syntax::FirstArg,
+ LockingSemantics::NotApplicable},
+ EventDescriptor{
+ CallDescription{CDM::CLibrary, {"lck_rw_unlock_exclusive"}, 1},
+ Event::Release, Syntax::FirstArg, LockingSemantics::NotApplicable},
+ EventDescriptor{
+ CallDescription{CDM::CLibrary, {"lck_rw_unlock_shared"}, 1},
+ Event::Release, Syntax::FirstArg, LockingSemantics::NotApplicable},
+ EventDescriptor{CallDescription{CDM::CLibrary, {"lck_rw_done"}, 1},
+ Event::Release, Syntax::FirstArg,
+ LockingSemantics::NotApplicable},
+
+ // - Fuchsia
+ EventDescriptor{CallDescription{CDM::CLibrary, {"spin_unlock"}, 1},
+ Event::Release, Syntax::FirstArg,
+ LockingSemantics::NotApplicable},
+ EventDescriptor{
+ CallDescription{CDM::CLibrary, {"spin_unlock_restore"}, 3},
+ Event::Release, Syntax::FirstArg, LockingSemantics::NotApplicable},
+ EventDescriptor{
+ CallDescription{CDM::CLibrary, {"sync_mutex_unlock"}, 1},
+ Event::Release, Syntax::FirstArg, LockingSemantics::NotApplicable},
+
+ // - C11
+ EventDescriptor{CallDescription{CDM::CLibrary, {"mtx_unlock"}, 1},
+ Event::Release, Syntax::FirstArg,
+ LockingSemantics::NotApplicable},
+
+ // - std
+ EventDescriptor{
+ CallDescription{CDM::CXXMethod, {"std", "mutex", "unlock"}, 0},
+ Event::Release, Syntax::Member, LockingSemantics::NotApplicable},
+
+ // Destroy kind
+ // - Pthread
+ EventDescriptor{{CDM::CLibrary, {"pthread_mutex_destroy"}, 1},
+ Event::Destroy,
+ Syntax::FirstArg,
+ LockingSemantics::NotApplicable},
+ EventDescriptor{CallDescription{CDM::CLibrary, {"lck_mtx_destroy"}, 2},
+ Event::Destroy, Syntax::FirstArg,
+ LockingSemantics::NotApplicable},
+ // TODO: pthread_rwlock_destroy(1 argument).
+ // TODO: lck_rw_destroy(2 arguments).
+
+ // - C11
+ EventDescriptor{CallDescription{CDM::CLibrary, {"mtx_destroy"}, 1},
+ Event::Destroy, Syntax::FirstArg,
+ LockingSemantics::NotApplicable}
+};
+
+} // namespace clang::ento::mutex_modeling
+
+#endif
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h
index 609d4ec974a2c7..a0dfb8dc73b62a 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h
@@ -13,6 +13,9 @@
#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELINGDOMAIN_H
#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELINGDOMAIN_H
+#include "MutexRegionExtractor.h"
+#include "clang/Basic/IdentifierTable.h"
+
namespace clang {
class Expr;
@@ -27,46 +30,52 @@ enum class EventKind { Init, Acquire, TryAcquire, Release, Destroy };
enum class SyntaxKind { FirstArg, Member, RAII };
-enum class LockingSemanticsKind { PthreadSemantics, XNUSemantics };
+enum class SemanticsKind { NotApplicable = 0, PthreadSemantics, XNUSemantics };
enum class LockStateKind {
Unlocked,
Locked,
Destroyed,
UntouchedAndPossiblyDestroyed,
- UnlockedAndPossiblyDestroyed
+ UnlockedAndPossiblyDestroyed,
+ Error_DoubleInit,
+ Error_DoubleInitWhileLocked,
};
struct EventDescriptor {
+ MutexRegionExtractor Trigger;
EventKind Kind{};
- SyntaxKind Syntax{};
- LockingSemanticsKind Semantics{};
+ SemanticsKind Semantics{};
+ // TODO: Modernize to spaceship when C++20 is available.
[[nodiscard]] constexpr bool
- operator==(const EventDescriptor &Other) const noexcept {
- return Kind == Other.Kind && Syntax == Other.Syntax &&
- Semantics == Other.Semantics;
+ operator!=(const EventDescriptor &Other) const noexcept {
+ return !(Trigger == Other.Trigger) || Kind != Other.Kind ||
+ Semantics != Other.Semantics;
}
[[nodiscard]] constexpr bool
- operator!=(const EventDescriptor &Other) const noexcept {
- return !(*this == Other);
+ operator==(const EventDescriptor &Other) const noexcept {
+ return !(*this != Other);
}
};
struct EventMarker {
- EventDescriptor Event{};
- LockStateKind LockState{};
+ EventKind Kind{};
+ SemanticsKind Semantics{};
+ const IdentifierInfo *Event;
const clang::Expr *EventExpr{};
const clang::ento::MemRegion *MutexRegion{};
+ // TODO: Modernize to spaceship when C++20 is available.
[[nodiscard]] constexpr bool
- operator==(const EventMarker &Other) const noexcept {
- return Event == Other.Event && LockState == Other.LockState &&
- EventExpr == Other.EventExpr && MutexRegion == Other.MutexRegion;
+ operator!=(const EventMarker &Other) const noexcept {
+ return Event != Other.Event || Kind != Other.Kind ||
+ Semantics != Other.Semantics || LockState != Other.LockState ||
+ EventExpr != Other.EventExpr || MutexRegion != Other.MutexRegion;
}
[[nodiscard]] constexpr bool
- operator!=(const EventMarker &Other) const noexcept {
- return !(*this == Other);
+ operator==(const EventMarker &Other) const noexcept {
+ return !(*this != Other);
}
};
@@ -74,13 +83,18 @@ struct CritSectionMarker {
const clang::Expr *BeginExpr;
const clang::ento::MemRegion *MutexRegion;
+ explicit CritSectionMarker(const clang::Expr *BeginExpr,
+ const clang::ento::MemRegion *MutexRegion)
+ : BeginExpr(BeginExpr), MutexRegion(MutexRegion) {}
+
+ // TODO: Modernize to spaceship when C++20 is available.
[[nodiscard]] constexpr bool
- operator==(const CritSectionMarker &Other) const noexcept {
- return BeginExpr == Other.BeginExpr && MutexRegion == Other.MutexRegion;
+ operator!=(const CritSectionMarker &Other) const noexcept {
+ return BeginExpr != Other.BeginExpr || MutexRegion != Other.MutexRegion;
}
[[nodiscard]] constexpr bool
- operator!=(const CritSectionMarker &Other) const noexcept {
- return !(*this == Other);
+ operator==(const CritSectionMarker &Other) const noexcept {
+ return !(*this != Other);
}
};
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingGDM.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingGDM.h
index 3cd83c3c37123d..566c8d7fdc730c 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingGDM.h
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingGDM.h
@@ -24,12 +24,21 @@ namespace clang {
namespace ento {
namespace mutex_modeling {
+// Raw data of the mutex events.
class MutexEvents {};
using MutexEventsTy = llvm::ImmutableList<EventMarker>;
+// Aggregated data structures for tracking critical sections.
class CritSections {};
using CritSectionsTy = llvm::ImmutableList<CritSectionMarker>;
+// Aggregated data structures for tracking mutex states.
+class LockStates {};
+using LockStatesTy = llvm::ImmutableMap<const MemRegion *, LockStateKind>;
+
+class DestroyedRetVals {};
+using DestroyedRetValsTy = llvm::ImmutableMap<const MemRegion *, SymbolRef>;
+
} // namespace mutex_modeling
} // namespace ento
} // namespace clang
@@ -38,10 +47,10 @@ using CritSectionsTy = llvm::ImmutableList<CritSectionMarker>;
namespace llvm {
template <> struct FoldingSetTrait<clang::ento::mutex_modeling::EventMarker> {
static void Profile(const clang::ento::mutex_modeling::EventMarker &EM,
- llvm::FoldingSetNodeID &ID) {
- ID.Add(EM.Event.Kind);
- ID.Add(EM.Event.Syntax);
- ID.Add(EM.Event.Semantics);
+ llvm::FoldingSetNodeID &ID) {
+ ID.Add(EM.Event);
+ ID.Add(EM.Kind);
+ ID.Add(EM.Semantics);
ID.Add(EM.LockState);
ID.Add(EM.EventExpr);
ID.Add(EM.MutexRegion);
@@ -51,14 +60,14 @@ template <> struct FoldingSetTrait<clang::ento::mutex_modeling::EventMarker> {
template <>
struct FoldingSetTrait<clang::ento::mutex_modeling::CritSectionMarker> {
static void Profile(const clang::ento::mutex_modeling::CritSectionMarker &CSM,
- llvm::FoldingSetNodeID &ID) {
+ llvm::FoldingSetNodeID &ID) {
ID.Add(CSM.BeginExpr);
ID.Add(CSM.MutexRegion);
}
};
} // namespace llvm
-// Iterator traits for ImmutableList data structure
+// Iterator traits for ImmutableList and ImmutableMap data structures
// that enable the use of STL algorithms.
namespace std {
// TODO: Move these to llvm::ImmutableList when overhauling immutable data
@@ -81,10 +90,36 @@ struct iterator_traits<typename llvm::ImmutableList<
using reference = clang::ento::mutex_modeling::CritSectionMarker &;
using pointer = clang::ento::mutex_modeling::CritSectionMarker *;
};
+template <>
+struct iterator_traits<typename llvm::ImmutableMap<
+ const clang::ento::MemRegion *,
+ clang::ento::mutex_modeling::LockStateKind>::iterator> {
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = std::pair<const clang::ento::MemRegion *,
+ clang::ento::mutex_modeling::LockStateKind>;
+ using difference_type = std::ptrdiff_t;
+ using reference = std::pair<const clang::ento::MemRegion *,
+ clang::ento::mutex_modeling::LockStateKind> &;
+ using pointer = std::pair<const clang::ento::MemRegion *,
+ clang::ento::mutex_modeling::LockStateKind> *;
+};
+template <>
+struct iterator_traits<typename llvm::ImmutableMap<
+ const clang::ento::MemRegion *, clang::ento::SymbolRef>::iterator> {
+ using iterator_category = std::forward_iterator_tag;
+ using value_type =
+ std::pair<const clang::ento::MemRegion *, clang::ento::SymbolRef>;
+ using difference_type = std::ptrdiff_t;
+ using reference =
+ std::pair<const clang::ento::MemRegion *, clang::ento::SymbolRef> &;
+ using pointer =
+ std::pair<const clang::ento::MemRegion *, clang::ento::SymbolRef> *;
+};
} // namespace std
// NOTE: ProgramState macros are not used here, because the visibility of these
// GDM entries must span multiple translation units (multiple checkers).
+// TODO: check if this is still true after finishing the implementation.
namespace clang {
namespace ento {
template <>
@@ -105,6 +140,24 @@ struct ProgramStateTrait<clang::ento::mutex_modeling::CritSections>
return &Index;
}
};
+template <>
+struct ProgramStateTrait<clang::ento::mutex_modeling::LockStates>
+ : public ProgramStatePartialTrait<
+ clang::ento::mutex_modeling::LockStatesTy> {
+ static void *GDMIndex() {
+ static int Index;
+ return &Index;
+ }
+};
+template <>
+struct ProgramStateTrait<clang::ento::mutex_modeling::DestroyedRetVals>
+ : public ProgramStatePartialTrait<
+ clang::ento::mutex_modeling::DestroyedRetValsTy> {
+ static void *GDMIndex() {
+ static int Index;
+ return &Index;
+ }
+};
} // namespace ento
} // namespace clang
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexRegionExtractor.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexRegionExtractor.h
new file mode 100644
index 00000000000000..88d7b79daad1b4
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexRegionExtractor.h
@@ -0,0 +1,131 @@
+//===--- MutexRegionExtractor.h - 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.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELING_MUTEXREGIONEXTRACTOR_H
+#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELING_MUTEXREGIONEXTRACTOR_H
+
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
+#include <variant>
+
+namespace clang::ento::mutex_modeling {
+
+class FirstArgMutexExtractor : public clang::ento::CallDescription {
+public:
+ template <typename T>
+ FirstArgMutexExtractor(T &&CD) : CallDescription(std::forward<T>(CD)) {}
+
+ [[nodiscard]] const clang::ento::MemRegion *
+ getRegion(const clang::ento::CallEvent &Call) const {
+ return Call.getArgSVal(0).getAsRegion();
+ }
+};
+
+class MemberMutexExtractor : public clang::ento::CallDescription {
+public:
+ template <typename T>
+ MemberMutexExtractor(T &&CD) : CallDescription(std::forward<T>(CD)) {}
+
+ [[nodiscard]] const clang::ento::MemRegion *
+ getRegion(const clang::ento::CallEvent &Call) const {
+ return llvm::cast<clang::ento::CXXMemberCall>(Call)
+ .getCXXThisVal()
+ .getAsRegion();
+ }
+};
+
+template <bool IsLock> class RAIIMutexExtractor {
+ mutable const clang::IdentifierInfo *Guard{};
+ mutable bool IdentifierInfoInitialized{};
+ mutable llvm::SmallString<32> GuardName{};
+
+ void initIdentifierInfo(const clang::ento::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 clang::ento::CallEvent &Call) const {
+ const T *C = llvm::dyn_cast<T>(&Call);
+ if (!C)
+ return false;
+ const clang::IdentifierInfo *II =
+ llvm::cast<clang::CXXRecordDecl>(C->getDecl()->getParent())
+ ->getIdentifier();
+ return II == Guard;
+ }
+
+public:
+ RAIIMutexExtractor(llvm::StringRef GuardName) : GuardName(GuardName) {}
+ [[nodiscard]] bool matches(const clang::ento::CallEvent &Call) const {
+ initIdentifierInfo(Call);
+ if constexpr (IsLock) {
+ return matchesImpl<clang::ento::CXXConstructorCall>(Call);
+ } else {
+ return matchesImpl<clang::ento::CXXDestructorCall>(Call);
+ }
+ }
+ [[nodiscard]] const clang::ento::MemRegion *
+ getRegion(const clang::ento::CallEvent &Call) const {
+ const clang::ento::MemRegion *MutexRegion = nullptr;
+ if constexpr (IsLock) {
+ if (std::optional<clang::ento::SVal> Object =
+ Call.getReturnValueUnderConstruction()) {
+ MutexRegion = Object->getAsRegion();
+ }
+ } else {
+ MutexRegion =
+ llvm::cast<CXXDestructorCall>(Call).getCXXThisVal().getAsRegion();
+ }
+ return MutexRegion;
+ }
+};
+
+using RAIILockExtractor = RAIIMutexExtractor<true>;
+using RAIIReleaseExtractor = RAIIMutexExtractor<false>;
+
+using MutexRegionExtractor =
+ std::variant<FirstArgMutexExtractor, MemberMutexExtractor,
+ RAIILockExtractor, RAIIReleaseExtractor>;
+
+inline const clang::ento::MemRegion *
+getRegion(const MutexRegionExtractor &Extractor,
+ const clang::ento::CallEvent &Call) {
+ return std::visit(
+ [&Call](auto &&Descriptor) { return Descriptor.getRegion(Call); },
+ Extractor);
+}
+
+inline bool operator==(const MutexRegionExtractor &LHS,
+ const MutexRegionExtractor &RHS) {
+ return std::visit([](auto &&LHS, auto &&RHS) { return LHS == RHS; }, LHS,
+ RHS);
+}
+
+inline bool matches(const MutexRegionExtractor &Extractor,
+ const CallEvent &Call) {
+ return std::visit(
+ [](auto &&Extractor, const CallEvent &Call) {
+ return Extractor.matches(Call);
+ },
+ Extractor, Call);
+}
+
+} // namespace clang::ento::mutex_modeling
+
+#endif
>From fb737ccd4883f471e38bc5eaa3197b2903c30f9a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Thu, 25 Jul 2024 22:12:18 +0200
Subject: [PATCH 09/30] create multiple headers
---
.../clang/StaticAnalyzer/Checkers/Checkers.td | 2 +-
.../BlockInCriticalSectionChecker.cpp | 2 +-
.../StaticAnalyzer/Checkers/MutexModeling.cpp | 98 ++---
.../Checkers/MutexModeling/MutexModelingAPI.h | 155 ++++++-
.../MutexModeling/MutexModelingDefs.h | 71 ++--
.../MutexModeling/MutexModelingDomain.h | 46 ++-
.../Checkers/MutexModeling/MutexModelingGDM.h | 4 +-
.../MutexModeling/MutexRegionExtractor.h | 62 +--
.../Checkers/PthreadLockChecker.cpp | 386 +++++-------------
9 files changed, 402 insertions(+), 424 deletions(-)
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index d29b40833f0962..d93b8b0871677b 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -622,7 +622,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 91c3d154c3bbc2..12b61a36cf934c 100644
--- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
@@ -53,7 +53,7 @@ class BlockInCriticalSectionChecker : public Checker<check::PostCall> {
} // end anonymous namespace
BlockInCriticalSectionChecker::BlockInCriticalSectionChecker() {
- RegisterCheckerForMutexModeling(&BlockInCritSectionBugType);
+ RegisterBugTypeForMutexModeling(&BlockInCritSectionBugType);
}
bool BlockInCriticalSectionChecker::isBlockingInCritSection(
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
index badb6ed64fd312..0c8166ac61c6a6 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
@@ -119,77 +119,61 @@ void updateCriticalSectionOnUnlock(const EventDescriptor &UnlockDescriptor,
}
class MutexModeling : public Checker<check::PostCall> {
- std::vector<EventDescriptor> handledEvents = getHandledEvents();
-
public:
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
private:
- std::unique_ptr<BugType> BT_initlock = std::make_unique<BugType>(
- this->getCheckerName(), "Init invalid lock", "Lock checker");
+ mutable std::unique_ptr<BugType> BT_initlock;
ProgramStateRef handleInit(const EventDescriptor &Event, const MemRegion *MTX,
- const Expr *MTXExpr, const CallEvent &Call,
- ProgramStateRef State, CheckerContext &C) const;
+ const CallEvent &Call, ProgramStateRef State,
+ CheckerContext &C) const;
ProgramStateRef handleEvent(const EventDescriptor &Event,
- const MemRegion *MTX, const Expr *MTXExpr,
- const CallEvent &Call, ProgramStateRef State,
- CheckerContext &C) const;
+ const MemRegion *MTX, const CallEvent &Call,
+ ProgramStateRef State, CheckerContext &C) const;
};
} // namespace
-ProgramStateRef
-MutexModeling::handleInit(const EventDescriptor &Event, const MemRegion *MTX,
- const Expr *MTXExpr, const CallEvent &Call,
- ProgramStateRef State, CheckerContext &C) const {
+ProgramStateRef MutexModeling::handleInit(const EventDescriptor &Event,
+ const MemRegion *MTX,
+ const CallEvent &Call,
+ ProgramStateRef State,
+ CheckerContext &C) const {
assert(MTX && "should only be called with a mutex region");
- assert(MTXExpr && "should only be called with a valid mutex expression");
- State = State->add<MutexEvents>(EventMarker{
- Event.Kind, Event.Semantics, Call.getCalleeIdentifier(), MTXExpr, MTX});
+ State = State->add<MutexEvents>(
+ EventMarker{Event.Kind, Event.Semantics, Event.Library,
+ Call.getCalleeIdentifier(), Call.getOriginExpr(), MTX});
const LockStateKind *LState = State->get<LockStates>(MTX);
- if (!LState || *LState == LockStateKind::Destroyed) {
+
+ if (!LState)
return State->set<LockStates>(MTX, LockStateKind::Unlocked);
- }
- // We are here if three is an init event on a lock that is modelled, and it
- // is not in destroyed state. The bugreporting should be done in the
- // reporting checker and not it the modeling, but we still want to be
- // efficient.
- StringRef Message;
- if (*LState == LockStateKind::Locked) {
- Message = "This lock is still being held";
- State =
- State->set<LockStates>(MTX, LockStateKind::Error_DoubleInitWhileLocked);
- } else {
- Message = "This lock has already been initialized";
- State = State->set<LockStates>(MTX, LockStateKind::Error_DoubleInit);
+ switch (*LState) {
+ case (LockStateKind::Destroyed): {
+ return State->set<LockStates>(MTX, LockStateKind::Unlocked);
+ }
+ case (LockStateKind::Locked): {
+ return State->set<LockStates>(MTX,
+ LockStateKind::Error_DoubleInitWhileLocked);
+ }
+ default: {
+ return State->set<LockStates>(MTX, LockStateKind::Error_DoubleInit);
+ }
}
-
- // TODO: put this part in the reporting checker
-
- ExplodedNode *N = C.generateErrorNode();
- if (!N)
- return State;
- auto Report =
- std::make_unique<PathSensitiveBugReport>(BT_initlock.get(), Message, N);
- Report->addRange(MTXExpr->getSourceRange());
- C.emitReport(std::move(Report));
-
- return State;
}
-ProgramStateRef
-MutexModeling::handleEvent(const EventDescriptor &Event, const MemRegion *MTX,
- const Expr *MTXExpr, const CallEvent &Call,
- ProgramStateRef State, CheckerContext &C) const {
+ProgramStateRef MutexModeling::handleEvent(const EventDescriptor &Event,
+ const MemRegion *MTX,
+ const CallEvent &Call,
+ ProgramStateRef State,
+ CheckerContext &C) const {
assert(MTX && "should only be called with a mutex region");
- assert(MTXExpr && "should only be called with a valid mutex expression");
switch (Event.Kind) {
case EventKind::Init:
- return handleInit(Event, MTX, MTXExpr, Call, State, C);
+ return handleInit(Event, MTX, Call, State, C);
break;
default:
llvm_unreachable("Unhandled event kind!");
@@ -212,33 +196,33 @@ MutexModeling::handleEvent(const EventDescriptor &Event, const MemRegion *MTX,
void MutexModeling::checkPostCall(const CallEvent &Call,
CheckerContext &C) const {
- // FIXME: Try to handle cases when the implementation was inlined rather than
- // just giving up.
+ // FIXME: Try to handle cases when the implementation was inlined rather
+ // than just giving up.
+
if (C.wasInlined)
return;
ProgramStateRef State = C.getState();
- for (auto &&Event : handledEvents) {
+ for (auto &&Event : RegisteredEvents) {
if (matches(Event.Trigger, Call)) {
const MemRegion *MTX = getRegion(Event.Trigger, Call);
if (!MTX)
continue;
- const Expr *MTXExpr = Call.getOriginExpr();
- if (!MTXExpr)
- continue;
State = doResolvePossiblyDestroyedMutex(State, MTX);
- State = handleEvent(Event, MTX, MTXExpr, Call, State, C);
+ State = handleEvent(Event, MTX, Call, State, C);
C.addTransition(State);
}
}
-
-} // namespace
+}
namespace clang {
namespace ento {
// Checker registration
void registerMutexModeling(CheckerManager &mgr) {
mgr.registerChecker<MutexModeling>();
+ RegisterEvent(
+ EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_init"}, 2),
+ EventKind::Init, LibraryKind::Pthread});
}
bool shouldRegisterMutexModeling(const CheckerManager &) { return true; }
} // namespace ento
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingAPI.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingAPI.h
index 58d16045366973..2e53b71c932eb4 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingAPI.h
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingAPI.h
@@ -28,14 +28,44 @@ namespace ento {
class BugType;
namespace mutex_modeling {
-inline llvm::SmallSet<const BugType *, 8> RegisteredCheckers{};
+inline llvm::SmallSet<const BugType *, 0> RegisteredBugTypes{};
-inline void RegisterCheckerForMutexModeling(const BugType *BT) {
- RegisteredCheckers.insert(BT);
+inline void RegisterBugTypeForMutexModeling(const BugType *BT) {
+ RegisteredBugTypes.insert(BT);
}
-inline bool IsCheckerRegisteredForMutexModeling(const BugType *BT) {
- return RegisteredCheckers.contains(BT);
+inline bool IsBugTypeRegisteredForMutexModeling(const BugType *BT) {
+ return RegisteredBugTypes.contains(BT);
+}
+
+inline llvm::SmallVector<EventDescriptor, 0> RegisteredEvents{};
+
+inline auto RegisterEvent(EventDescriptor Event) {
+ RegisteredEvents.push_back(Event);
+}
+
+// Opinionated make functions for commonly used parameter values as default
+// arguments.
+inline auto
+MakeFirstArgExtractor(ArrayRef<StringRef> NameParts, int NumArgsRequired = 1,
+ CallDescription::Mode MatchAs = CDM::CLibrary) {
+ return FirstArgMutexExtractor{
+ CallDescription{MatchAs, NameParts, NumArgsRequired}};
+}
+
+inline auto MakeMemberExtractor(ArrayRef<StringRef> NameParts,
+ int NumArgsRequired = 1,
+ CallDescription::Mode MatchAs = CDM::CLibrary) {
+ return MemberMutexExtractor{
+ CallDescription{MatchAs, NameParts, NumArgsRequired}};
+}
+
+inline auto MakeRAIILockExtractor(StringRef GuardObjectName) {
+ return RAIILockExtractor{GuardObjectName};
+}
+
+inline auto MakeRAIIReleaseExtractor(StringRef GuardObjectName) {
+ return RAIIReleaseExtractor{GuardObjectName};
}
inline bool AreAnyCritsectionsActive(CheckerContext &C) {
@@ -46,7 +76,7 @@ inline const NoteTag *CreateMutexCritSectionNote(CritSectionMarker M,
CheckerContext &C) {
return C.getNoteTag([M](const PathSensitiveBugReport &BR,
llvm::raw_ostream &OS) {
- if (!IsCheckerRegisteredForMutexModeling(&BR.getBugType()))
+ if (!IsBugTypeRegisteredForMutexModeling(&BR.getBugType()))
return;
const auto CritSectionBegins =
BR.getErrorNode()->getState()->get<CritSections>();
@@ -87,6 +117,119 @@ inline const NoteTag *CreateMutexCritSectionNote(CritSectionMarker M,
});
}
+inline void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
+ const char *Sep) {
+
+ const MutexEventsTy &ME = State->get<MutexEvents>();
+ if (!ME.isEmpty()) {
+ Out << Sep << "Mutex event:" << NL;
+ for (auto I : ME) {
+ Out << Sep << "Kind: " << ": ";
+ switch (I.Kind) {
+ case (EventKind::Init):
+ Out << "Init";
+ break;
+ case (EventKind::Acquire):
+ Out << "Acquire";
+ break;
+ case (EventKind::TryAcquire):
+ Out << "TryAcquire";
+ break;
+ case (EventKind::Release):
+ Out << "Release";
+ break;
+ case (EventKind::Destroy):
+ Out << "Destroy";
+ break;
+ default:
+ llvm_unreachable("Unknown event kind");
+ }
+ Out << NL;
+ Out << Sep << "Semantics: ";
+ switch (I.Semantics) {
+ case (SemanticsKind::NotApplicable):
+ Out << "NotApplicable";
+ break;
+ case (SemanticsKind::PthreadSemantics):
+ Out << "PthreadSemantics";
+ break;
+ case (SemanticsKind::XNUSemantics):
+ Out << "XNUSemantics";
+ break;
+ default:
+ llvm_unreachable("Unknown semantics");
+ }
+ Out << NL;
+ Out << Sep << "Library: ";
+ switch (I.Library) {
+ case (LibraryKind::Pthread):
+ Out << "Pthread";
+ break;
+ case (LibraryKind::Fuchsia):
+ Out << "Fuchsia";
+ break;
+ case (LibraryKind::C11):
+ Out << "C11";
+ break;
+ default:
+ llvm_unreachable("Unknown library");
+ }
+ Out << NL;
+
+ // Omit MutexExpr and EventExpr
+
+ Out << Sep << "Mutex region: ";
+ I.MutexRegion->dumpToStream(Out);
+ Out << NL;
+ }
+
+ const LockStatesTy &LM = State->get<LockStates>();
+ if (!LM.isEmpty()) {
+ Out << Sep << "Mutex states:" << NL;
+ for (auto I : LM) {
+ I.first->dumpToStream(Out);
+ switch (I.second) {
+ case (LockStateKind::Locked):
+ Out << ": locked";
+ break;
+ case (LockStateKind::Unlocked):
+ Out << ": unlocked";
+ break;
+ case (LockStateKind::Destroyed):
+ Out << ": destroyed";
+ break;
+ case (LockStateKind::UntouchedAndPossiblyDestroyed):
+ Out << ": not tracked, possibly destroyed";
+ break;
+ case (LockStateKind::UnlockedAndPossiblyDestroyed):
+ Out << ": unlocked, possibly destroyed";
+ break;
+ case (LockStateKind::Error_DoubleInit):
+ Out << ": error: double init";
+ break;
+ case (LockStateKind::Error_DoubleInitWhileLocked):
+ Out << ": error: double init while locked";
+ break;
+ default:
+ llvm_unreachable("Unknown lock state");
+ }
+ Out << NL;
+ }
+ }
+
+ const DestroyedRetValsTy &DRV = State->get<DestroyedRetVals>();
+ if (!DRV.isEmpty()) {
+ Out << Sep << "Mutexes in unresolved possibly destroyed state:" << NL;
+ for (auto I : DRV) {
+ I.first->dumpToStream(Out);
+ Out << ": ";
+ I.second->dumpToStream(Out);
+ Out << NL;
+ }
+ }
+ }
+}
+
} // namespace mutex_modeling
} // namespace ento
} // namespace clang
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDefs.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDefs.h
index 936fd5fc87ea34..d0b2f5957bdcfe 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDefs.h
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDefs.h
@@ -20,39 +20,42 @@
namespace clang::ento::mutex_modeling {
-inline auto getHandledEvents() -> std::vector<EventDescriptor> {
- return std::vector<EventDescriptor>{
- // - Pthread
- EventDescriptor{FirstArgMutexExtractor{CallDescription{
- CDM::CLibrary, {"pthread_mutex_init"}, 2}}},
- EventKind::Init, SemanticsKind::NotApplicable},
- // TODO: pthread_rwlock_init(2 arguments).
- // TODO: lck_mtx_init(3 arguments).
- // TODO: lck_mtx_alloc_init(2 arguments) => returns the mutex.
- // TODO: lck_rw_init(3 arguments).
- // TODO: lck_rw_alloc_init(2 arguments) => returns the mutex.
-
- // - Fuchsia
- EventDescriptor{CallDescription{CDM::CLibrary, {"spin_lock_init"}, 1},
- Event::Init, Syntax::FirstArg,
- LockingSemantics::NotApplicable},
-
- // - C11
- EventDescriptor{CallDescription{CDM::CLibrary, {"mtx_init"}, 2},
- Event::Init, Syntax::FirstArg,
- LockingSemantics::NotApplicable},
-
- // Acquire kind
- // - Pthread
- //
- EventDescriptor{
- CallDescription{CDM::CLibrary, {"pthread_mutex_lock"}, 1},
- Event::Acquire, Syntax::FirstArg,
- LockingSemantics::PthreadSemantics},
- EventDescriptor{
- CallDescription{CDM::CLibrary, {"pthread_rwlock_rdlock"}, 1},
- Event::Acquire, Syntax::FirstArg,
- LockingSemantics::PthreadSemantics},
+static auto getHandledEvents(){return std::vector<EventDescriptor> {
+ // - Pthread
+ EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_init"}), EventKind::Init,
+ LibraryKind::Pthread},
+#if 0
+ // TODO: pthread_rwlock_init(2 arguments).
+ // TODO: lck_mtx_init(3 arguments).
+ // TODO: lck_mtx_alloc_init(2 arguments) => returns the mutex.
+ // TODO: lck_rw_init(3 arguments).
+ // TODO: lck_rw_alloc_init(2 arguments) => returns the mutex.
+
+ // - Fuchsia
+ EventDescriptor{FirstArgMutexExtractor{CallDescription{
+ CDM::CLibrary, {"spin_lock_init"}, 1}},
+ EventKind::Init, LibraryKind::Fuchsia},
+
+ // - C11
+ EventDescriptor{FirstArgMutexExtractor{CallDescription{
+ CDM::CLibrary, {"mtx_init"}, 2}},
+ EventKind::Init, LibraryKind::C11},
+
+ // Acquire kind
+ // - Pthread
+ //
+ EventDescriptor{FirstArgMutexExtractor{CallDescription{
+ CDM::CLibrary, {"pthread_mutex_lock"}, 1}},
+ Event::Acquire, LibraryKind::Pthread,
+ SemanticsKind::PthreadSemantics},
+ EventDescriptor{FirstArgMutexExtractor{
+ CallDescription{CDM::CLibrary, {"pthread_rwlock_rdlock"}, 1},
+ Event::Acquire, LibraryKind::Pthread,
+ SemanticsKind::PthreadSemantics}},
+ EventDescriptor{FirstArgMutexExtractor{CallDescription{
+ CDM::CLibrary, {"pthread_rwlock_wrlock"}, 1}},
+ Event::Acquire, LibraryKind::Pthread,
+ SemanticsKind::PthreadSemantics}},
EventDescriptor{
CallDescription{CDM::CLibrary, {"pthread_rwlock_wrlock"}, 1},
Event::Acquire, Syntax::FirstArg,
@@ -205,7 +208,9 @@ inline auto getHandledEvents() -> std::vector<EventDescriptor> {
EventDescriptor{CallDescription{CDM::CLibrary, {"mtx_destroy"}, 1},
Event::Destroy, Syntax::FirstArg,
LockingSemantics::NotApplicable}
+#endif
};
+} // namespace clang::ento::mutex_modeling
} // namespace clang::ento::mutex_modeling
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h
index a0dfb8dc73b62a..56e50bf4273efc 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h
@@ -14,21 +14,28 @@
#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MUTEXMODELINGDOMAIN_H
#include "MutexRegionExtractor.h"
-#include "clang/Basic/IdentifierTable.h"
+// Forwad declarations.
namespace clang {
-
class Expr;
-
+class IdentifierInfo;
namespace ento {
-
class MemRegion;
+} // namespace ento
+} // namespace clang
-namespace mutex_modeling {
+namespace clang::ento::mutex_modeling {
enum class EventKind { Init, Acquire, TryAcquire, Release, Destroy };
-enum class SyntaxKind { FirstArg, Member, RAII };
+// TODO: Ideally the modeling should not know about which checkers consume the
+// modeling information. This enum is here to make a correspondence between the
+// checked mutex event the library that event came from. In order to keep the
+// external API of multiple distinct checkers (PthreadLockChecker,
+// FuchsiaLockChecker and C11LockChecker), this mapping is done here, but if
+// more consumers of this modeling arise, adding all of them here may note be
+// feasible and we may need to make this modeling more flexible.
+enum class LibraryKind { Pthread = 0, Fuchsia, C11, NumLibraryKinds };
enum class SemanticsKind { NotApplicable = 0, PthreadSemantics, XNUSemantics };
@@ -42,35 +49,40 @@ enum class LockStateKind {
Error_DoubleInitWhileLocked,
};
+/// This class is intended for describing the list of events to detect.
+/// This list of events is the configuration of the MutexModeling checker.
struct EventDescriptor {
MutexRegionExtractor Trigger;
EventKind Kind{};
+ LibraryKind Library{};
SemanticsKind Semantics{};
// TODO: Modernize to spaceship when C++20 is available.
- [[nodiscard]] constexpr bool
- operator!=(const EventDescriptor &Other) const noexcept {
- return !(Trigger == Other.Trigger) || Kind != Other.Kind ||
- Semantics != Other.Semantics;
+ [[nodiscard]] bool operator!=(const EventDescriptor &Other) const noexcept {
+ return !(Trigger == Other.Trigger) || Library != Other.Library ||
+ Kind != Other.Kind || Semantics != Other.Semantics;
}
- [[nodiscard]] constexpr bool
- operator==(const EventDescriptor &Other) const noexcept {
+ [[nodiscard]] bool operator==(const EventDescriptor &Other) const noexcept {
return !(*this != Other);
}
};
+/// This class is used in the GDM to describe the events that were detected.
+/// As instances of this class can appear many times in the ExplodedGraph, it
+/// best to keep it as simple and small as possible.
struct EventMarker {
EventKind Kind{};
SemanticsKind Semantics{};
- const IdentifierInfo *Event;
+ LibraryKind Library{};
+ const IdentifierInfo *EventII;
const clang::Expr *EventExpr{};
const clang::ento::MemRegion *MutexRegion{};
// TODO: Modernize to spaceship when C++20 is available.
[[nodiscard]] constexpr bool
operator!=(const EventMarker &Other) const noexcept {
- return Event != Other.Event || Kind != Other.Kind ||
- Semantics != Other.Semantics || LockState != Other.LockState ||
+ return EventII != Other.EventII || Kind != Other.Kind ||
+ Semantics != Other.Semantics || Library != Other.Library ||
EventExpr != Other.EventExpr || MutexRegion != Other.MutexRegion;
}
[[nodiscard]] constexpr bool
@@ -98,8 +110,6 @@ struct CritSectionMarker {
}
};
-} // namespace mutex_modeling
-} // namespace ento
-} // namespace clang
+} // namespace clang::ento::mutex_modeling
#endif
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingGDM.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingGDM.h
index 566c8d7fdc730c..603f8172c9806a 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingGDM.h
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingGDM.h
@@ -48,10 +48,10 @@ namespace llvm {
template <> struct FoldingSetTrait<clang::ento::mutex_modeling::EventMarker> {
static void Profile(const clang::ento::mutex_modeling::EventMarker &EM,
llvm::FoldingSetNodeID &ID) {
- ID.Add(EM.Event);
ID.Add(EM.Kind);
+ ID.Add(EM.Library);
ID.Add(EM.Semantics);
- ID.Add(EM.LockState);
+ ID.Add(EM.EventII);
ID.Add(EM.EventExpr);
ID.Add(EM.MutexRegion);
}
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexRegionExtractor.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexRegionExtractor.h
index 88d7b79daad1b4..6fa33f9437adbc 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexRegionExtractor.h
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexRegionExtractor.h
@@ -18,27 +18,35 @@
namespace clang::ento::mutex_modeling {
-class FirstArgMutexExtractor : public clang::ento::CallDescription {
+class FirstArgMutexExtractor {
+ CallDescription CD;
+
public:
template <typename T>
- FirstArgMutexExtractor(T &&CD) : CallDescription(std::forward<T>(CD)) {}
+ FirstArgMutexExtractor(T &&CD) : CD(std::forward<T>(CD)) {}
+
+ [[nodiscard]] bool matches(const CallEvent &Call) const {
+ return CD.matches(Call);
+ }
- [[nodiscard]] const clang::ento::MemRegion *
- getRegion(const clang::ento::CallEvent &Call) const {
+ [[nodiscard]] const MemRegion *getRegion(const CallEvent &Call) const {
return Call.getArgSVal(0).getAsRegion();
}
};
-class MemberMutexExtractor : public clang::ento::CallDescription {
+class MemberMutexExtractor {
+ CallDescription CD;
+
public:
template <typename T>
- MemberMutexExtractor(T &&CD) : CallDescription(std::forward<T>(CD)) {}
+ MemberMutexExtractor(T &&CD) : CD(std::forward<T>(CD)) {}
- [[nodiscard]] const clang::ento::MemRegion *
- getRegion(const clang::ento::CallEvent &Call) const {
- return llvm::cast<clang::ento::CXXMemberCall>(Call)
- .getCXXThisVal()
- .getAsRegion();
+ [[nodiscard]] bool matches(const CallEvent &Call) const {
+ return CD.matches(Call);
+ }
+
+ [[nodiscard]] const MemRegion *getRegion(const CallEvent &Call) const {
+ return llvm::cast<CXXMemberCall>(Call).getCXXThisVal().getAsRegion();
}
};
@@ -47,7 +55,7 @@ template <bool IsLock> class RAIIMutexExtractor {
mutable bool IdentifierInfoInitialized{};
mutable llvm::SmallString<32> GuardName{};
- void initIdentifierInfo(const clang::ento::CallEvent &Call) const {
+ 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
@@ -59,8 +67,7 @@ template <bool IsLock> class RAIIMutexExtractor {
}
}
- template <typename T>
- bool matchesImpl(const clang::ento::CallEvent &Call) const {
+ template <typename T> bool matchesImpl(const CallEvent &Call) const {
const T *C = llvm::dyn_cast<T>(&Call);
if (!C)
return false;
@@ -72,21 +79,22 @@ template <bool IsLock> class RAIIMutexExtractor {
public:
RAIIMutexExtractor(llvm::StringRef GuardName) : GuardName(GuardName) {}
- [[nodiscard]] bool matches(const clang::ento::CallEvent &Call) const {
+ [[nodiscard]] bool matches(const CallEvent &Call) const {
initIdentifierInfo(Call);
if constexpr (IsLock) {
- return matchesImpl<clang::ento::CXXConstructorCall>(Call);
+ return matchesImpl<CXXConstructorCall>(Call);
} else {
- return matchesImpl<clang::ento::CXXDestructorCall>(Call);
+ return matchesImpl<CXXDestructorCall>(Call);
}
}
- [[nodiscard]] const clang::ento::MemRegion *
- getRegion(const clang::ento::CallEvent &Call) const {
- const clang::ento::MemRegion *MutexRegion = nullptr;
+ [[nodiscard]] const MemRegion *getRegion(const CallEvent &Call) const {
+ const MemRegion *MutexRegion = nullptr;
if constexpr (IsLock) {
- if (std::optional<clang::ento::SVal> Object =
- Call.getReturnValueUnderConstruction()) {
+ if (std::optional<SVal> Object = Call.getReturnValueUnderConstruction()) {
MutexRegion = Object->getAsRegion();
+ } else {
+ MutexRegion =
+ llvm::cast<CXXDestructorCall>(Call).getCXXThisVal().getAsRegion();
}
} else {
MutexRegion =
@@ -103,9 +111,8 @@ using MutexRegionExtractor =
std::variant<FirstArgMutexExtractor, MemberMutexExtractor,
RAIILockExtractor, RAIIReleaseExtractor>;
-inline const clang::ento::MemRegion *
-getRegion(const MutexRegionExtractor &Extractor,
- const clang::ento::CallEvent &Call) {
+inline const MemRegion *getRegion(const MutexRegionExtractor &Extractor,
+ const CallEvent &Call) {
return std::visit(
[&Call](auto &&Descriptor) { return Descriptor.getRegion(Call); },
Extractor);
@@ -120,10 +127,7 @@ inline bool operator==(const MutexRegionExtractor &LHS,
inline bool matches(const MutexRegionExtractor &Extractor,
const CallEvent &Call) {
return std::visit(
- [](auto &&Extractor, const CallEvent &Call) {
- return Extractor.matches(Call);
- },
- Extractor, Call);
+ [&Call](auto &&Extractor) { return Extractor.matches(Call); }, Extractor);
}
} // namespace clang::ento::mutex_modeling
diff --git a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
index 86530086ff1b27..63adf792eba04b 100644
--- a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
@@ -21,12 +21,14 @@
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "MutexModeling/MutexModelingAPI.h"
+
using namespace clang;
using namespace ento;
+using namespace mutex_modeling;
namespace {
@@ -68,191 +70,46 @@ struct LockState {
void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); }
};
-class PthreadLockChecker : public Checker<check::PostCall, check::DeadSymbols,
- check::RegionChanges> {
+class PthreadLockChecker : public Checker<check::PostCall> {
+// , check::DeadSymbols,
+// check::RegionChanges> {
+
public:
- enum LockingSemantics { NotApplicable = 0, PthreadSemantics, XNUSemantics };
enum CheckerKind {
CK_PthreadLockChecker,
CK_FuchsiaLockChecker,
CK_C11LockChecker,
CK_NumCheckKinds
};
+
bool ChecksEnabled[CK_NumCheckKinds] = {false};
CheckerNameRef CheckNames[CK_NumCheckKinds];
-private:
- typedef void (PthreadLockChecker::*FnCheck)(const CallEvent &Call,
- CheckerContext &C,
- CheckerKind CheckKind) const;
- CallDescriptionMap<FnCheck> PThreadCallbacks = {
- // Init.
- {{CDM::CLibrary, {"pthread_mutex_init"}, 2},
- &PthreadLockChecker::InitAnyLock},
- // TODO: pthread_rwlock_init(2 arguments).
- // TODO: lck_mtx_init(3 arguments).
- // TODO: lck_mtx_alloc_init(2 arguments) => returns the mutex.
- // TODO: lck_rw_init(3 arguments).
- // TODO: lck_rw_alloc_init(2 arguments) => returns the mutex.
-
- // Acquire.
- {{CDM::CLibrary, {"pthread_mutex_lock"}, 1},
- &PthreadLockChecker::AcquirePthreadLock},
- {{CDM::CLibrary, {"pthread_rwlock_rdlock"}, 1},
- &PthreadLockChecker::AcquirePthreadLock},
- {{CDM::CLibrary, {"pthread_rwlock_wrlock"}, 1},
- &PthreadLockChecker::AcquirePthreadLock},
- {{CDM::CLibrary, {"lck_mtx_lock"}, 1},
- &PthreadLockChecker::AcquireXNULock},
- {{CDM::CLibrary, {"lck_rw_lock_exclusive"}, 1},
- &PthreadLockChecker::AcquireXNULock},
- {{CDM::CLibrary, {"lck_rw_lock_shared"}, 1},
- &PthreadLockChecker::AcquireXNULock},
-
- // Try.
- {{CDM::CLibrary, {"pthread_mutex_trylock"}, 1},
- &PthreadLockChecker::TryPthreadLock},
- {{CDM::CLibrary, {"pthread_rwlock_tryrdlock"}, 1},
- &PthreadLockChecker::TryPthreadLock},
- {{CDM::CLibrary, {"pthread_rwlock_trywrlock"}, 1},
- &PthreadLockChecker::TryPthreadLock},
- {{CDM::CLibrary, {"lck_mtx_try_lock"}, 1},
- &PthreadLockChecker::TryXNULock},
- {{CDM::CLibrary, {"lck_rw_try_lock_exclusive"}, 1},
- &PthreadLockChecker::TryXNULock},
- {{CDM::CLibrary, {"lck_rw_try_lock_shared"}, 1},
- &PthreadLockChecker::TryXNULock},
-
- // Release.
- {{CDM::CLibrary, {"pthread_mutex_unlock"}, 1},
- &PthreadLockChecker::ReleaseAnyLock},
- {{CDM::CLibrary, {"pthread_rwlock_unlock"}, 1},
- &PthreadLockChecker::ReleaseAnyLock},
- {{CDM::CLibrary, {"lck_mtx_unlock"}, 1},
- &PthreadLockChecker::ReleaseAnyLock},
- {{CDM::CLibrary, {"lck_rw_unlock_exclusive"}, 1},
- &PthreadLockChecker::ReleaseAnyLock},
- {{CDM::CLibrary, {"lck_rw_unlock_shared"}, 1},
- &PthreadLockChecker::ReleaseAnyLock},
- {{CDM::CLibrary, {"lck_rw_done"}, 1},
- &PthreadLockChecker::ReleaseAnyLock},
-
- // Destroy.
- {{CDM::CLibrary, {"pthread_mutex_destroy"}, 1},
- &PthreadLockChecker::DestroyPthreadLock},
- {{CDM::CLibrary, {"lck_mtx_destroy"}, 2},
- &PthreadLockChecker::DestroyXNULock},
- // TODO: pthread_rwlock_destroy(1 argument).
- // TODO: lck_rw_destroy(2 arguments).
- };
-
- CallDescriptionMap<FnCheck> FuchsiaCallbacks = {
- // Init.
- {{CDM::CLibrary, {"spin_lock_init"}, 1},
- &PthreadLockChecker::InitAnyLock},
-
- // Acquire.
- {{CDM::CLibrary, {"spin_lock"}, 1},
- &PthreadLockChecker::AcquirePthreadLock},
- {{CDM::CLibrary, {"spin_lock_save"}, 3},
- &PthreadLockChecker::AcquirePthreadLock},
- {{CDM::CLibrary, {"sync_mutex_lock"}, 1},
- &PthreadLockChecker::AcquirePthreadLock},
- {{CDM::CLibrary, {"sync_mutex_lock_with_waiter"}, 1},
- &PthreadLockChecker::AcquirePthreadLock},
-
- // Try.
- {{CDM::CLibrary, {"spin_trylock"}, 1},
- &PthreadLockChecker::TryFuchsiaLock},
- {{CDM::CLibrary, {"sync_mutex_trylock"}, 1},
- &PthreadLockChecker::TryFuchsiaLock},
- {{CDM::CLibrary, {"sync_mutex_timedlock"}, 2},
- &PthreadLockChecker::TryFuchsiaLock},
-
- // Release.
- {{CDM::CLibrary, {"spin_unlock"}, 1},
- &PthreadLockChecker::ReleaseAnyLock},
- {{CDM::CLibrary, {"spin_unlock_restore"}, 3},
- &PthreadLockChecker::ReleaseAnyLock},
- {{CDM::CLibrary, {"sync_mutex_unlock"}, 1},
- &PthreadLockChecker::ReleaseAnyLock},
- };
-
- CallDescriptionMap<FnCheck> C11Callbacks = {
- // Init.
- {{CDM::CLibrary, {"mtx_init"}, 2}, &PthreadLockChecker::InitAnyLock},
-
- // Acquire.
- {{CDM::CLibrary, {"mtx_lock"}, 1},
- &PthreadLockChecker::AcquirePthreadLock},
-
- // Try.
- {{CDM::CLibrary, {"mtx_trylock"}, 1}, &PthreadLockChecker::TryC11Lock},
- {{CDM::CLibrary, {"mtx_timedlock"}, 2}, &PthreadLockChecker::TryC11Lock},
-
- // Release.
- {{CDM::CLibrary, {"mtx_unlock"}, 1}, &PthreadLockChecker::ReleaseAnyLock},
-
- // Destroy
- {{CDM::CLibrary, {"mtx_destroy"}, 1},
- &PthreadLockChecker::DestroyPthreadLock},
- };
+ PthreadLockChecker() {
+ RegisterEvents();
+ }
- ProgramStateRef resolvePossiblyDestroyedMutex(ProgramStateRef state,
- const MemRegion *lockR,
- const SymbolRef *sym) const;
+private:
+ std::vector<EventDescriptor> EventsToModel{
+ EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_init"}, 2),
+ EventKind::Init, LibraryKind::Pthread}};
+ void RegisterEvents() const {
+ for (auto &&Event : EventsToModel) {
+ RegisterEvent(Event);
+ }
+ }
void reportBug(CheckerContext &C, std::unique_ptr<BugType> BT[],
const Expr *MtxExpr, CheckerKind CheckKind,
StringRef Desc) const;
- // Init.
- void InitAnyLock(const CallEvent &Call, CheckerContext &C,
- CheckerKind CheckKind) const;
- void InitLockAux(const CallEvent &Call, CheckerContext &C,
- const Expr *MtxExpr, SVal MtxVal,
- CheckerKind CheckKind) const;
-
- // Lock, Try-lock.
- void AcquirePthreadLock(const CallEvent &Call, CheckerContext &C,
- CheckerKind CheckKind) const;
- void AcquireXNULock(const CallEvent &Call, CheckerContext &C,
- CheckerKind CheckKind) const;
- void TryPthreadLock(const CallEvent &Call, CheckerContext &C,
- CheckerKind CheckKind) const;
- void TryXNULock(const CallEvent &Call, CheckerContext &C,
- CheckerKind CheckKind) const;
- void TryFuchsiaLock(const CallEvent &Call, CheckerContext &C,
- CheckerKind CheckKind) const;
- void TryC11Lock(const CallEvent &Call, CheckerContext &C,
- CheckerKind CheckKind) const;
- void AcquireLockAux(const CallEvent &Call, CheckerContext &C,
- const Expr *MtxExpr, SVal MtxVal, bool IsTryLock,
- LockingSemantics Semantics, CheckerKind CheckKind) const;
-
- // Release.
- void ReleaseAnyLock(const CallEvent &Call, CheckerContext &C,
- CheckerKind CheckKind) const;
- void ReleaseLockAux(const CallEvent &Call, CheckerContext &C,
- const Expr *MtxExpr, SVal MtxVal,
- CheckerKind CheckKind) const;
-
- // Destroy.
- void DestroyPthreadLock(const CallEvent &Call, CheckerContext &C,
- CheckerKind CheckKind) const;
- void DestroyXNULock(const CallEvent &Call, CheckerContext &C,
- CheckerKind CheckKind) const;
- void DestroyLockAux(const CallEvent &Call, CheckerContext &C,
- const Expr *MtxExpr, SVal MtxVal,
- LockingSemantics Semantics, CheckerKind CheckKind) const;
-
public:
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
- void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
- ProgramStateRef
- checkRegionChanges(ProgramStateRef State, const InvalidatedSymbols *Symbols,
- ArrayRef<const MemRegion *> ExplicitRegions,
- ArrayRef<const MemRegion *> Regions,
- const LocationContext *LCtx, const CallEvent *Call) const;
+ // void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
+ // ProgramStateRef
+ // checkRegionChanges(ProgramStateRef State, const InvalidatedSymbols *Symbols,
+ // ArrayRef<const MemRegion *> ExplicitRegions,
+ // ArrayRef<const MemRegion *> Regions,
+ // const LocationContext *LCtx, const CallEvent *Call) const;
void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
const char *Sep) const override;
@@ -261,6 +118,7 @@ class PthreadLockChecker : public Checker<check::PostCall, check::DeadSymbols,
mutable std::unique_ptr<BugType> BT_doubleunlock[CK_NumCheckKinds];
mutable std::unique_ptr<BugType> BT_destroylock[CK_NumCheckKinds];
mutable std::unique_ptr<BugType> BT_initlock[CK_NumCheckKinds];
+ mutable std::unique_ptr<BugType> BT_initlockPthread;
mutable std::unique_ptr<BugType> BT_lor[CK_NumCheckKinds];
void initBugType(CheckerKind CheckKind) const {
@@ -277,111 +135,97 @@ class PthreadLockChecker : public Checker<check::PostCall, check::DeadSymbols,
BT_lor[CheckKind].reset(new BugType{CheckNames[CheckKind],
"Lock order reversal", "Lock checker"});
}
+
+ [[nodiscard]] constexpr PthreadLockChecker::CheckerKind
+ detectCheckerKind(mutex_modeling::EventMarker EV) const noexcept {
+ switch (EV.Library) {
+ case mutex_modeling::LibraryKind::Pthread:
+ return PthreadLockChecker::CK_PthreadLockChecker;
+ case mutex_modeling::LibraryKind::Fuchsia:
+ return PthreadLockChecker::CK_FuchsiaLockChecker;
+ case mutex_modeling::LibraryKind::C11:
+ return PthreadLockChecker::CK_C11LockChecker;
+ default:
+ llvm_unreachable("Unknown locking library");
+ }
+ }
};
} // end anonymous namespace
-// A stack of locks for tracking lock-unlock order.
-REGISTER_LIST_WITH_PROGRAMSTATE(LockSet, const MemRegion *)
+void PthreadLockChecker::checkPostCall(const CallEvent &Call,
+ CheckerContext &C) const {
-// An entry for tracking lock states.
-REGISTER_MAP_WITH_PROGRAMSTATE(LockMap, const MemRegion *, LockState)
+ ProgramStateRef State = C.getState();
-// Return values for unresolved calls to pthread_mutex_destroy().
-REGISTER_MAP_WITH_PROGRAMSTATE(DestroyRetVal, const MemRegion *, SymbolRef)
+ const auto &MTXEvents = State->get<MutexEvents>();
-void PthreadLockChecker::checkPostCall(const CallEvent &Call,
- CheckerContext &C) const {
- // FIXME: Try to handle cases when the implementation was inlined rather
- // than just giving up.
- if (C.wasInlined)
+ Call.dump(llvm::errs());
+ llvm::errs() << "before MTXEvents isEmpty check\n";
+
+ if (MTXEvents.isEmpty()) {
return;
+ }
- if (const FnCheck *Callback = PThreadCallbacks.lookup(Call))
- (this->**Callback)(Call, C, CK_PthreadLockChecker);
- else if (const FnCheck *Callback = FuchsiaCallbacks.lookup(Call))
- (this->**Callback)(Call, C, CK_FuchsiaLockChecker);
- else if (const FnCheck *Callback = C11Callbacks.lookup(Call))
- (this->**Callback)(Call, C, CK_C11LockChecker);
-}
+ llvm::errs() << "after MTXEvents isEmpty check\n";
+ printState(llvm::errs(), State, "\n", " ");
-// When a lock is destroyed, in some semantics(like PthreadSemantics) we are not
-// sure if the destroy call has succeeded or failed, and the lock enters one of
-// the 'possibly destroyed' state. There is a short time frame for the
-// programmer to check the return value to see if the lock was successfully
-// destroyed. Before we model the next operation over that lock, we call this
-// function to see if the return value was checked by now and set the lock state
-// - either to destroyed state or back to its previous state.
-
-// In PthreadSemantics, pthread_mutex_destroy() returns zero if the lock is
-// successfully destroyed and it returns a non-zero value otherwise.
-ProgramStateRef PthreadLockChecker::resolvePossiblyDestroyedMutex(
- ProgramStateRef state, const MemRegion *lockR, const SymbolRef *sym) const {
- const LockState *lstate = state->get<LockMap>(lockR);
- // Existence in DestroyRetVal ensures existence in LockMap.
- // Existence in Destroyed also ensures that the lock state for lockR is either
- // UntouchedAndPossiblyDestroyed or UnlockedAndPossiblyDestroyed.
- assert(lstate);
- assert(lstate->isUntouchedAndPossiblyDestroyed() ||
- lstate->isUnlockedAndPossiblyDestroyed());
-
- ConstraintManager &CMgr = state->getConstraintManager();
- ConditionTruthVal retZero = CMgr.isNull(state, *sym);
- if (retZero.isConstrainedFalse()) {
- if (lstate->isUntouchedAndPossiblyDestroyed())
- state = state->remove<LockMap>(lockR);
- else if (lstate->isUnlockedAndPossiblyDestroyed())
- state = state->set<LockMap>(lockR, LockState::getUnlocked());
- } else
- state = state->set<LockMap>(lockR, LockState::getDestroyed());
-
- // Removing the map entry (lockR, sym) from DestroyRetVal as the lock state is
- // now resolved.
- state = state->remove<DestroyRetVal>(lockR);
- return state;
-}
+ const auto &LastEvent = MTXEvents.getHead();
-void PthreadLockChecker::printState(raw_ostream &Out, ProgramStateRef State,
- const char *NL, const char *Sep) const {
- LockMapTy LM = State->get<LockMap>();
- if (!LM.isEmpty()) {
- Out << Sep << "Mutex states:" << NL;
- for (auto I : LM) {
- I.first->dumpToStream(Out);
- if (I.second.isLocked())
- Out << ": locked";
- else if (I.second.isUnlocked())
- Out << ": unlocked";
- else if (I.second.isDestroyed())
- Out << ": destroyed";
- else if (I.second.isUntouchedAndPossiblyDestroyed())
- Out << ": not tracked, possibly destroyed";
- else if (I.second.isUnlockedAndPossiblyDestroyed())
- Out << ": unlocked, possibly destroyed";
- Out << NL;
- }
+ if (LastEvent.Kind != EventKind::Init) {
+ return;
}
- LockSetTy LS = State->get<LockSet>();
- if (!LS.isEmpty()) {
- Out << Sep << "Mutex lock order:" << NL;
- for (auto I : LS) {
- I->dumpToStream(Out);
- Out << NL;
- }
+ const LockStateKind *const LockState =
+ State->get<LockStates>(LastEvent.MutexRegion);
+
+ if (!LockState || *LockState == LockStateKind::Destroyed) {
+ return;
}
- DestroyRetValTy DRV = State->get<DestroyRetVal>();
- if (!DRV.isEmpty()) {
- Out << Sep << "Mutexes in unresolved possibly destroyed state:" << NL;
- for (auto I : DRV) {
- I.first->dumpToStream(Out);
- Out << ": ";
- I.second->dumpToStream(Out);
- Out << NL;
- }
+ bool IsError = false;
+ StringRef Message;
+ switch (*LockState) {
+ case LockStateKind::Error_DoubleInit: {
+ IsError = true;
+ Message = "This lock has already been initialized";
+ break;
+ }
+ case LockStateKind::Error_DoubleInitWhileLocked: {
+ IsError = true;
+ Message = "This lock is still being held";
+ break;
}
+ default: {
+ }
+ }
+
+ if (!IsError) {
+ return;
+ }
+
+ reportBug(C, BT_initlock, LastEvent.EventExpr, detectCheckerKind(LastEvent), Message);
+}
+
+void PthreadLockChecker::reportBug(CheckerContext &C,
+ std::unique_ptr<BugType> BT[],
+ const Expr *MtxExpr, CheckerKind CheckKind,
+ StringRef Desc) const {
+ ExplodedNode *N = C.generateErrorNode();
+ if (!N)
+ return;
+ initBugType(CheckKind);
+ auto Report =
+ std::make_unique<PathSensitiveBugReport>(*BT[CheckKind], Desc, N);
+ Report->addRange(MtxExpr->getSourceRange());
+ C.emitReport(std::move(Report));
}
+void PthreadLockChecker::printState(raw_ostream &Out, ProgramStateRef State,
+ const char *NL, const char *Sep) const {
+ mutex_modeling::printState(Out, State, NL, Sep);
+}
+
+#if 0
void PthreadLockChecker::AcquirePthreadLock(const CallEvent &Call,
CheckerContext &C,
CheckerKind CheckKind) const {
@@ -650,20 +494,6 @@ void PthreadLockChecker::InitLockAux(const CallEvent &Call, CheckerContext &C,
reportBug(C, BT_initlock, MtxExpr, CheckKind, Message);
}
-void PthreadLockChecker::reportBug(CheckerContext &C,
- std::unique_ptr<BugType> BT[],
- const Expr *MtxExpr, CheckerKind CheckKind,
- StringRef Desc) const {
- ExplodedNode *N = C.generateErrorNode();
- if (!N)
- return;
- initBugType(CheckKind);
- auto Report =
- std::make_unique<PathSensitiveBugReport>(*BT[CheckKind], Desc, N);
- Report->addRange(MtxExpr->getSourceRange());
- C.emitReport(std::move(Report));
-}
-
void PthreadLockChecker::checkDeadSymbols(SymbolReaper &SymReaper,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
@@ -725,23 +555,25 @@ ProgramStateRef PthreadLockChecker::checkRegionChanges(
return State;
}
+#endif
void ento::registerPthreadLockBase(CheckerManager &mgr) {
mgr.registerChecker<PthreadLockChecker>();
}
-bool ento::shouldRegisterPthreadLockBase(const CheckerManager &mgr) { return true; }
+bool ento::shouldRegisterPthreadLockBase(const CheckerManager &mgr) {
+ return true;
+}
-#define REGISTER_CHECKER(name) \
+#define REGISTER_CHECKER(name, library) \
void ento::register##name(CheckerManager &mgr) { \
PthreadLockChecker *checker = mgr.getChecker<PthreadLockChecker>(); \
checker->ChecksEnabled[PthreadLockChecker::CK_##name] = true; \
checker->CheckNames[PthreadLockChecker::CK_##name] = \
mgr.getCurrentCheckerName(); \
} \
- \
bool ento::shouldRegister##name(const CheckerManager &mgr) { return true; }
-REGISTER_CHECKER(PthreadLockChecker)
-REGISTER_CHECKER(FuchsiaLockChecker)
-REGISTER_CHECKER(C11LockChecker)
+REGISTER_CHECKER(PthreadLockChecker, LibraryKind::Pthread)
+REGISTER_CHECKER(FuchsiaLockChecker, LibraryKind::Fuchsia)
+REGISTER_CHECKER(C11LockChecker, LibraryKind::C11)
>From 45004e9a6e7c1e954cf38518bf4def423028517f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Fri, 26 Jul 2024 09:50:47 +0200
Subject: [PATCH 10/30] add acquire to modeling
---
.../StaticAnalyzer/Checkers/MutexModeling.cpp | 45 +++++++++-
.../MutexModeling/MutexModelingDomain.h | 2 +
.../Checkers/PthreadLockChecker.cpp | 89 +++++++++++++------
3 files changed, 103 insertions(+), 33 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
index 0c8166ac61c6a6..319b3e39a8055c 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
@@ -141,10 +141,6 @@ ProgramStateRef MutexModeling::handleInit(const EventDescriptor &Event,
CheckerContext &C) const {
assert(MTX && "should only be called with a mutex region");
- State = State->add<MutexEvents>(
- EventMarker{Event.Kind, Event.Semantics, Event.Library,
- Call.getCalleeIdentifier(), Call.getOriginExpr(), MTX});
-
const LockStateKind *LState = State->get<LockStates>(MTX);
if (!LState)
@@ -164,6 +160,43 @@ ProgramStateRef MutexModeling::handleInit(const EventDescriptor &Event,
}
}
+ProgramStateRef handleAcquire(const EventDescriptor &Event,
+ const MemRegion *MTX, const CallEvent &Call,
+ ProgramStateRef State, CheckerContext &C) const {
+ assert(MTX && "should only be called with a mutex region");
+
+ const LockStateKind *LState = State->get<LockStates>(MTX);
+
+ if (!LState)
+ return State->set<LockStates>(MTX, LockStateKind::Locked);
+
+ switch (*LState) {
+ case (LockStateKind::Locked): {
+ return State->set<LockStates>(MTX, LockStateKind::Error_DoubleLock);
+ }
+ case (LockStateKind::Destroyed): {
+ return State->set<LockStates>(MTX, LockStateKind::Error_LockDestroyed);
+ }
+ }
+
+ if (Semantics == PthreadSemantics) {
+ // Assume that the return value was 0.
+ SVal RetVal = Call.getReturnValue();
+ if (auto DefinedRetVal = RetVal.getAs<DefinedSVal>()) {
+ // FIXME: If the lock function was inlined and returned true,
+ // we need to behave sanely - at least generate sink.
+ lockSucc = state->assume(*DefinedRetVal, false);
+ assert(lockSucc);
+ }
+ // We might want to handle the case when the mutex lock function was inlined
+ // and returned an Unknown or Undefined value.
+ } else {
+ // XNU locking semantics return void on non-try locks
+ assert((Semantics == XNUSemantics) && "Unknown locking semantics");
+ lockSucc = state;
+ }
+}
+
ProgramStateRef MutexModeling::handleEvent(const EventDescriptor &Event,
const MemRegion *MTX,
const CallEvent &Call,
@@ -171,6 +204,10 @@ ProgramStateRef MutexModeling::handleEvent(const EventDescriptor &Event,
CheckerContext &C) const {
assert(MTX && "should only be called with a mutex region");
+ State = State->add<MutexEvents>(
+ EventMarker{Event.Kind, Event.Semantics, Event.Library,
+ Call.getCalleeIdentifier(), Call.getOriginExpr(), MTX});
+
switch (Event.Kind) {
case EventKind::Init:
return handleInit(Event, MTX, Call, State, C);
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h
index 56e50bf4273efc..088d486fe48635 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h
@@ -47,6 +47,8 @@ enum class LockStateKind {
UnlockedAndPossiblyDestroyed,
Error_DoubleInit,
Error_DoubleInitWhileLocked,
+ Error_DoubleLock,
+ Error_LockDestroyed
};
/// This class is intended for describing the list of events to detect.
diff --git a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
index 63adf792eba04b..e1688a244f1645 100644
--- a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
@@ -71,8 +71,8 @@ struct LockState {
};
class PthreadLockChecker : public Checker<check::PostCall> {
-// , check::DeadSymbols,
-// check::RegionChanges> {
+ // , check::DeadSymbols,
+ // check::RegionChanges> {
public:
enum CheckerKind {
@@ -85,9 +85,7 @@ class PthreadLockChecker : public Checker<check::PostCall> {
bool ChecksEnabled[CK_NumCheckKinds] = {false};
CheckerNameRef CheckNames[CK_NumCheckKinds];
- PthreadLockChecker() {
- RegisterEvents();
- }
+ PthreadLockChecker() { RegisterEvents(); }
private:
std::vector<EventDescriptor> EventsToModel{
@@ -106,10 +104,12 @@ class PthreadLockChecker : public Checker<check::PostCall> {
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
// void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
// ProgramStateRef
- // checkRegionChanges(ProgramStateRef State, const InvalidatedSymbols *Symbols,
+ // checkRegionChanges(ProgramStateRef State, const InvalidatedSymbols
+ // *Symbols,
// ArrayRef<const MemRegion *> ExplicitRegions,
// ArrayRef<const MemRegion *> Regions,
- // const LocationContext *LCtx, const CallEvent *Call) const;
+ // const LocationContext *LCtx, const CallEvent *Call)
+ // const;
void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
const char *Sep) const override;
@@ -149,32 +149,20 @@ class PthreadLockChecker : public Checker<check::PostCall> {
llvm_unreachable("Unknown locking library");
}
}
+
+ void checkInitEvent(const EventMarker &LastEvent, CheckerContext &C) const;
+ void checkAcquireEvent(const EventMarker &LastEvent, CheckerContext &C) const;
+ void checkTryAcquireEvent(const EventMarker &LastEvent,
+ CheckerContext &C) const;
+ void checkReleaseEvent(const EventMarker &LastEvent, CheckerContext &C) const;
+ void checkDestroyEvent(const EventMarker &LastEvent, CheckerContext &C) const;
};
} // end anonymous namespace
-void PthreadLockChecker::checkPostCall(const CallEvent &Call,
- CheckerContext &C) const {
-
+void PthreadLockChecker::checkInitEvent(const EventMarker &LastEvent,
+ CheckerContext &C) const {
ProgramStateRef State = C.getState();
- const auto &MTXEvents = State->get<MutexEvents>();
-
- Call.dump(llvm::errs());
- llvm::errs() << "before MTXEvents isEmpty check\n";
-
- if (MTXEvents.isEmpty()) {
- return;
- }
-
- llvm::errs() << "after MTXEvents isEmpty check\n";
- printState(llvm::errs(), State, "\n", " ");
-
- const auto &LastEvent = MTXEvents.getHead();
-
- if (LastEvent.Kind != EventKind::Init) {
- return;
- }
-
const LockStateKind *const LockState =
State->get<LockStates>(LastEvent.MutexRegion);
@@ -203,7 +191,50 @@ void PthreadLockChecker::checkPostCall(const CallEvent &Call,
return;
}
- reportBug(C, BT_initlock, LastEvent.EventExpr, detectCheckerKind(LastEvent), Message);
+ reportBug(C, BT_initlock, LastEvent.EventExpr, detectCheckerKind(LastEvent),
+ Message);
+}
+
+void PthreadLockChecker::checkAcquireEvent(const EventMarker &LastEvent,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ const LockStateKind *const LockState =
+ State->get<LockStates>(LastEvent.MutexRegion);
+}
+
+void PthreadLockChecker::checkPostCall(const CallEvent &Call,
+ CheckerContext &C) const {
+
+ ProgramStateRef State = C.getState();
+
+ const auto &MTXEvents = State->get<MutexEvents>();
+
+ if (MTXEvents.isEmpty()) {
+ return;
+ }
+
+ const auto &LastEvent = MTXEvents.getHead();
+
+ switch (LastEvent.Kind) {
+ case EventKind::Init:
+ checkInitEvent(LastEvent, C);
+ break;
+ case EventKind::Acquire:
+ checkAcquireEvent(LastEvent, C);
+ break;
+ case EventKind::TryAcquire:
+ checkTryAcquireEvent(LastEvent, C);
+ break;
+ case EventKind::Release:
+ checkReleaseEvent(LastEvent, C);
+ break;
+ case EventKind::Destroy:
+ checkDestroyEvent(LastEvent, C);
+ break;
+ default:
+ llvm_unreachable("Unknown event kind");
+ }
}
void PthreadLockChecker::reportBug(CheckerContext &C,
>From 51548569d9033166b0621b5785d3437c7438ec12 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Fri, 26 Jul 2024 12:06:19 +0200
Subject: [PATCH 11/30] add trylock stub
---
.../StaticAnalyzer/Checkers/MutexModeling.cpp | 112 +++++++++++++-----
1 file changed, 83 insertions(+), 29 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
index 319b3e39a8055c..c9bac9c6a814fc 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
@@ -127,6 +127,19 @@ class MutexModeling : public Checker<check::PostCall> {
ProgramStateRef handleInit(const EventDescriptor &Event, const MemRegion *MTX,
const CallEvent &Call, ProgramStateRef State,
CheckerContext &C) const;
+ ProgramStateRef handleAcquire(const EventDescriptor &Event,
+ const MemRegion *MTX, const CallEvent &Call,
+ ProgramStateRef State, CheckerContext &C) const;
+ ProgramStateRef handleTryAcquire(const EventDescriptor &Event,
+ const MemRegion *MTX, const CallEvent &Call,
+ ProgramStateRef State,
+ CheckerContext &C) const;
+ ProgramStateRef handleRelease(const EventDescriptor &Event,
+ const MemRegion *MTX, const CallEvent &Call,
+ ProgramStateRef State, CheckerContext &C) const;
+ ProgramStateRef handleDestroy(const EventDescriptor &Event,
+ const MemRegion *MTX, const CallEvent &Call,
+ ProgramStateRef State, CheckerContext &C) const;
ProgramStateRef handleEvent(const EventDescriptor &Event,
const MemRegion *MTX, const CallEvent &Call,
ProgramStateRef State, CheckerContext &C) const;
@@ -160,40 +173,83 @@ ProgramStateRef MutexModeling::handleInit(const EventDescriptor &Event,
}
}
-ProgramStateRef handleAcquire(const EventDescriptor &Event,
- const MemRegion *MTX, const CallEvent &Call,
- ProgramStateRef State, CheckerContext &C) const {
- assert(MTX && "should only be called with a mutex region");
-
+static ProgramStateRef doAcquireCommonLogic(ProgramStateRef State,
+ const MemRegion *MTX) {
const LockStateKind *LState = State->get<LockStates>(MTX);
if (!LState)
- return State->set<LockStates>(MTX, LockStateKind::Locked);
+ State = State->set<LockStates>(MTX, LockStateKind::Locked);
+ else if (*LState == LockStateKind::Locked)
+ State = State->set<LockStates>(MTX, LockStateKind::Error_DoubleLock);
+ if (*LState == LockStateKind::Destroyed)
+ State = State->set<LockStates>(MTX, LockStateKind::Error_LockDestroyed);
- switch (*LState) {
- case (LockStateKind::Locked): {
- return State->set<LockStates>(MTX, LockStateKind::Error_DoubleLock);
- }
- case (LockStateKind::Destroyed): {
- return State->set<LockStates>(MTX, LockStateKind::Error_LockDestroyed);
- }
- }
+ return State;
+}
+
+ProgramStateRef MutexModeling::handleAcquire(const EventDescriptor &Event,
+ const MemRegion *MTX,
+ const CallEvent &Call,
+ ProgramStateRef State,
+ CheckerContext &C) const {
- if (Semantics == PthreadSemantics) {
+ State = doAcquireCommonLogic(State, MTX);
+
+ switch (Event.Semantics) {
+ case SemanticsKind::PthreadSemantics: {
// Assume that the return value was 0.
SVal RetVal = Call.getReturnValue();
if (auto DefinedRetVal = RetVal.getAs<DefinedSVal>()) {
// FIXME: If the lock function was inlined and returned true,
// we need to behave sanely - at least generate sink.
- lockSucc = state->assume(*DefinedRetVal, false);
- assert(lockSucc);
+ State = State->assume(*DefinedRetVal, false);
+ assert(State);
}
+ break;
+ }
+ case SemanticsKind::XNUSemantics:
+ // XNU semantics return void on non-try locks.
+ break;
+ default:
+ llvm_unreachable(
+ "Acquire events should have either Pthread or XNU semantics");
+ }
+
+ return State;
+}
+
+ProgramStateRef MutexModeling::handleTryAcquire(const EventDescriptor &Event,
+ const MemRegion *MTX,
+ const CallEvent &Call,
+ ProgramStateRef State,
+ CheckerContext &C) const {
+
+ State = doAcquireCommonLogic(State, MTX);
+
+ // Bifurcate the state, and allow a mode where the lock acquisition fails.
+ ProgramStateRef LockSucc;
+ SVal RetVal = Call.getReturnValue();
+ if (auto DefinedRetVal = RetVal.getAs<DefinedSVal>()) {
+ ProgramStateRef LockFail;
+ switch (Event.Semantics) {
+ case SemanticsKind::PthreadSemantics:
+ std::tie(LockFail, LockSucc) = State->assume(*DefinedRetVal);
+ break;
+ case SemanticsKind::XNUSemantics:
+ std::tie(LockSucc, LockFail) = State->assume(*DefinedRetVal);
+ break;
+ default:
+ llvm_unreachable("Unknown tryLock locking semantics");
+ }
+
+ // This is the bifurcation point in the ExplodedGraph, we do not need to
+ // pass the new ExplodedGraph node because we do not plan on building this
+ // failed case part forward in this checker.
+ C.addTransition(LockFail);
+
+ return LockSucc;
// We might want to handle the case when the mutex lock function was inlined
// and returned an Unknown or Undefined value.
- } else {
- // XNU locking semantics return void on non-try locks
- assert((Semantics == XNUSemantics) && "Unknown locking semantics");
- lockSucc = state;
}
}
@@ -212,22 +268,20 @@ ProgramStateRef MutexModeling::handleEvent(const EventDescriptor &Event,
case EventKind::Init:
return handleInit(Event, MTX, Call, State, C);
break;
- default:
- llvm_unreachable("Unhandled event kind!");
-#if 0
case EventKind::Acquire:
- handleAcquire(Event, Call, C);
+ handleAcquire(Event, MTX, Call, State, C);
break;
case EventKind::TryAcquire:
- handleTryAcquire(Event, Call, C);
+ handleTryAcquire(Event, MTX, Call, State, C);
break;
case EventKind::Release:
- handleRelease(Event, Call, C);
+ handleRelease(Event, MTX, Call, State, C);
break;
case EventKind::Destroy:
- handleDestroy(Event, Call, C);
+ handleDestroy(Event, MTX, Call, State, C);
break;
-#endif
+ default:
+ llvm_unreachable("Unhandled event kind!");
}
}
>From a77eb689bfcd0b888babd973350017631465f4f2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Fri, 26 Jul 2024 14:51:06 +0200
Subject: [PATCH 12/30] add release stub
---
.../StaticAnalyzer/Checkers/MutexModeling.cpp | 41 ++++++++++++++++++-
1 file changed, 40 insertions(+), 1 deletion(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
index c9bac9c6a814fc..b1df525df13989 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
@@ -247,10 +247,49 @@ ProgramStateRef MutexModeling::handleTryAcquire(const EventDescriptor &Event,
// failed case part forward in this checker.
C.addTransition(LockFail);
- return LockSucc;
+ // Pass the state where the locking succeeded onwards.
+ State = LockSucc;
// We might want to handle the case when the mutex lock function was inlined
// and returned an Unknown or Undefined value.
}
+ return State;
+}
+
+ProgramStateRef MutexModeling::handleRelease(const EventDescriptor &Event,
+ const MemRegion *MTX,
+ const CallEvent &Call,
+ ProgramStateRef State,
+ CheckerContext &C) const {
+
+ const LockStateKind *LState = State->get<LockStates>(MTX);
+
+ if (!LState)
+ return State->set<LockStates>(MTX, LockStateKind::Unlocked);
+
+ if (*LState == LockStateKind::Unlocked)
+ return State->set<LockStates>(MTX, LockStateKind::Error_DoubleUnlock);
+
+ if (*LState == LockStateKind::Destroyed)
+ return State->set<LockStates>(MTX, LockStateKind::Error_UnlockDestroyed);
+
+ // Check if the currently released mutex is also the most recently locked one.
+ // If not, report a lock reversal bug.
+ // NOTE: MutexEvents stores events in reverse order as the ImmutableList data
+ // structure grows towards its Head element.
+ const auto &Events = State->get<MutexEvents>();
+ bool IsLockReversal = false;
+ for (const auto &Event : Events) {
+ if (Event.Kind == EventKind::Acquire) {
+ IsLockReversal = Event.MutexRegion != MTX;
+ break;
+ }
+ }
+
+ if (IsLockReversal) {
+ return State->set<LockStates>(MTX, LockStateKind::Error_LockReversal);
+ }
+
+ return State->set<LockStates>(MTX, LockStateKind::Unlocked);
}
ProgramStateRef MutexModeling::handleEvent(const EventDescriptor &Event,
>From 530a7ca3532e9787962147c18daa6a948edeaa0c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Mon, 29 Jul 2024 20:56:30 +0200
Subject: [PATCH 13/30] add all event kind checks, move mutexmodeling because
of dependency issues
---
.../clang/StaticAnalyzer/Checkers/Checkers.td | 11 +-
.../StaticAnalyzer/Checkers/MutexModeling.cpp | 171 ++++++-
.../MutexModeling/MutexModelingDomain.h | 7 +-
.../Checkers/PthreadLockChecker.cpp | 464 +++---------------
4 files changed, 227 insertions(+), 426 deletions(-)
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index d93b8b0871677b..155cde7cb2106e 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -261,6 +261,11 @@ def NonnullGlobalConstantsChecker: Checker<"NonnilStringConstants">,
let ParentPackage = CoreAlpha in {
+def MutexModeling : Checker<"MutexModeling">,
+ HelpText<"Model mutexes lock and unlock events">,
+ Documentation<NotDocumented>,
+ Hidden;
+
def BoolAssignmentChecker : Checker<"BoolAssignment">,
HelpText<"Warn about assigning non-{0,1} values to Boolean variables">,
Documentation<HasDocumentation>;
@@ -313,6 +318,7 @@ def StackAddrAsyncEscapeChecker : Checker<"StackAddressAsyncEscape">,
def PthreadLockBase : Checker<"PthreadLockBase">,
HelpText<"Helper registering multiple checks.">,
+ Dependencies<[MutexModeling]>,
Documentation<NotDocumented>,
Hidden;
@@ -509,11 +515,6 @@ def UnixAPIMisuseChecker : Checker<"API">,
HelpText<"Check calls to various UNIX/Posix functions">,
Documentation<HasDocumentation>;
-def MutexModeling : Checker<"MutexModeling">,
- HelpText<"Model mutexes lock and unlock events">,
- Documentation<NotDocumented>,
- Hidden;
-
def BlockInCriticalSectionChecker : Checker<"BlockInCriticalSection">,
HelpText<"Check for calls to blocking functions inside a critical section">,
Dependencies<[MutexModeling]>,
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
index b1df525df13989..2d7d18364a90f5 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
@@ -38,7 +38,7 @@ namespace {
// successfully destroyed and it returns a non-zero value otherwise.
ProgramStateRef resolvePossiblyDestroyedMutex(ProgramStateRef State,
const MemRegion *LockReg,
- const SymbolRef *Sym) {
+ const SymbolRef *LockReturnSym) {
const LockStateKind *LState = State->get<LockStates>(LockReg);
// Existence in DestroyRetVal ensures existence in LockMap.
// Existence in Destroyed also ensures that the lock state for lockR is either
@@ -48,7 +48,7 @@ ProgramStateRef resolvePossiblyDestroyedMutex(ProgramStateRef State,
*LState == LockStateKind::UnlockedAndPossiblyDestroyed);
ConstraintManager &CMgr = State->getConstraintManager();
- ConditionTruthVal RetZero = CMgr.isNull(State, *Sym);
+ ConditionTruthVal RetZero = CMgr.isNull(State, *LockReturnSym);
if (RetZero.isConstrainedFalse()) {
switch (*LState) {
case LockStateKind::UntouchedAndPossiblyDestroyed: {
@@ -118,10 +118,19 @@ void updateCriticalSectionOnUnlock(const EventDescriptor &UnlockDescriptor,
C.addTransition(State->set<CritSections>(NewList));
}
-class MutexModeling : public Checker<check::PostCall> {
+class MutexModeling : public Checker<check::PostCall, check::DeadSymbols,
+ check::RegionChanges> {
public:
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+ void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
+
+ ProgramStateRef
+ checkRegionChanges(ProgramStateRef State, const InvalidatedSymbols *Symbols,
+ ArrayRef<const MemRegion *> ExplicitRegions,
+ ArrayRef<const MemRegion *> Regions,
+ const LocationContext *LCtx, const CallEvent *Call) const;
+
private:
mutable std::unique_ptr<BugType> BT_initlock;
ProgramStateRef handleInit(const EventDescriptor &Event, const MemRegion *MTX,
@@ -178,13 +187,18 @@ static ProgramStateRef doAcquireCommonLogic(ProgramStateRef State,
const LockStateKind *LState = State->get<LockStates>(MTX);
if (!LState)
- State = State->set<LockStates>(MTX, LockStateKind::Locked);
- else if (*LState == LockStateKind::Locked)
- State = State->set<LockStates>(MTX, LockStateKind::Error_DoubleLock);
- if (*LState == LockStateKind::Destroyed)
- State = State->set<LockStates>(MTX, LockStateKind::Error_LockDestroyed);
+ return State->set<LockStates>(MTX, LockStateKind::Locked);
- return State;
+ switch (*LState) {
+ case LockStateKind::Unlocked:
+ return State->set<LockStates>(MTX, LockStateKind::Locked);
+ case LockStateKind::Locked:
+ return State->set<LockStates>(MTX, LockStateKind::Error_DoubleLock);
+ case LockStateKind::Destroyed:
+ return State->set<LockStates>(MTX, LockStateKind::Error_LockDestroyed);
+ default:
+ return State;
+ }
}
ProgramStateRef MutexModeling::handleAcquire(const EventDescriptor &Event,
@@ -249,8 +263,8 @@ ProgramStateRef MutexModeling::handleTryAcquire(const EventDescriptor &Event,
// Pass the state where the locking succeeded onwards.
State = LockSucc;
- // We might want to handle the case when the mutex lock function was inlined
- // and returned an Unknown or Undefined value.
+ // We might want to handle the case when the mutex lock function was
+ // inlined and returned an Unknown or Undefined value.
}
return State;
}
@@ -272,10 +286,10 @@ ProgramStateRef MutexModeling::handleRelease(const EventDescriptor &Event,
if (*LState == LockStateKind::Destroyed)
return State->set<LockStates>(MTX, LockStateKind::Error_UnlockDestroyed);
- // Check if the currently released mutex is also the most recently locked one.
- // If not, report a lock reversal bug.
- // NOTE: MutexEvents stores events in reverse order as the ImmutableList data
- // structure grows towards its Head element.
+ // Check if the currently released mutex is also the most recently locked
+ // one. If not, report a lock reversal bug. NOTE: MutexEvents stores events
+ // in reverse order as the ImmutableList data structure grows towards its
+ // Head element.
const auto &Events = State->get<MutexEvents>();
bool IsLockReversal = false;
for (const auto &Event : Events) {
@@ -292,6 +306,54 @@ ProgramStateRef MutexModeling::handleRelease(const EventDescriptor &Event,
return State->set<LockStates>(MTX, LockStateKind::Unlocked);
}
+ProgramStateRef MutexModeling::handleDestroy(const EventDescriptor &Event,
+ const MemRegion *MTX,
+ const CallEvent &Call,
+ ProgramStateRef State,
+ CheckerContext &C) const {
+
+ // TODO: Check if calling resolvePossiblyDestroyedMutex is necessary.
+ const SymbolRef *LockReturnSym = State->get<DestroyedRetVals>(MTX);
+ if (LockReturnSym)
+ State = resolvePossiblyDestroyedMutex(State, MTX, LockReturnSym);
+
+ const LockStateKind *LState = State->get<LockStates>(MTX);
+
+ if (!LState || *LState == LockStateKind::Unlocked) {
+ if (Event.Semantics != SemanticsKind::PthreadSemantics) {
+ return State->set<LockStates>(MTX, LockStateKind::Destroyed);
+ }
+
+ // In PthreadSemantics we reason about the return value of the destroy
+ // calls.
+ SymbolRef Sym = Call.getReturnValue().getAsSymbol();
+ if (!Sym) {
+ return State->remove<LockStates>(MTX);
+ }
+
+ State = State->set<DestroyedRetVals>(MTX, Sym);
+ return State->set<LockStates>(
+ MTX, LState && *LState == LockStateKind::Unlocked
+ ? LockStateKind::UnlockedAndPossiblyDestroyed
+ : LockStateKind::UntouchedAndPossiblyDestroyed);
+ }
+
+ if (*LState == LockStateKind::Locked) {
+ return State->set<LockStates>(MTX, LockStateKind::Error_DestroyLocked);
+ }
+
+ if (*LState == LockStateKind::Destroyed) {
+ return State->set<LockStates>(MTX, LockStateKind::Error_DoubleDestroy);
+ }
+
+ assert(LState && *LState != LockStateKind::Unlocked &&
+ *LState != LockStateKind::Locked &&
+ *LState != LockStateKind::Destroyed &&
+ "Only error states should remain if we get here");
+
+ return State;
+}
+
ProgramStateRef MutexModeling::handleEvent(const EventDescriptor &Event,
const MemRegion *MTX,
const CallEvent &Call,
@@ -306,19 +368,14 @@ ProgramStateRef MutexModeling::handleEvent(const EventDescriptor &Event,
switch (Event.Kind) {
case EventKind::Init:
return handleInit(Event, MTX, Call, State, C);
- break;
case EventKind::Acquire:
- handleAcquire(Event, MTX, Call, State, C);
- break;
+ return handleAcquire(Event, MTX, Call, State, C);
case EventKind::TryAcquire:
- handleTryAcquire(Event, MTX, Call, State, C);
- break;
+ return handleTryAcquire(Event, MTX, Call, State, C);
case EventKind::Release:
- handleRelease(Event, MTX, Call, State, C);
- break;
+ return handleRelease(Event, MTX, Call, State, C);
case EventKind::Destroy:
- handleDestroy(Event, MTX, Call, State, C);
- break;
+ return handleDestroy(Event, MTX, Call, State, C);
default:
llvm_unreachable("Unhandled event kind!");
}
@@ -328,7 +385,6 @@ void MutexModeling::checkPostCall(const CallEvent &Call,
CheckerContext &C) const {
// FIXME: Try to handle cases when the implementation was inlined rather
// than just giving up.
-
if (C.wasInlined)
return;
@@ -345,6 +401,71 @@ void MutexModeling::checkPostCall(const CallEvent &Call,
}
}
+void MutexModeling::checkDeadSymbols(SymbolReaper &SymReaper,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ for (auto I : State->get<DestroyedRetVals>()) {
+ // Once the return value symbol dies, no more checks can be performed
+ // against it. See if the return value was checked before this point.
+ // This would remove the symbol from the map as well.
+ if (SymReaper.isDead(I.second))
+ State = resolvePossiblyDestroyedMutex(State, I.first, &I.second);
+ }
+
+ for (auto I : State->get<LockStates>()) {
+ // Stop tracking dead mutex regions as well.
+ if (!SymReaper.isLiveRegion(I.first)) {
+ State = State->remove<LockStates>(I.first);
+ State = State->remove<DestroyedRetVals>(I.first);
+ }
+ }
+
+ // TODO: We probably need to clean up the lock stack as well.
+ // It is tricky though: even if the mutex cannot be unlocked anymore,
+ // it can still participate in lock order reversal resolution.
+
+ C.addTransition(State);
+}
+
+ProgramStateRef MutexModeling::checkRegionChanges(
+ ProgramStateRef State, const InvalidatedSymbols *Symbols,
+ ArrayRef<const MemRegion *> ExplicitRegions,
+ ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx,
+ const CallEvent *Call) const {
+
+ bool IsLibraryFunction = false;
+ if (Call && Call->isGlobalCFunction()) {
+ // Avoid invalidating mutex state when a known supported function is
+ // called.
+ for (auto &&Event : RegisteredEvents) {
+ if (matches(Event.Trigger, *Call)) {
+ return State;
+ }
+ }
+
+ if (Call->isInSystemHeader())
+ IsLibraryFunction = true;
+ }
+
+ for (auto R : Regions) {
+ // We assume that system library function wouldn't touch the mutex unless
+ // it takes the mutex explicitly as an argument.
+ // FIXME: This is a bit quadratic.
+ if (IsLibraryFunction && !llvm::is_contained(ExplicitRegions, R))
+ continue;
+
+ State = State->remove<LockStates>(R);
+ State = State->remove<DestroyedRetVals>(R);
+
+ // TODO: We need to invalidate the lock stack as well. This is tricky
+ // to implement correctly and efficiently though, because the effects
+ // of mutex escapes on lock order may be fairly varied.
+ }
+
+ return State;
+}
+
namespace clang {
namespace ento {
// Checker registration
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h
index 088d486fe48635..9dcb3b967dd5ec 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h
@@ -48,7 +48,12 @@ enum class LockStateKind {
Error_DoubleInit,
Error_DoubleInitWhileLocked,
Error_DoubleLock,
- Error_LockDestroyed
+ Error_LockDestroyed,
+ Error_DoubleUnlock,
+ Error_UnlockDestroyed,
+ Error_LockReversal,
+ Error_DestroyLocked,
+ Error_DoubleDestroy
};
/// This class is intended for describing the list of events to detect.
diff --git a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
index e1688a244f1645..6bcfafc8544bcf 100644
--- a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
@@ -32,44 +32,6 @@ using namespace mutex_modeling;
namespace {
-struct LockState {
- enum Kind {
- Destroyed,
- Locked,
- Unlocked,
- UntouchedAndPossiblyDestroyed,
- UnlockedAndPossiblyDestroyed
- } K;
-
-private:
- LockState(Kind K) : K(K) {}
-
-public:
- static LockState getLocked() { return LockState(Locked); }
- static LockState getUnlocked() { return LockState(Unlocked); }
- static LockState getDestroyed() { return LockState(Destroyed); }
- static LockState getUntouchedAndPossiblyDestroyed() {
- return LockState(UntouchedAndPossiblyDestroyed);
- }
- static LockState getUnlockedAndPossiblyDestroyed() {
- return LockState(UnlockedAndPossiblyDestroyed);
- }
-
- bool operator==(const LockState &X) const { return K == X.K; }
-
- bool isLocked() const { return K == Locked; }
- bool isUnlocked() const { return K == Unlocked; }
- bool isDestroyed() const { return K == Destroyed; }
- bool isUntouchedAndPossiblyDestroyed() const {
- return K == UntouchedAndPossiblyDestroyed;
- }
- bool isUnlockedAndPossiblyDestroyed() const {
- return K == UnlockedAndPossiblyDestroyed;
- }
-
- void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); }
-};
-
class PthreadLockChecker : public Checker<check::PostCall> {
// , check::DeadSymbols,
// check::RegionChanges> {
@@ -90,7 +52,12 @@ class PthreadLockChecker : public Checker<check::PostCall> {
private:
std::vector<EventDescriptor> EventsToModel{
EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_init"}, 2),
- EventKind::Init, LibraryKind::Pthread}};
+ EventKind::Init, LibraryKind::Pthread},
+ EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_lock"}),
+ EventKind::Acquire, LibraryKind::Pthread,
+ SemanticsKind::PthreadSemantics},
+ EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_unlock"}),
+ EventKind::Release, LibraryKind::Pthread}};
void RegisterEvents() const {
for (auto &&Event : EventsToModel) {
RegisterEvent(Event);
@@ -152,8 +119,6 @@ class PthreadLockChecker : public Checker<check::PostCall> {
void checkInitEvent(const EventMarker &LastEvent, CheckerContext &C) const;
void checkAcquireEvent(const EventMarker &LastEvent, CheckerContext &C) const;
- void checkTryAcquireEvent(const EventMarker &LastEvent,
- CheckerContext &C) const;
void checkReleaseEvent(const EventMarker &LastEvent, CheckerContext &C) const;
void checkDestroyEvent(const EventMarker &LastEvent, CheckerContext &C) const;
};
@@ -166,41 +131,84 @@ void PthreadLockChecker::checkInitEvent(const EventMarker &LastEvent,
const LockStateKind *const LockState =
State->get<LockStates>(LastEvent.MutexRegion);
- if (!LockState || *LockState == LockStateKind::Destroyed) {
+ if (!LockState) {
return;
}
- bool IsError = false;
- StringRef Message;
- switch (*LockState) {
- case LockStateKind::Error_DoubleInit: {
- IsError = true;
- Message = "This lock has already been initialized";
- break;
+ if (*LockState == LockStateKind::Error_DoubleInit) {
+ reportBug(C, BT_initlock, LastEvent.EventExpr, detectCheckerKind(LastEvent),
+ "This lock has already been initialized");
+ } else if (*LockState == LockStateKind::Error_DoubleInitWhileLocked) {
+ reportBug(C, BT_initlock, LastEvent.EventExpr, detectCheckerKind(LastEvent),
+ "This lock is still being held");
}
- case LockStateKind::Error_DoubleInitWhileLocked: {
- IsError = true;
- Message = "This lock is still being held";
- break;
- }
- default: {
+}
+
+void PthreadLockChecker::checkAcquireEvent(const EventMarker &LastEvent,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ const LockStateKind *const LockState =
+ State->get<LockStates>(LastEvent.MutexRegion);
+
+ if (!LockState) {
+ return;
}
+
+ if (*LockState == LockStateKind::Error_DoubleLock) {
+ reportBug(C, BT_doublelock, LastEvent.EventExpr,
+ detectCheckerKind(LastEvent),
+ "This lock has already been acquired");
+ } else if (*LockState == LockStateKind::Error_LockDestroyed) {
+ reportBug(C, BT_destroylock, LastEvent.EventExpr,
+ detectCheckerKind(LastEvent),
+ "This lock has already been destroyed");
}
+}
- if (!IsError) {
+void PthreadLockChecker::checkReleaseEvent(const EventMarker &LastEvent,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ const LockStateKind *const LockState =
+ State->get<LockStates>(LastEvent.MutexRegion);
+
+ if (!LockState) {
return;
}
- reportBug(C, BT_initlock, LastEvent.EventExpr, detectCheckerKind(LastEvent),
- Message);
+ if (*LockState == LockStateKind::Error_DoubleUnlock) {
+ reportBug(C, BT_doubleunlock, LastEvent.EventExpr,
+ detectCheckerKind(LastEvent),
+ "This lock has already been released");
+ } else if (*LockState == LockStateKind::Error_UnlockDestroyed) {
+ reportBug(C, BT_destroylock, LastEvent.EventExpr,
+ detectCheckerKind(LastEvent),
+ "This lock has already been destroyed");
+ } else if (*LockState == LockStateKind::Error_LockReversal) {
+ reportBug(C, BT_lor, LastEvent.EventExpr, detectCheckerKind(LastEvent),
+ "Lock order reversal");
+ }
}
-void PthreadLockChecker::checkAcquireEvent(const EventMarker &LastEvent,
+void PthreadLockChecker::checkDestroyEvent(const EventMarker &LastEvent,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
- const LockStateKind *const LockState =
+ const LockStateKind *const LState =
State->get<LockStates>(LastEvent.MutexRegion);
+
+ if (!LState || *LState == LockStateKind::Destroyed) {
+ return;
+ }
+
+ if (*LState == LockStateKind::Error_DestroyLocked) {
+ reportBug(C, BT_destroylock, LastEvent.EventExpr,
+ detectCheckerKind(LastEvent), "This lock is still locked");
+ } else if (*LState == LockStateKind::Error_DoubleDestroy)
+ reportBug(C, BT_destroylock, LastEvent.EventExpr,
+ detectCheckerKind(LastEvent),
+ "This lock has already been destroyed");
}
void PthreadLockChecker::checkPostCall(const CallEvent &Call,
@@ -221,10 +229,8 @@ void PthreadLockChecker::checkPostCall(const CallEvent &Call,
checkInitEvent(LastEvent, C);
break;
case EventKind::Acquire:
- checkAcquireEvent(LastEvent, C);
- break;
case EventKind::TryAcquire:
- checkTryAcquireEvent(LastEvent, C);
+ checkAcquireEvent(LastEvent, C);
break;
case EventKind::Release:
checkReleaseEvent(LastEvent, C);
@@ -256,338 +262,6 @@ void PthreadLockChecker::printState(raw_ostream &Out, ProgramStateRef State,
mutex_modeling::printState(Out, State, NL, Sep);
}
-#if 0
-void PthreadLockChecker::AcquirePthreadLock(const CallEvent &Call,
- CheckerContext &C,
- CheckerKind CheckKind) const {
- AcquireLockAux(Call, C, Call.getArgExpr(0), Call.getArgSVal(0), false,
- PthreadSemantics, CheckKind);
-}
-
-void PthreadLockChecker::AcquireXNULock(const CallEvent &Call,
- CheckerContext &C,
- CheckerKind CheckKind) const {
- AcquireLockAux(Call, C, Call.getArgExpr(0), Call.getArgSVal(0), false,
- XNUSemantics, CheckKind);
-}
-
-void PthreadLockChecker::TryPthreadLock(const CallEvent &Call,
- CheckerContext &C,
- CheckerKind CheckKind) const {
- AcquireLockAux(Call, C, Call.getArgExpr(0), Call.getArgSVal(0), true,
- PthreadSemantics, CheckKind);
-}
-
-void PthreadLockChecker::TryXNULock(const CallEvent &Call, CheckerContext &C,
- CheckerKind CheckKind) const {
- AcquireLockAux(Call, C, Call.getArgExpr(0), Call.getArgSVal(0), true,
- PthreadSemantics, CheckKind);
-}
-
-void PthreadLockChecker::TryFuchsiaLock(const CallEvent &Call,
- CheckerContext &C,
- CheckerKind CheckKind) const {
- AcquireLockAux(Call, C, Call.getArgExpr(0), Call.getArgSVal(0), true,
- PthreadSemantics, CheckKind);
-}
-
-void PthreadLockChecker::TryC11Lock(const CallEvent &Call, CheckerContext &C,
- CheckerKind CheckKind) const {
- AcquireLockAux(Call, C, Call.getArgExpr(0), Call.getArgSVal(0), true,
- PthreadSemantics, CheckKind);
-}
-
-void PthreadLockChecker::AcquireLockAux(const CallEvent &Call,
- CheckerContext &C, const Expr *MtxExpr,
- SVal MtxVal, bool IsTryLock,
- enum LockingSemantics Semantics,
- CheckerKind CheckKind) const {
- if (!ChecksEnabled[CheckKind])
- return;
-
- const MemRegion *lockR = MtxVal.getAsRegion();
- if (!lockR)
- return;
-
- ProgramStateRef state = C.getState();
- const SymbolRef *sym = state->get<DestroyRetVal>(lockR);
- if (sym)
- state = resolvePossiblyDestroyedMutex(state, lockR, sym);
-
- if (const LockState *LState = state->get<LockMap>(lockR)) {
- if (LState->isLocked()) {
- reportBug(C, BT_doublelock, MtxExpr, CheckKind,
- "This lock has already been acquired");
- return;
- } else if (LState->isDestroyed()) {
- reportBug(C, BT_destroylock, MtxExpr, CheckKind,
- "This lock has already been destroyed");
- return;
- }
- }
-
- ProgramStateRef lockSucc = state;
- if (IsTryLock) {
- // Bifurcate the state, and allow a mode where the lock acquisition fails.
- SVal RetVal = Call.getReturnValue();
- if (auto DefinedRetVal = RetVal.getAs<DefinedSVal>()) {
- ProgramStateRef lockFail;
- switch (Semantics) {
- case PthreadSemantics:
- std::tie(lockFail, lockSucc) = state->assume(*DefinedRetVal);
- break;
- case XNUSemantics:
- std::tie(lockSucc, lockFail) = state->assume(*DefinedRetVal);
- break;
- default:
- llvm_unreachable("Unknown tryLock locking semantics");
- }
- assert(lockFail && lockSucc);
- C.addTransition(lockFail);
- }
- // We might want to handle the case when the mutex lock function was inlined
- // and returned an Unknown or Undefined value.
- } else if (Semantics == PthreadSemantics) {
- // Assume that the return value was 0.
- SVal RetVal = Call.getReturnValue();
- if (auto DefinedRetVal = RetVal.getAs<DefinedSVal>()) {
- // FIXME: If the lock function was inlined and returned true,
- // we need to behave sanely - at least generate sink.
- lockSucc = state->assume(*DefinedRetVal, false);
- assert(lockSucc);
- }
- // We might want to handle the case when the mutex lock function was inlined
- // and returned an Unknown or Undefined value.
- } else {
- // XNU locking semantics return void on non-try locks
- assert((Semantics == XNUSemantics) && "Unknown locking semantics");
- lockSucc = state;
- }
-
- // Record that the lock was acquired.
- lockSucc = lockSucc->add<LockSet>(lockR);
- lockSucc = lockSucc->set<LockMap>(lockR, LockState::getLocked());
- C.addTransition(lockSucc);
-}
-
-void PthreadLockChecker::ReleaseAnyLock(const CallEvent &Call,
- CheckerContext &C,
- CheckerKind CheckKind) const {
- ReleaseLockAux(Call, C, Call.getArgExpr(0), Call.getArgSVal(0), CheckKind);
-}
-
-void PthreadLockChecker::ReleaseLockAux(const CallEvent &Call,
- CheckerContext &C, const Expr *MtxExpr,
- SVal MtxVal,
- CheckerKind CheckKind) const {
- if (!ChecksEnabled[CheckKind])
- return;
-
- const MemRegion *lockR = MtxVal.getAsRegion();
- if (!lockR)
- return;
-
- ProgramStateRef state = C.getState();
- const SymbolRef *sym = state->get<DestroyRetVal>(lockR);
- if (sym)
- state = resolvePossiblyDestroyedMutex(state, lockR, sym);
-
- if (const LockState *LState = state->get<LockMap>(lockR)) {
- if (LState->isUnlocked()) {
- reportBug(C, BT_doubleunlock, MtxExpr, CheckKind,
- "This lock has already been unlocked");
- return;
- } else if (LState->isDestroyed()) {
- reportBug(C, BT_destroylock, MtxExpr, CheckKind,
- "This lock has already been destroyed");
- return;
- }
- }
-
- LockSetTy LS = state->get<LockSet>();
-
- if (!LS.isEmpty()) {
- const MemRegion *firstLockR = LS.getHead();
- if (firstLockR != lockR) {
- reportBug(C, BT_lor, MtxExpr, CheckKind,
- "This was not the most recently acquired lock. Possible lock "
- "order reversal");
- return;
- }
- // Record that the lock was released.
- state = state->set<LockSet>(LS.getTail());
- }
-
- state = state->set<LockMap>(lockR, LockState::getUnlocked());
- C.addTransition(state);
-}
-
-void PthreadLockChecker::DestroyPthreadLock(const CallEvent &Call,
- CheckerContext &C,
- CheckerKind CheckKind) const {
- DestroyLockAux(Call, C, Call.getArgExpr(0), Call.getArgSVal(0),
- PthreadSemantics, CheckKind);
-}
-
-void PthreadLockChecker::DestroyXNULock(const CallEvent &Call,
- CheckerContext &C,
- CheckerKind CheckKind) const {
- DestroyLockAux(Call, C, Call.getArgExpr(0), Call.getArgSVal(0), XNUSemantics,
- CheckKind);
-}
-
-void PthreadLockChecker::DestroyLockAux(const CallEvent &Call,
- CheckerContext &C, const Expr *MtxExpr,
- SVal MtxVal,
- enum LockingSemantics Semantics,
- CheckerKind CheckKind) const {
- if (!ChecksEnabled[CheckKind])
- return;
-
- const MemRegion *LockR = MtxVal.getAsRegion();
- if (!LockR)
- return;
-
- ProgramStateRef State = C.getState();
-
- const SymbolRef *sym = State->get<DestroyRetVal>(LockR);
- if (sym)
- State = resolvePossiblyDestroyedMutex(State, LockR, sym);
-
- const LockState *LState = State->get<LockMap>(LockR);
- // Checking the return value of the destroy method only in the case of
- // PthreadSemantics
- if (Semantics == PthreadSemantics) {
- if (!LState || LState->isUnlocked()) {
- SymbolRef sym = Call.getReturnValue().getAsSymbol();
- if (!sym) {
- State = State->remove<LockMap>(LockR);
- C.addTransition(State);
- return;
- }
- State = State->set<DestroyRetVal>(LockR, sym);
- if (LState && LState->isUnlocked())
- State = State->set<LockMap>(
- LockR, LockState::getUnlockedAndPossiblyDestroyed());
- else
- State = State->set<LockMap>(
- LockR, LockState::getUntouchedAndPossiblyDestroyed());
- C.addTransition(State);
- return;
- }
- } else {
- if (!LState || LState->isUnlocked()) {
- State = State->set<LockMap>(LockR, LockState::getDestroyed());
- C.addTransition(State);
- return;
- }
- }
-
- StringRef Message = LState->isLocked()
- ? "This lock is still locked"
- : "This lock has already been destroyed";
-
- reportBug(C, BT_destroylock, MtxExpr, CheckKind, Message);
-}
-
-void PthreadLockChecker::InitAnyLock(const CallEvent &Call, CheckerContext &C,
- CheckerKind CheckKind) const {
- InitLockAux(Call, C, Call.getArgExpr(0), Call.getArgSVal(0), CheckKind);
-}
-
-void PthreadLockChecker::InitLockAux(const CallEvent &Call, CheckerContext &C,
- const Expr *MtxExpr, SVal MtxVal,
- CheckerKind CheckKind) const {
- if (!ChecksEnabled[CheckKind])
- return;
-
- const MemRegion *LockR = MtxVal.getAsRegion();
- if (!LockR)
- return;
-
- ProgramStateRef State = C.getState();
-
- const SymbolRef *sym = State->get<DestroyRetVal>(LockR);
- if (sym)
- State = resolvePossiblyDestroyedMutex(State, LockR, sym);
-
- const struct LockState *LState = State->get<LockMap>(LockR);
- if (!LState || LState->isDestroyed()) {
- State = State->set<LockMap>(LockR, LockState::getUnlocked());
- C.addTransition(State);
- return;
- }
-
- StringRef Message = LState->isLocked()
- ? "This lock is still being held"
- : "This lock has already been initialized";
-
- reportBug(C, BT_initlock, MtxExpr, CheckKind, Message);
-}
-
-void PthreadLockChecker::checkDeadSymbols(SymbolReaper &SymReaper,
- CheckerContext &C) const {
- ProgramStateRef State = C.getState();
-
- for (auto I : State->get<DestroyRetVal>()) {
- // Once the return value symbol dies, no more checks can be performed
- // against it. See if the return value was checked before this point.
- // This would remove the symbol from the map as well.
- if (SymReaper.isDead(I.second))
- State = resolvePossiblyDestroyedMutex(State, I.first, &I.second);
- }
-
- for (auto I : State->get<LockMap>()) {
- // Stop tracking dead mutex regions as well.
- if (!SymReaper.isLiveRegion(I.first)) {
- State = State->remove<LockMap>(I.first);
- State = State->remove<DestroyRetVal>(I.first);
- }
- }
-
- // TODO: We probably need to clean up the lock stack as well.
- // It is tricky though: even if the mutex cannot be unlocked anymore,
- // it can still participate in lock order reversal resolution.
-
- C.addTransition(State);
-}
-
-ProgramStateRef PthreadLockChecker::checkRegionChanges(
- ProgramStateRef State, const InvalidatedSymbols *Symbols,
- ArrayRef<const MemRegion *> ExplicitRegions,
- ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx,
- const CallEvent *Call) const {
-
- bool IsLibraryFunction = false;
- if (Call && Call->isGlobalCFunction()) {
- // Avoid invalidating mutex state when a known supported function is called.
- if (PThreadCallbacks.lookup(*Call) || FuchsiaCallbacks.lookup(*Call) ||
- C11Callbacks.lookup(*Call))
- return State;
-
- if (Call->isInSystemHeader())
- IsLibraryFunction = true;
- }
-
- for (auto R : Regions) {
- // We assume that system library function wouldn't touch the mutex unless
- // it takes the mutex explicitly as an argument.
- // FIXME: This is a bit quadratic.
- if (IsLibraryFunction && !llvm::is_contained(ExplicitRegions, R))
- continue;
-
- State = State->remove<LockMap>(R);
- State = State->remove<DestroyRetVal>(R);
-
- // TODO: We need to invalidate the lock stack as well. This is tricky
- // to implement correctly and efficiently though, because the effects
- // of mutex escapes on lock order may be fairly varied.
- }
-
- return State;
-}
-#endif
-
void ento::registerPthreadLockBase(CheckerManager &mgr) {
mgr.registerChecker<PthreadLockChecker>();
}
>From a6ce6146e33ebaed37b994981047df863fbe2f2c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Tue, 30 Jul 2024 17:12:53 +0200
Subject: [PATCH 14/30] reword variables, move initialization of descriptors
---
.../StaticAnalyzer/Checkers/MutexModeling.cpp | 93 +++++++++++-------
.../Checkers/PthreadLockChecker.cpp | 97 +++++++++++++------
2 files changed, 125 insertions(+), 65 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
index 2d7d18364a90f5..e553234e6b73f3 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
@@ -39,18 +39,18 @@ namespace {
ProgramStateRef resolvePossiblyDestroyedMutex(ProgramStateRef State,
const MemRegion *LockReg,
const SymbolRef *LockReturnSym) {
- const LockStateKind *LState = State->get<LockStates>(LockReg);
+ const LockStateKind *LockState = State->get<LockStates>(LockReg);
// Existence in DestroyRetVal ensures existence in LockMap.
// Existence in Destroyed also ensures that the lock state for lockR is either
// UntouchedAndPossiblyDestroyed or UnlockedAndPossiblyDestroyed.
- assert(LState);
- assert(*LState == LockStateKind::UntouchedAndPossiblyDestroyed ||
- *LState == LockStateKind::UnlockedAndPossiblyDestroyed);
+ assert(LockState);
+ assert(*LockState == LockStateKind::UntouchedAndPossiblyDestroyed ||
+ *LockState == LockStateKind::UnlockedAndPossiblyDestroyed);
ConstraintManager &CMgr = State->getConstraintManager();
ConditionTruthVal RetZero = CMgr.isNull(State, *LockReturnSym);
if (RetZero.isConstrainedFalse()) {
- switch (*LState) {
+ switch (*LockState) {
case LockStateKind::UntouchedAndPossiblyDestroyed: {
State = State->remove<LockStates>(LockReg);
break;
@@ -163,12 +163,12 @@ ProgramStateRef MutexModeling::handleInit(const EventDescriptor &Event,
CheckerContext &C) const {
assert(MTX && "should only be called with a mutex region");
- const LockStateKind *LState = State->get<LockStates>(MTX);
+ const LockStateKind *LockState = State->get<LockStates>(MTX);
- if (!LState)
+ if (!LockState)
return State->set<LockStates>(MTX, LockStateKind::Unlocked);
- switch (*LState) {
+ switch (*LockState) {
case (LockStateKind::Destroyed): {
return State->set<LockStates>(MTX, LockStateKind::Unlocked);
}
@@ -184,12 +184,12 @@ ProgramStateRef MutexModeling::handleInit(const EventDescriptor &Event,
static ProgramStateRef doAcquireCommonLogic(ProgramStateRef State,
const MemRegion *MTX) {
- const LockStateKind *LState = State->get<LockStates>(MTX);
+ const LockStateKind *LockState = State->get<LockStates>(MTX);
- if (!LState)
+ if (!LockState)
return State->set<LockStates>(MTX, LockStateKind::Locked);
- switch (*LState) {
+ switch (*LockState) {
case LockStateKind::Unlocked:
return State->set<LockStates>(MTX, LockStateKind::Locked);
case LockStateKind::Locked:
@@ -275,31 +275,53 @@ ProgramStateRef MutexModeling::handleRelease(const EventDescriptor &Event,
ProgramStateRef State,
CheckerContext &C) const {
- const LockStateKind *LState = State->get<LockStates>(MTX);
+ const LockStateKind *LockState = State->get<LockStates>(MTX);
- if (!LState)
- return State->set<LockStates>(MTX, LockStateKind::Unlocked);
+ // FIXME: to be more conservative we shoud maybe just assume, that the lock
+ // is was in a locked state? This would assume that before we start modeling
+ // the mutex, the state was correct.
+ // return State->set<LockStates>(MTX, LockStateKind::Unlocked);
+ if (!LockState)
+ return State->set<LockStates>(MTX, LockStateKind::Error_DoubleUnlock);
- if (*LState == LockStateKind::Unlocked)
+ if (*LockState == LockStateKind::Unlocked)
return State->set<LockStates>(MTX, LockStateKind::Error_DoubleUnlock);
- if (*LState == LockStateKind::Destroyed)
+ if (*LockState == LockStateKind::Destroyed)
return State->set<LockStates>(MTX, LockStateKind::Error_UnlockDestroyed);
// Check if the currently released mutex is also the most recently locked
- // one. If not, report a lock reversal bug. NOTE: MutexEvents stores events
- // in reverse order as the ImmutableList data structure grows towards its
- // Head element.
+ // one. If not, report a lock reversal bug.
+ //
+ // NOTE: MutexEvents stores events in reverse order as the ImmutableList data
+ // structure grows towards its Head element.
+ //
+ // Every time a Release event is met in the reverse order of Mutex events it
+ // is pushed to a stack. Every time an Acquire-like event is met, if the top
+ // of the stack matches the Acquire event's mutex region, it is popped
+ // (signifying that the order of lock/unlocks was matching), but if a
+ // non-matching item is found, we are sure that there is a reversal.
+ //
+ // FIXME: A stack of mutexes could be used instead of computing the reverse
+ // critical sections every time, use the critical section modeling aggregated
+ // data for this check, and rewrite the comment paragraph above.
const auto &Events = State->get<MutexEvents>();
- bool IsLockReversal = false;
+ llvm::SmallVector<const EventMarker *, 4> InverseLockStack;
for (const auto &Event : Events) {
- if (Event.Kind == EventKind::Acquire) {
- IsLockReversal = Event.MutexRegion != MTX;
- break;
+ if (Event.Kind == EventKind::Release) {
+ InverseLockStack.push_back(&Event);
+ } else if (Event.Kind == EventKind::Acquire ||
+ Event.Kind == EventKind::TryAcquire) {
+ if (InverseLockStack.empty()) {
+ continue;
+ }
+ if (InverseLockStack.back()->MutexRegion == Event.MutexRegion) {
+ InverseLockStack.pop_back();
+ }
}
}
- if (IsLockReversal) {
+ if (!InverseLockStack.empty()) {
return State->set<LockStates>(MTX, LockStateKind::Error_LockReversal);
}
@@ -317,9 +339,9 @@ ProgramStateRef MutexModeling::handleDestroy(const EventDescriptor &Event,
if (LockReturnSym)
State = resolvePossiblyDestroyedMutex(State, MTX, LockReturnSym);
- const LockStateKind *LState = State->get<LockStates>(MTX);
+ const LockStateKind *LockState = State->get<LockStates>(MTX);
- if (!LState || *LState == LockStateKind::Unlocked) {
+ if (!LockState || *LockState == LockStateKind::Unlocked) {
if (Event.Semantics != SemanticsKind::PthreadSemantics) {
return State->set<LockStates>(MTX, LockStateKind::Destroyed);
}
@@ -333,22 +355,22 @@ ProgramStateRef MutexModeling::handleDestroy(const EventDescriptor &Event,
State = State->set<DestroyedRetVals>(MTX, Sym);
return State->set<LockStates>(
- MTX, LState && *LState == LockStateKind::Unlocked
+ MTX, LockState && *LockState == LockStateKind::Unlocked
? LockStateKind::UnlockedAndPossiblyDestroyed
: LockStateKind::UntouchedAndPossiblyDestroyed);
}
- if (*LState == LockStateKind::Locked) {
+ if (*LockState == LockStateKind::Locked) {
return State->set<LockStates>(MTX, LockStateKind::Error_DestroyLocked);
}
- if (*LState == LockStateKind::Destroyed) {
+ if (*LockState == LockStateKind::Destroyed) {
return State->set<LockStates>(MTX, LockStateKind::Error_DoubleDestroy);
}
- assert(LState && *LState != LockStateKind::Unlocked &&
- *LState != LockStateKind::Locked &&
- *LState != LockStateKind::Destroyed &&
+ assert(LockState && *LockState != LockStateKind::Unlocked &&
+ *LockState != LockStateKind::Locked &&
+ *LockState != LockStateKind::Destroyed &&
"Only error states should remain if we get here");
return State;
@@ -469,11 +491,8 @@ ProgramStateRef MutexModeling::checkRegionChanges(
namespace clang {
namespace ento {
// Checker registration
-void registerMutexModeling(CheckerManager &mgr) {
- mgr.registerChecker<MutexModeling>();
- RegisterEvent(
- EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_init"}, 2),
- EventKind::Init, LibraryKind::Pthread});
+void registerMutexModeling(CheckerManager &CM) {
+ CM.registerChecker<MutexModeling>();
}
bool shouldRegisterMutexModeling(const CheckerManager &) { return true; }
} // namespace ento
diff --git a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
index 6bcfafc8544bcf..325b0c168c9196 100644
--- a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
@@ -33,8 +33,6 @@ using namespace mutex_modeling;
namespace {
class PthreadLockChecker : public Checker<check::PostCall> {
- // , check::DeadSymbols,
- // check::RegionChanges> {
public:
enum CheckerKind {
@@ -50,14 +48,7 @@ class PthreadLockChecker : public Checker<check::PostCall> {
PthreadLockChecker() { RegisterEvents(); }
private:
- std::vector<EventDescriptor> EventsToModel{
- EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_init"}, 2),
- EventKind::Init, LibraryKind::Pthread},
- EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_lock"}),
- EventKind::Acquire, LibraryKind::Pthread,
- SemanticsKind::PthreadSemantics},
- EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_unlock"}),
- EventKind::Release, LibraryKind::Pthread}};
+ std::vector<EventDescriptor> EventsToModel{};
void RegisterEvents() const {
for (auto &&Event : EventsToModel) {
RegisterEvent(Event);
@@ -180,14 +171,14 @@ void PthreadLockChecker::checkReleaseEvent(const EventMarker &LastEvent,
if (*LockState == LockStateKind::Error_DoubleUnlock) {
reportBug(C, BT_doubleunlock, LastEvent.EventExpr,
detectCheckerKind(LastEvent),
- "This lock has already been released");
+ "This lock has already been unlocked");
} else if (*LockState == LockStateKind::Error_UnlockDestroyed) {
reportBug(C, BT_destroylock, LastEvent.EventExpr,
detectCheckerKind(LastEvent),
"This lock has already been destroyed");
} else if (*LockState == LockStateKind::Error_LockReversal) {
reportBug(C, BT_lor, LastEvent.EventExpr, detectCheckerKind(LastEvent),
- "Lock order reversal");
+ "This was not the most recently acquired lock");
}
}
@@ -195,17 +186,17 @@ void PthreadLockChecker::checkDestroyEvent(const EventMarker &LastEvent,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
- const LockStateKind *const LState =
+ const LockStateKind *const LockState =
State->get<LockStates>(LastEvent.MutexRegion);
- if (!LState || *LState == LockStateKind::Destroyed) {
+ if (!LockState || *LockState == LockStateKind::Destroyed) {
return;
}
- if (*LState == LockStateKind::Error_DestroyLocked) {
+ if (*LockState == LockStateKind::Error_DestroyLocked) {
reportBug(C, BT_destroylock, LastEvent.EventExpr,
detectCheckerKind(LastEvent), "This lock is still locked");
- } else if (*LState == LockStateKind::Error_DoubleDestroy)
+ } else if (*LockState == LockStateKind::Error_DoubleDestroy)
reportBug(C, BT_destroylock, LastEvent.EventExpr,
detectCheckerKind(LastEvent),
"This lock has already been destroyed");
@@ -224,6 +215,9 @@ void PthreadLockChecker::checkPostCall(const CallEvent &Call,
const auto &LastEvent = MTXEvents.getHead();
+ if (!ChecksEnabled[detectCheckerKind(LastEvent)])
+ return;
+
switch (LastEvent.Kind) {
case EventKind::Init:
checkInitEvent(LastEvent, C);
@@ -270,15 +264,62 @@ bool ento::shouldRegisterPthreadLockBase(const CheckerManager &mgr) {
return true;
}
-#define REGISTER_CHECKER(name, library) \
- void ento::register##name(CheckerManager &mgr) { \
- PthreadLockChecker *checker = mgr.getChecker<PthreadLockChecker>(); \
- checker->ChecksEnabled[PthreadLockChecker::CK_##name] = true; \
- checker->CheckNames[PthreadLockChecker::CK_##name] = \
- mgr.getCurrentCheckerName(); \
- } \
- bool ento::shouldRegister##name(const CheckerManager &mgr) { return true; }
-
-REGISTER_CHECKER(PthreadLockChecker, LibraryKind::Pthread)
-REGISTER_CHECKER(FuchsiaLockChecker, LibraryKind::Fuchsia)
-REGISTER_CHECKER(C11LockChecker, LibraryKind::C11)
+void ento::registerPthreadLockChecker(CheckerManager &CM) {
+ PthreadLockChecker *ImplChecker = CM.getChecker<PthreadLockChecker>();
+ ImplChecker->ChecksEnabled[PthreadLockChecker::CK_PthreadLockChecker] = true;
+ ImplChecker->CheckNames[PthreadLockChecker::CK_PthreadLockChecker] =
+ CM.getCurrentCheckerName();
+
+ RegisterEvent(
+ EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_init"}, 2),
+ EventKind::Init, LibraryKind::Pthread});
+ RegisterEvent(
+ EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_init"}, 2),
+ EventKind::Init, LibraryKind::Pthread});
+ RegisterEvent(EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_lock"}),
+ EventKind::Acquire, LibraryKind::Pthread,
+ SemanticsKind::PthreadSemantics});
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"pthread_mutex_trylock"}), EventKind::TryAcquire,
+ LibraryKind::Pthread, SemanticsKind::PthreadSemantics});
+ RegisterEvent(EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_unlock"}),
+ EventKind::Release, LibraryKind::Pthread});
+ RegisterEvent(EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_destroy"}),
+ EventKind::Destroy, LibraryKind::Pthread});
+}
+
+bool ento::shouldRegisterPthreadLockChecker(const CheckerManager &CM) {
+ return true;
+}
+
+void ento::registerFuchsiaLockChecker(CheckerManager &CM) {
+ PthreadLockChecker *ImplChecker = CM.getChecker<PthreadLockChecker>();
+ ImplChecker->ChecksEnabled[PthreadLockChecker::CK_FuchsiaLockChecker] = true;
+ ImplChecker->CheckNames[PthreadLockChecker::CK_FuchsiaLockChecker] =
+ CM.getCurrentCheckerName();
+}
+
+bool ento::shouldRegisterFuchsiaLockChecker(const CheckerManager &CM) {
+ return true;
+}
+
+void ento::registerC11LockChecker(CheckerManager &CM) {
+ PthreadLockChecker *ImplChecker = CM.getChecker<PthreadLockChecker>();
+ ImplChecker->ChecksEnabled[PthreadLockChecker::CK_C11LockChecker] = true;
+ ImplChecker->CheckNames[PthreadLockChecker::CK_C11LockChecker] =
+ CM.getCurrentCheckerName();
+
+ RegisterEvent(EventDescriptor{MakeFirstArgExtractor({"lck_mtx_lock"}),
+ EventKind::Acquire, LibraryKind::C11,
+ SemanticsKind::XNUSemantics});
+ RegisterEvent(EventDescriptor{MakeFirstArgExtractor({"lck_mtx_try_lock"}),
+ EventKind::TryAcquire, LibraryKind::C11,
+ SemanticsKind::XNUSemantics});
+ RegisterEvent(EventDescriptor{MakeFirstArgExtractor({"lck_mtx_unlock"}),
+ EventKind::Release, LibraryKind::C11,
+ SemanticsKind::XNUSemantics});
+}
+
+bool ento::shouldRegisterC11LockChecker(const CheckerManager &CM) {
+ return true;
+}
>From 9cd8c5f7846183be1f66d05c9ea7a0ec91efed58 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Thu, 1 Aug 2024 13:50:20 +0200
Subject: [PATCH 15/30] add pthread and c11 destroy
---
clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
index 325b0c168c9196..e2b5b9868a7025 100644
--- a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
@@ -284,8 +284,9 @@ void ento::registerPthreadLockChecker(CheckerManager &CM) {
LibraryKind::Pthread, SemanticsKind::PthreadSemantics});
RegisterEvent(EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_unlock"}),
EventKind::Release, LibraryKind::Pthread});
- RegisterEvent(EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_destroy"}),
- EventKind::Destroy, LibraryKind::Pthread});
+ RegisterEvent(
+ EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_destroy"}),
+ EventKind::Destroy, LibraryKind::Pthread});
}
bool ento::shouldRegisterPthreadLockChecker(const CheckerManager &CM) {
@@ -318,6 +319,8 @@ void ento::registerC11LockChecker(CheckerManager &CM) {
RegisterEvent(EventDescriptor{MakeFirstArgExtractor({"lck_mtx_unlock"}),
EventKind::Release, LibraryKind::C11,
SemanticsKind::XNUSemantics});
+ RegisterEvent(EventDescriptor{MakeFirstArgExtractor({"lck_mtx_destroy"}, 2),
+ EventKind::Destroy, LibraryKind::C11});
}
bool ento::shouldRegisterC11LockChecker(const CheckerManager &CM) {
>From bedf7b4c13a7fe3db96dd8b93efe26e3349c01c5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Thu, 1 Aug 2024 18:01:10 +0200
Subject: [PATCH 16/30] add all previously modelled events
---
.../Checkers/PthreadLockChecker.cpp | 281 +++++++++++++++---
1 file changed, 245 insertions(+), 36 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
index e2b5b9868a7025..903df9a7ca86b4 100644
--- a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
@@ -60,14 +60,6 @@ class PthreadLockChecker : public Checker<check::PostCall> {
public:
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
- // void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
- // ProgramStateRef
- // checkRegionChanges(ProgramStateRef State, const InvalidatedSymbols
- // *Symbols,
- // ArrayRef<const MemRegion *> ExplicitRegions,
- // ArrayRef<const MemRegion *> Regions,
- // const LocationContext *LCtx, const CallEvent *Call)
- // const;
void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
const char *Sep) const override;
@@ -270,23 +262,136 @@ void ento::registerPthreadLockChecker(CheckerManager &CM) {
ImplChecker->CheckNames[PthreadLockChecker::CK_PthreadLockChecker] =
CM.getCurrentCheckerName();
- RegisterEvent(
- EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_init"}, 2),
- EventKind::Init, LibraryKind::Pthread});
- RegisterEvent(
- EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_init"}, 2),
- EventKind::Init, LibraryKind::Pthread});
- RegisterEvent(EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_lock"}),
- EventKind::Acquire, LibraryKind::Pthread,
- SemanticsKind::PthreadSemantics});
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"pthread_mutex_trylock"}), EventKind::TryAcquire,
- LibraryKind::Pthread, SemanticsKind::PthreadSemantics});
- RegisterEvent(EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_unlock"}),
- EventKind::Release, LibraryKind::Pthread});
- RegisterEvent(
- EventDescriptor{MakeFirstArgExtractor({"pthread_mutex_destroy"}),
- EventKind::Destroy, LibraryKind::Pthread});
+ // clang-format off
+ // Init
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor(
+ {"pthread_mutex_init"}, 2),
+ EventKind::Init,
+ LibraryKind::Pthread
+ });
+
+ // Acquire
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor(
+ {"pthread_mutex_lock"}),
+ EventKind::Acquire,
+ LibraryKind::Pthread,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"pthread_rwlock_rdlock"}),
+ EventKind::Acquire,
+ LibraryKind::Pthread,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"pthread_rwlock_wrlock"}),
+ EventKind::Acquire,
+ LibraryKind::Pthread,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_mtx_lock"}),
+ EventKind::Acquire,
+ LibraryKind::Pthread,
+ SemanticsKind::XNUSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_rw_lock_exclusive"}),
+ EventKind::Acquire,
+ LibraryKind::Pthread,
+ SemanticsKind::XNUSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_rw_lock_shared"}),
+ EventKind::Acquire,
+ LibraryKind::Pthread,
+ SemanticsKind::XNUSemantics
+ });
+
+ // TryAcquire
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"pthread_mutex_trylock"}),
+ EventKind::TryAcquire,
+ LibraryKind::Pthread,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"pthread_rwlock_tryrdlock"}),
+ EventKind::Acquire,
+ LibraryKind::Pthread,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"pthread_rwlock_trywrlock"}),
+ EventKind::Acquire,
+ LibraryKind::Pthread,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_mtx_try_lock"}),
+ EventKind::Acquire,
+ LibraryKind::Pthread,
+ SemanticsKind::XNUSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_rw_try_lock_exclusive"}),
+ EventKind::Acquire,
+ LibraryKind::Pthread,
+ SemanticsKind::XNUSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_rw_try_lock_shared"}),
+ EventKind::Acquire,
+ LibraryKind::Pthread,
+ SemanticsKind::XNUSemantics
+ });
+
+ // Release
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"pthread_mutex_unlock"}),
+ EventKind::Release,
+ LibraryKind::Pthread
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"pthread_rwlock_unlock"}),
+ EventKind::Release,
+ LibraryKind::Pthread
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_mtx_unlock"}),
+ EventKind::Release,
+ LibraryKind::Pthread
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_rw_unlock_exclusive"}),
+ EventKind::Release,
+ LibraryKind::Pthread
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_rw_unlock_shared"}),
+ EventKind::Release,
+ LibraryKind::Pthread
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_rw_done"}),
+ EventKind::Release,
+ LibraryKind::Pthread
+ });
+
+ // Destroy
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"pthread_mutex_destroy"}),
+ EventKind::Destroy,
+ LibraryKind::Pthread
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_mtx_destroy"}, 2),
+ EventKind::Destroy,
+ LibraryKind::Pthread
+ });
+ // clang-format on
}
bool ento::shouldRegisterPthreadLockChecker(const CheckerManager &CM) {
@@ -298,6 +403,77 @@ void ento::registerFuchsiaLockChecker(CheckerManager &CM) {
ImplChecker->ChecksEnabled[PthreadLockChecker::CK_FuchsiaLockChecker] = true;
ImplChecker->CheckNames[PthreadLockChecker::CK_FuchsiaLockChecker] =
CM.getCurrentCheckerName();
+ // clang-format off
+ // Init
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"spin_lock_init"}),
+ EventKind::Init,
+ LibraryKind::Fuchsia
+ });
+
+ // Acquire
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"spin_lock"}),
+ EventKind::Acquire,
+ LibraryKind::Fuchsia,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"spin_lock_save"}, 3),
+ EventKind::Acquire,
+ LibraryKind::Fuchsia,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"sync_mutex_lock"}),
+ EventKind::Acquire,
+ LibraryKind::Fuchsia,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"sync_mutex_lock_with_waiter"}),
+ EventKind::Acquire,
+ LibraryKind::Fuchsia,
+ SemanticsKind::PthreadSemantics
+ });
+
+ // TryAcquire
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"spin_try_lock"}),
+ EventKind::TryAcquire,
+ LibraryKind::Fuchsia,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"sync_mutex_trylock"}),
+ EventKind::TryAcquire,
+ LibraryKind::Fuchsia,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"sync_mutex_timedlock"}, 2),
+ EventKind::TryAcquire,
+ LibraryKind::Fuchsia,
+ SemanticsKind::PthreadSemantics
+ });
+
+ // Release
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"spin_unlock"}),
+ EventKind::Release,
+ LibraryKind::Fuchsia
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"spin_unlock_restore"}, 3),
+ EventKind::Release,
+ LibraryKind::Fuchsia
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"sync_mutex_unlock"}),
+ EventKind::Release,
+ LibraryKind::Fuchsia
+ });
+ // clang-format on
}
bool ento::shouldRegisterFuchsiaLockChecker(const CheckerManager &CM) {
@@ -310,17 +486,50 @@ void ento::registerC11LockChecker(CheckerManager &CM) {
ImplChecker->CheckNames[PthreadLockChecker::CK_C11LockChecker] =
CM.getCurrentCheckerName();
- RegisterEvent(EventDescriptor{MakeFirstArgExtractor({"lck_mtx_lock"}),
- EventKind::Acquire, LibraryKind::C11,
- SemanticsKind::XNUSemantics});
- RegisterEvent(EventDescriptor{MakeFirstArgExtractor({"lck_mtx_try_lock"}),
- EventKind::TryAcquire, LibraryKind::C11,
- SemanticsKind::XNUSemantics});
- RegisterEvent(EventDescriptor{MakeFirstArgExtractor({"lck_mtx_unlock"}),
- EventKind::Release, LibraryKind::C11,
- SemanticsKind::XNUSemantics});
- RegisterEvent(EventDescriptor{MakeFirstArgExtractor({"lck_mtx_destroy"}, 2),
- EventKind::Destroy, LibraryKind::C11});
+ // clang-format off
+ // Init
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"mtx_init"}, 2),
+ EventKind::Init,
+ LibraryKind::C11
+ });
+
+ // Acquire
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"mtx_lock"}),
+ EventKind::Acquire,
+ LibraryKind::C11,
+ SemanticsKind::PthreadSemantics
+ });
+
+ // TryAcquire
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"mtx_trylock"}),
+ EventKind::TryAcquire,
+ LibraryKind::C11,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"mtx_timedlock"}, 2),
+ EventKind::TryAcquire,
+ LibraryKind::C11,
+ SemanticsKind::PthreadSemantics
+ });
+
+ // Release
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"mtx_unlock"}),
+ EventKind::Release,
+ LibraryKind::C11
+ });
+
+ // Destroy
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"mtx_destroy"}),
+ EventKind::Destroy,
+ LibraryKind::C11
+ });
+ // clang-format on
}
bool ento::shouldRegisterC11LockChecker(const CheckerManager &CM) {
>From 647836aab012028b9eb88981aeaa0ba727d0b6ba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Fri, 2 Aug 2024 15:37:00 +0200
Subject: [PATCH 17/30] make all tests pass in pthread.c
---
.../StaticAnalyzer/Checkers/MutexModeling.cpp | 422 ++++++++++--------
.../Checkers/PthreadLockChecker.cpp | 24 +-
2 files changed, 247 insertions(+), 199 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
index e553234e6b73f3..3b1f45aa4dd896 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
@@ -11,7 +11,6 @@
//===----------------------------------------------------------------------===//
#include "MutexModeling/MutexModelingAPI.h"
-#include "MutexModeling/MutexModelingDefs.h"
#include "MutexModeling/MutexModelingDomain.h"
#include "MutexModeling/MutexRegionExtractor.h"
@@ -59,10 +58,11 @@ ProgramStateRef resolvePossiblyDestroyedMutex(ProgramStateRef State,
State = State->set<LockStates>(LockReg, LockStateKind::Unlocked);
break;
}
- default: {
- State = State->set<LockStates>(LockReg, LockStateKind::Destroyed);
- }
+ default:
+ llvm_unreachable("Unknown lock state for a lock inside DestroyRetVal");
}
+ } else {
+ State = State->set<LockStates>(LockReg, LockStateKind::Destroyed);
}
// Removing the map entry (LockReg, sym) from DestroyRetVal as the lock
@@ -79,45 +79,6 @@ ProgramStateRef doResolvePossiblyDestroyedMutex(ProgramStateRef State,
return State;
}
-void updateCritSectionOnLock(const MutexRegionExtractor &LockDescriptor,
- const CallEvent &Call, CheckerContext &C) {
- const MemRegion *MutexRegion = getRegion(LockDescriptor, Call);
- if (!MutexRegion)
- return;
-
- const CritSectionMarker MarkToAdd{Call.getOriginExpr(), MutexRegion};
- ProgramStateRef StateWithLockEvent =
- C.getState()->add<CritSections>(MarkToAdd);
- C.addTransition(StateWithLockEvent, CreateMutexCritSectionNote(MarkToAdd, C));
-}
-
-void updateCriticalSectionOnUnlock(const EventDescriptor &UnlockDescriptor,
- const CallEvent &Call, CheckerContext &C) {
- const MemRegion *MutexRegion = getRegion(UnlockDescriptor.Trigger, Call);
- if (!MutexRegion)
- return;
-
- ProgramStateRef State = C.getState();
- const auto ActiveSections = State->get<CritSections>();
- const auto MostRecentLock =
- llvm::find_if(ActiveSections, [MutexRegion](auto &&Marker) {
- return Marker.MutexRegion == MutexRegion;
- });
- if (MostRecentLock == ActiveSections.end())
- return;
-
- // Build a new ImmutableList without this element.
- auto &Factory = State->get_context<CritSections>();
- 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);
- }
-
- C.addTransition(State->set<CritSections>(NewList));
-}
-
class MutexModeling : public Checker<check::PostCall, check::DeadSymbols,
check::RegionChanges> {
public:
@@ -133,81 +94,121 @@ class MutexModeling : public Checker<check::PostCall, check::DeadSymbols,
private:
mutable std::unique_ptr<BugType> BT_initlock;
- ProgramStateRef handleInit(const EventDescriptor &Event, const MemRegion *MTX,
+
+ // When handling events, NoteTags can be placed on ProgramPoints. This struct
+ // supports returning both the resulting ProgramState and a NoteTag.
+ struct ModelingResult {
+ ProgramStateRef State;
+ const NoteTag *Note = nullptr;
+ };
+
+ ModelingResult handleInit(const EventDescriptor &Event, const MemRegion *MTX,
+ const CallEvent &Call, ProgramStateRef State,
+ CheckerContext &C) const;
+ ModelingResult onSuccessfullAcquire(const MemRegion *MTX,
+ const CallEvent &Call,
+ ProgramStateRef State,
+ CheckerContext &C) const;
+ ModelingResult markCritSection(ModelingResult InputState,
+ const MemRegion *MTX, const CallEvent &Call,
+ CheckerContext &C) const;
+ ModelingResult handleAcquire(const EventDescriptor &Event,
+ const MemRegion *MTX, const CallEvent &Call,
+ ProgramStateRef State, CheckerContext &C) const;
+ ModelingResult handleTryAcquire(const EventDescriptor &Event,
+ const MemRegion *MTX, const CallEvent &Call,
+ ProgramStateRef State,
+ CheckerContext &C) const;
+ ModelingResult handleRelease(const EventDescriptor &Event,
+ const MemRegion *MTX, const CallEvent &Call,
+ ProgramStateRef State, CheckerContext &C) const;
+ ModelingResult handleDestroy(const EventDescriptor &Event,
+ const MemRegion *MTX, const CallEvent &Call,
+ ProgramStateRef State, CheckerContext &C) const;
+ ModelingResult handleEvent(const EventDescriptor &Event, const MemRegion *MTX,
const CallEvent &Call, ProgramStateRef State,
CheckerContext &C) const;
- ProgramStateRef handleAcquire(const EventDescriptor &Event,
- const MemRegion *MTX, const CallEvent &Call,
- ProgramStateRef State, CheckerContext &C) const;
- ProgramStateRef handleTryAcquire(const EventDescriptor &Event,
- const MemRegion *MTX, const CallEvent &Call,
- ProgramStateRef State,
- CheckerContext &C) const;
- ProgramStateRef handleRelease(const EventDescriptor &Event,
- const MemRegion *MTX, const CallEvent &Call,
- ProgramStateRef State, CheckerContext &C) const;
- ProgramStateRef handleDestroy(const EventDescriptor &Event,
- const MemRegion *MTX, const CallEvent &Call,
- ProgramStateRef State, CheckerContext &C) const;
- ProgramStateRef handleEvent(const EventDescriptor &Event,
- const MemRegion *MTX, const CallEvent &Call,
- ProgramStateRef State, CheckerContext &C) const;
};
} // namespace
-ProgramStateRef MutexModeling::handleInit(const EventDescriptor &Event,
- const MemRegion *MTX,
- const CallEvent &Call,
- ProgramStateRef State,
- CheckerContext &C) const {
- assert(MTX && "should only be called with a mutex region");
+MutexModeling::ModelingResult
+MutexModeling::handleInit(const EventDescriptor &Event, const MemRegion *MTX,
+ const CallEvent &Call, ProgramStateRef State,
+ CheckerContext &C) const {
+ ModelingResult Result{State->set<LockStates>(MTX, LockStateKind::Unlocked)};
const LockStateKind *LockState = State->get<LockStates>(MTX);
if (!LockState)
- return State->set<LockStates>(MTX, LockStateKind::Unlocked);
+ return Result;
switch (*LockState) {
case (LockStateKind::Destroyed): {
- return State->set<LockStates>(MTX, LockStateKind::Unlocked);
+ Result.State = State->set<LockStates>(MTX, LockStateKind::Unlocked);
+ break;
}
case (LockStateKind::Locked): {
- return State->set<LockStates>(MTX,
- LockStateKind::Error_DoubleInitWhileLocked);
+ Result.State =
+ State->set<LockStates>(MTX, LockStateKind::Error_DoubleInitWhileLocked);
+ break;
}
default: {
- return State->set<LockStates>(MTX, LockStateKind::Error_DoubleInit);
+ Result.State = State->set<LockStates>(MTX, LockStateKind::Error_DoubleInit);
+ break;
}
}
+
+ return Result;
}
-static ProgramStateRef doAcquireCommonLogic(ProgramStateRef State,
- const MemRegion *MTX) {
+MutexModeling::ModelingResult
+MutexModeling::markCritSection(MutexModeling::ModelingResult InputState,
+ const MemRegion *MTX, const CallEvent &Call,
+ CheckerContext &C) const {
+ const CritSectionMarker MarkToAdd{Call.getOriginExpr(), MTX};
+ return {InputState.State->add<CritSections>(MarkToAdd),
+ CreateMutexCritSectionNote(MarkToAdd, C)};
+}
+
+MutexModeling::ModelingResult
+MutexModeling::onSuccessfullAcquire(const MemRegion *MTX, const CallEvent &Call,
+ ProgramStateRef State,
+ CheckerContext &C) const {
+ ModelingResult Result{State};
+
const LockStateKind *LockState = State->get<LockStates>(MTX);
- if (!LockState)
- return State->set<LockStates>(MTX, LockStateKind::Locked);
+ if (!LockState) {
+ Result.State = Result.State->set<LockStates>(MTX, LockStateKind::Locked);
+ Result = markCritSection(Result, MTX, Call, C);
+ return Result;
+ }
switch (*LockState) {
case LockStateKind::Unlocked:
- return State->set<LockStates>(MTX, LockStateKind::Locked);
+ Result.State = Result.State->set<LockStates>(MTX, LockStateKind::Locked);
+ Result = markCritSection(Result, MTX, Call, C);
+ break;
case LockStateKind::Locked:
- return State->set<LockStates>(MTX, LockStateKind::Error_DoubleLock);
+ Result.State =
+ Result.State->set<LockStates>(MTX, LockStateKind::Error_DoubleLock);
+ break;
case LockStateKind::Destroyed:
- return State->set<LockStates>(MTX, LockStateKind::Error_LockDestroyed);
+ Result.State =
+ Result.State->set<LockStates>(MTX, LockStateKind::Error_LockDestroyed);
+ break;
default:
- return State;
+ break;
}
-}
-ProgramStateRef MutexModeling::handleAcquire(const EventDescriptor &Event,
- const MemRegion *MTX,
- const CallEvent &Call,
- ProgramStateRef State,
- CheckerContext &C) const {
+ return Result;
+}
- State = doAcquireCommonLogic(State, MTX);
+MutexModeling::ModelingResult
+MutexModeling::handleAcquire(const EventDescriptor &Event, const MemRegion *MTX,
+ const CallEvent &Call, ProgramStateRef State,
+ CheckerContext &C) const {
switch (Event.Semantics) {
case SemanticsKind::PthreadSemantics: {
@@ -219,6 +220,8 @@ ProgramStateRef MutexModeling::handleAcquire(const EventDescriptor &Event,
State = State->assume(*DefinedRetVal, false);
assert(State);
}
+ // We might want to handle the case when the mutex lock function was
+ // inlined and returned an Unknown or Undefined value.
break;
}
case SemanticsKind::XNUSemantics:
@@ -229,17 +232,14 @@ ProgramStateRef MutexModeling::handleAcquire(const EventDescriptor &Event,
"Acquire events should have either Pthread or XNU semantics");
}
- return State;
+ return onSuccessfullAcquire(MTX, Call, State, C);
}
-ProgramStateRef MutexModeling::handleTryAcquire(const EventDescriptor &Event,
- const MemRegion *MTX,
- const CallEvent &Call,
- ProgramStateRef State,
- CheckerContext &C) const {
-
- State = doAcquireCommonLogic(State, MTX);
+MutexModeling::ModelingResult MutexModeling::handleTryAcquire(
+ const EventDescriptor &Event, const MemRegion *MTX, const CallEvent &Call,
+ ProgramStateRef State, CheckerContext &C) const {
+ ModelingResult Result{State};
// Bifurcate the state, and allow a mode where the lock acquisition fails.
ProgramStateRef LockSucc;
SVal RetVal = Call.getReturnValue();
@@ -253,134 +253,178 @@ ProgramStateRef MutexModeling::handleTryAcquire(const EventDescriptor &Event,
std::tie(LockSucc, LockFail) = State->assume(*DefinedRetVal);
break;
default:
- llvm_unreachable("Unknown tryLock locking semantics");
+ llvm_unreachable("Unknown TryLock locking semantics");
}
+ assert(LockFail && LockSucc && "Bifurcation point in ExplodedGraph");
// This is the bifurcation point in the ExplodedGraph, we do not need to
- // pass the new ExplodedGraph node because we do not plan on building this
- // failed case part forward in this checker.
+ // return the new ExplodedGraph node because we do not plan on building this
+ // lock-failed case path in this checker.
C.addTransition(LockFail);
-
- // Pass the state where the locking succeeded onwards.
- State = LockSucc;
- // We might want to handle the case when the mutex lock function was
- // inlined and returned an Unknown or Undefined value.
}
- return State;
-}
-ProgramStateRef MutexModeling::handleRelease(const EventDescriptor &Event,
- const MemRegion *MTX,
- const CallEvent &Call,
- ProgramStateRef State,
- CheckerContext &C) const {
+ // Pass the state where the locking succeeded onwards.
+ Result = onSuccessfullAcquire(MTX, Call, LockSucc, C);
+ return Result;
+}
- const LockStateKind *LockState = State->get<LockStates>(MTX);
+MutexModeling::ModelingResult
+MutexModeling::handleRelease(const EventDescriptor &Event, const MemRegion *MTX,
+ const CallEvent &Call, ProgramStateRef State,
+ CheckerContext &C) const {
- // FIXME: to be more conservative we shoud maybe just assume, that the lock
- // is was in a locked state? This would assume that before we start modeling
- // the mutex, the state was correct.
- // return State->set<LockStates>(MTX, LockStateKind::Unlocked);
- if (!LockState)
- return State->set<LockStates>(MTX, LockStateKind::Error_DoubleUnlock);
+ ModelingResult Result{State};
- if (*LockState == LockStateKind::Unlocked)
- return State->set<LockStates>(MTX, LockStateKind::Error_DoubleUnlock);
+ const LockStateKind *LockState = Result.State->get<LockStates>(MTX);
- if (*LockState == LockStateKind::Destroyed)
- return State->set<LockStates>(MTX, LockStateKind::Error_UnlockDestroyed);
+ if (!LockState) {
+ Result.State = Result.State->set<LockStates>(MTX, LockStateKind::Unlocked);
+ return Result;
+ }
- // Check if the currently released mutex is also the most recently locked
- // one. If not, report a lock reversal bug.
- //
- // NOTE: MutexEvents stores events in reverse order as the ImmutableList data
- // structure grows towards its Head element.
- //
- // Every time a Release event is met in the reverse order of Mutex events it
- // is pushed to a stack. Every time an Acquire-like event is met, if the top
- // of the stack matches the Acquire event's mutex region, it is popped
- // (signifying that the order of lock/unlocks was matching), but if a
- // non-matching item is found, we are sure that there is a reversal.
- //
- // FIXME: A stack of mutexes could be used instead of computing the reverse
- // critical sections every time, use the critical section modeling aggregated
- // data for this check, and rewrite the comment paragraph above.
- const auto &Events = State->get<MutexEvents>();
- llvm::SmallVector<const EventMarker *, 4> InverseLockStack;
- for (const auto &Event : Events) {
- if (Event.Kind == EventKind::Release) {
- InverseLockStack.push_back(&Event);
- } else if (Event.Kind == EventKind::Acquire ||
- Event.Kind == EventKind::TryAcquire) {
- if (InverseLockStack.empty()) {
- continue;
- }
- if (InverseLockStack.back()->MutexRegion == Event.MutexRegion) {
- InverseLockStack.pop_back();
- }
- }
+ if (*LockState == LockStateKind::Unlocked) {
+ Result.State =
+ State->set<LockStates>(MTX, LockStateKind::Error_DoubleUnlock);
+ return Result;
}
- if (!InverseLockStack.empty()) {
- return State->set<LockStates>(MTX, LockStateKind::Error_LockReversal);
+ if (*LockState == LockStateKind::Destroyed) {
+ Result.State =
+ State->set<LockStates>(MTX, LockStateKind::Error_UnlockDestroyed);
+ return Result;
}
- return State->set<LockStates>(MTX, LockStateKind::Unlocked);
-}
+ const auto ActiveSections = State->get<CritSections>();
+ const auto MostRecentLockForMTX =
+ llvm::find_if(ActiveSections,
+ [MTX](auto &&Marker) { return Marker.MutexRegion == MTX; });
+
+ // In a non-empty critical section list, if the most recent lock is for
+ // another mutex, then ther is a lock reversal.
+ bool IsLockInversion = MostRecentLockForMTX != ActiveSections.begin();
+
+ // NOTE: IsLockInversion -> !ActiveSections.isEmpty()
+ assert((!IsLockInversion || !ActiveSections.isEmpty()) &&
+ "The existance of an inversion implies that the list is not empty");
+
+ if (IsLockInversion) {
+ Result.State =
+ State->set<LockStates>(MTX, LockStateKind::Error_LockReversal);
+ // Build a new ImmutableList without this element.
+ auto &Factory = Result.State->get_context<CritSections>();
+ llvm::ImmutableList<CritSectionMarker> WithoutThisLock =
+ Factory.getEmptyList();
+ for (auto It = ActiveSections.begin(), End = ActiveSections.end();
+ It != End; ++It) {
+ if (It != MostRecentLockForMTX)
+ WithoutThisLock = Factory.add(*It, WithoutThisLock);
+ }
+ Result.State = Result.State->set<CritSections>(WithoutThisLock);
+ return Result;
+ }
-ProgramStateRef MutexModeling::handleDestroy(const EventDescriptor &Event,
- const MemRegion *MTX,
- const CallEvent &Call,
- ProgramStateRef State,
- CheckerContext &C) const {
+ Result.State = Result.State->set<LockStates>(MTX, LockStateKind::Unlocked);
+ // If there is no lock inversion, we can just remove the last crit section.
+ // NOTE: It should be safe to call getTail on an empty list
+ Result.State = Result.State->set<CritSections>(ActiveSections.getTail());
- // TODO: Check if calling resolvePossiblyDestroyedMutex is necessary.
- const SymbolRef *LockReturnSym = State->get<DestroyedRetVals>(MTX);
- if (LockReturnSym)
- State = resolvePossiblyDestroyedMutex(State, MTX, LockReturnSym);
+ return Result;
+}
- const LockStateKind *LockState = State->get<LockStates>(MTX);
+MutexModeling::ModelingResult
+MutexModeling::handleDestroy(const EventDescriptor &Event, const MemRegion *MTX,
+ const CallEvent &Call, ProgramStateRef State,
+ CheckerContext &C) const {
- if (!LockState || *LockState == LockStateKind::Unlocked) {
- if (Event.Semantics != SemanticsKind::PthreadSemantics) {
- return State->set<LockStates>(MTX, LockStateKind::Destroyed);
+ // Original implementation:
+ //
+ // const LockState *LState = State->get<LockMap>(LockR);
+ // // Checking the return value of the destroy method only in the case of
+ // // PthreadSemantics
+ // if (Semantics == PthreadSemantics) {
+ // if (!LState || LState->isUnlocked()) {
+ // SymbolRef sym = Call.getReturnValue().getAsSymbol();
+ // if (!sym) {
+ // State = State->remove<LockMap>(LockR);
+ // C.addTransition(State);
+ // return;
+ // }
+ // State = State->set<DestroyRetVal>(LockR, sym);
+ // if (LState && LState->isUnlocked())
+ // State = State->set<LockMap>(
+ // LockR, LockState::getUnlockedAndPossiblyDestroyed());
+ // else
+ // State = State->set<LockMap>(
+ // LockR, LockState::getUntouchedAndPossiblyDestroyed());
+ // C.addTransition(State);
+ // return;
+ // }
+ // } else {
+ // if (!LState || LState->isUnlocked()) {
+ // State = State->set<LockMap>(LockR, LockState::getDestroyed());
+ // C.addTransition(State);
+ // return;
+ // }
+ // }
+
+ // StringRef Message = LState->isLocked()
+ // ? "This lock is still locked"
+ // : "This lock has already been destroyed";
+
+ // reportBug(C, BT_destroylock, MtxExpr, CheckKind, Message);
+
+ // New implementation:
+
+ ModelingResult Result{State};
+
+ const LockStateKind *LockState = Result.State->get<LockStates>(MTX);
+
+ if (Event.Semantics == SemanticsKind::PthreadSemantics) {
+ if (!LockState || *LockState == LockStateKind::Unlocked) {
+ SymbolRef Sym = Call.getReturnValue().getAsSymbol();
+ if (!Sym) {
+ Result.State = Result.State->remove<LockStates>(MTX);
+ return Result;
+ }
+ Result.State = Result.State->set<DestroyedRetVals>(MTX, Sym);
+ Result.State = Result.State->set<LockStates>(
+ MTX, LockState && *LockState == LockStateKind::Unlocked
+ ? LockStateKind::UnlockedAndPossiblyDestroyed
+ : LockStateKind::UntouchedAndPossiblyDestroyed);
+ return Result;
}
-
- // In PthreadSemantics we reason about the return value of the destroy
- // calls.
- SymbolRef Sym = Call.getReturnValue().getAsSymbol();
- if (!Sym) {
- return State->remove<LockStates>(MTX);
+ } else {
+ if (!LockState || *LockState == LockStateKind::Unlocked) {
+ Result.State =
+ Result.State->set<LockStates>(MTX, LockStateKind::Destroyed);
+ return Result;
}
-
- State = State->set<DestroyedRetVals>(MTX, Sym);
- return State->set<LockStates>(
- MTX, LockState && *LockState == LockStateKind::Unlocked
- ? LockStateKind::UnlockedAndPossiblyDestroyed
- : LockStateKind::UntouchedAndPossiblyDestroyed);
}
if (*LockState == LockStateKind::Locked) {
- return State->set<LockStates>(MTX, LockStateKind::Error_DestroyLocked);
+ Result.State =
+ Result.State->set<LockStates>(MTX, LockStateKind::Error_DestroyLocked);
+ return Result;
}
if (*LockState == LockStateKind::Destroyed) {
- return State->set<LockStates>(MTX, LockStateKind::Error_DoubleDestroy);
+ Result.State =
+ Result.State->set<LockStates>(MTX, LockStateKind::Error_DoubleDestroy);
+ return Result;
}
assert(LockState && *LockState != LockStateKind::Unlocked &&
*LockState != LockStateKind::Locked &&
*LockState != LockStateKind::Destroyed &&
- "Only error states should remain if we get here");
+ "We can only get here if we came from an error-state to begin with");
- return State;
+ return Result;
}
-ProgramStateRef MutexModeling::handleEvent(const EventDescriptor &Event,
- const MemRegion *MTX,
- const CallEvent &Call,
- ProgramStateRef State,
- CheckerContext &C) const {
+MutexModeling::ModelingResult
+MutexModeling::handleEvent(const EventDescriptor &Event, const MemRegion *MTX,
+ const CallEvent &Call, ProgramStateRef State,
+ CheckerContext &C) const {
assert(MTX && "should only be called with a mutex region");
State = State->add<MutexEvents>(
@@ -417,8 +461,8 @@ void MutexModeling::checkPostCall(const CallEvent &Call,
if (!MTX)
continue;
State = doResolvePossiblyDestroyedMutex(State, MTX);
- State = handleEvent(Event, MTX, Call, State, C);
- C.addTransition(State);
+ ModelingResult Result = handleEvent(Event, MTX, Call, State, C);
+ C.addTransition(Result.State, Result.Note);
}
}
}
diff --git a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
index 903df9a7ca86b4..f857a1246712b6 100644
--- a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
@@ -170,7 +170,8 @@ void PthreadLockChecker::checkReleaseEvent(const EventMarker &LastEvent,
"This lock has already been destroyed");
} else if (*LockState == LockStateKind::Error_LockReversal) {
reportBug(C, BT_lor, LastEvent.EventExpr, detectCheckerKind(LastEvent),
- "This was not the most recently acquired lock");
+ "This was not the most recently acquired lock. Possible lock "
+ "order reversal");
}
}
@@ -319,31 +320,31 @@ void ento::registerPthreadLockChecker(CheckerManager &CM) {
});
RegisterEvent(EventDescriptor{
MakeFirstArgExtractor({"pthread_rwlock_tryrdlock"}),
- EventKind::Acquire,
+ EventKind::TryAcquire,
LibraryKind::Pthread,
SemanticsKind::PthreadSemantics
});
RegisterEvent(EventDescriptor{
MakeFirstArgExtractor({"pthread_rwlock_trywrlock"}),
- EventKind::Acquire,
+ EventKind::TryAcquire,
LibraryKind::Pthread,
SemanticsKind::PthreadSemantics
});
RegisterEvent(EventDescriptor{
MakeFirstArgExtractor({"lck_mtx_try_lock"}),
- EventKind::Acquire,
+ EventKind::TryAcquire,
LibraryKind::Pthread,
SemanticsKind::XNUSemantics
});
RegisterEvent(EventDescriptor{
MakeFirstArgExtractor({"lck_rw_try_lock_exclusive"}),
- EventKind::Acquire,
+ EventKind::TryAcquire,
LibraryKind::Pthread,
SemanticsKind::XNUSemantics
});
RegisterEvent(EventDescriptor{
MakeFirstArgExtractor({"lck_rw_try_lock_shared"}),
- EventKind::Acquire,
+ EventKind::TryAcquire,
LibraryKind::Pthread,
SemanticsKind::XNUSemantics
});
@@ -384,12 +385,14 @@ void ento::registerPthreadLockChecker(CheckerManager &CM) {
RegisterEvent(EventDescriptor{
MakeFirstArgExtractor({"pthread_mutex_destroy"}),
EventKind::Destroy,
- LibraryKind::Pthread
+ LibraryKind::Pthread,
+ SemanticsKind::PthreadSemantics
});
RegisterEvent(EventDescriptor{
MakeFirstArgExtractor({"lck_mtx_destroy"}, 2),
EventKind::Destroy,
- LibraryKind::Pthread
+ LibraryKind::Pthread,
+ SemanticsKind::XNUSemantics
});
// clang-format on
}
@@ -439,7 +442,7 @@ void ento::registerFuchsiaLockChecker(CheckerManager &CM) {
// TryAcquire
RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"spin_try_lock"}),
+ MakeFirstArgExtractor({"spin_trylock"}),
EventKind::TryAcquire,
LibraryKind::Fuchsia,
SemanticsKind::PthreadSemantics
@@ -527,7 +530,8 @@ void ento::registerC11LockChecker(CheckerManager &CM) {
RegisterEvent(EventDescriptor{
MakeFirstArgExtractor({"mtx_destroy"}),
EventKind::Destroy,
- LibraryKind::C11
+ LibraryKind::C11,
+ SemanticsKind::PthreadSemantics
});
// clang-format on
}
>From 03261b00aad39d888a8e9f9847453bc27e00918a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Fri, 2 Aug 2024 17:07:25 +0200
Subject: [PATCH 18/30] move modeling to mutexmodeling, remove default cases
where warnings were given
---
.../clang/StaticAnalyzer/Checkers/Checkers.td | 10 +-
.../StaticAnalyzer/Checkers/MutexModeling.cpp | 292 +++++++++++++++---
.../Checkers/MutexModeling/MutexModelingAPI.h | 4 -
.../Checkers/PthreadLockChecker.cpp | 252 ---------------
.../test/Analysis/analyzer-enabled-checkers.c | 2 +-
...c-library-functions-arg-enabled-checkers.c | 2 +-
6 files changed, 257 insertions(+), 305 deletions(-)
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index 155cde7cb2106e..3bbf794645c548 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -220,6 +220,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">,
@@ -261,11 +266,6 @@ def NonnullGlobalConstantsChecker: Checker<"NonnilStringConstants">,
let ParentPackage = CoreAlpha in {
-def MutexModeling : Checker<"MutexModeling">,
- HelpText<"Model mutexes lock and unlock events">,
- Documentation<NotDocumented>,
- Hidden;
-
def BoolAssignmentChecker : Checker<"BoolAssignment">,
HelpText<"Warn about assigning non-{0,1} values to Boolean variables">,
Documentation<HasDocumentation>;
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
index 3b1f45aa4dd896..f9653ad790b873 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
@@ -335,46 +335,6 @@ MutexModeling::ModelingResult
MutexModeling::handleDestroy(const EventDescriptor &Event, const MemRegion *MTX,
const CallEvent &Call, ProgramStateRef State,
CheckerContext &C) const {
-
- // Original implementation:
- //
- // const LockState *LState = State->get<LockMap>(LockR);
- // // Checking the return value of the destroy method only in the case of
- // // PthreadSemantics
- // if (Semantics == PthreadSemantics) {
- // if (!LState || LState->isUnlocked()) {
- // SymbolRef sym = Call.getReturnValue().getAsSymbol();
- // if (!sym) {
- // State = State->remove<LockMap>(LockR);
- // C.addTransition(State);
- // return;
- // }
- // State = State->set<DestroyRetVal>(LockR, sym);
- // if (LState && LState->isUnlocked())
- // State = State->set<LockMap>(
- // LockR, LockState::getUnlockedAndPossiblyDestroyed());
- // else
- // State = State->set<LockMap>(
- // LockR, LockState::getUntouchedAndPossiblyDestroyed());
- // C.addTransition(State);
- // return;
- // }
- // } else {
- // if (!LState || LState->isUnlocked()) {
- // State = State->set<LockMap>(LockR, LockState::getDestroyed());
- // C.addTransition(State);
- // return;
- // }
- // }
-
- // StringRef Message = LState->isLocked()
- // ? "This lock is still locked"
- // : "This lock has already been destroyed";
-
- // reportBug(C, BT_destroylock, MtxExpr, CheckKind, Message);
-
- // New implementation:
-
ModelingResult Result{State};
const LockStateKind *LockState = Result.State->get<LockStates>(MTX);
@@ -442,8 +402,6 @@ MutexModeling::handleEvent(const EventDescriptor &Event, const MemRegion *MTX,
return handleRelease(Event, MTX, Call, State, C);
case EventKind::Destroy:
return handleDestroy(Event, MTX, Call, State, C);
- default:
- llvm_unreachable("Unhandled event kind!");
}
}
@@ -537,6 +495,256 @@ namespace ento {
// Checker registration
void registerMutexModeling(CheckerManager &CM) {
CM.registerChecker<MutexModeling>();
+
+ // clang-format off
+ // Pthread-related events
+ // Init
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor(
+ {"pthread_mutex_init"}, 2),
+ EventKind::Init,
+ LibraryKind::Pthread
+ });
+
+ // Acquire
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor(
+ {"pthread_mutex_lock"}),
+ EventKind::Acquire,
+ LibraryKind::Pthread,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"pthread_rwlock_rdlock"}),
+ EventKind::Acquire,
+ LibraryKind::Pthread,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"pthread_rwlock_wrlock"}),
+ EventKind::Acquire,
+ LibraryKind::Pthread,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_mtx_lock"}),
+ EventKind::Acquire,
+ LibraryKind::Pthread,
+ SemanticsKind::XNUSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_rw_lock_exclusive"}),
+ EventKind::Acquire,
+ LibraryKind::Pthread,
+ SemanticsKind::XNUSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_rw_lock_shared"}),
+ EventKind::Acquire,
+ LibraryKind::Pthread,
+ SemanticsKind::XNUSemantics
+ });
+
+ // TryAcquire
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"pthread_mutex_trylock"}),
+ EventKind::TryAcquire,
+ LibraryKind::Pthread,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"pthread_rwlock_tryrdlock"}),
+ EventKind::TryAcquire,
+ LibraryKind::Pthread,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"pthread_rwlock_trywrlock"}),
+ EventKind::TryAcquire,
+ LibraryKind::Pthread,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_mtx_try_lock"}),
+ EventKind::TryAcquire,
+ LibraryKind::Pthread,
+ SemanticsKind::XNUSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_rw_try_lock_exclusive"}),
+ EventKind::TryAcquire,
+ LibraryKind::Pthread,
+ SemanticsKind::XNUSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_rw_try_lock_shared"}),
+ EventKind::TryAcquire,
+ LibraryKind::Pthread,
+ SemanticsKind::XNUSemantics
+ });
+
+ // Release
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"pthread_mutex_unlock"}),
+ EventKind::Release,
+ LibraryKind::Pthread
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"pthread_rwlock_unlock"}),
+ EventKind::Release,
+ LibraryKind::Pthread
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_mtx_unlock"}),
+ EventKind::Release,
+ LibraryKind::Pthread
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_rw_unlock_exclusive"}),
+ EventKind::Release,
+ LibraryKind::Pthread
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_rw_unlock_shared"}),
+ EventKind::Release,
+ LibraryKind::Pthread
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_rw_done"}),
+ EventKind::Release,
+ LibraryKind::Pthread
+ });
+
+ // Destroy
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"pthread_mutex_destroy"}),
+ EventKind::Destroy,
+ LibraryKind::Pthread,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"lck_mtx_destroy"}, 2),
+ EventKind::Destroy,
+ LibraryKind::Pthread,
+ SemanticsKind::XNUSemantics
+ });
+
+ // Fuchsia-related events
+ // Init
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"spin_lock_init"}),
+ EventKind::Init,
+ LibraryKind::Fuchsia
+ });
+
+ // Acquire
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"spin_lock"}),
+ EventKind::Acquire,
+ LibraryKind::Fuchsia,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"spin_lock_save"}, 3),
+ EventKind::Acquire,
+ LibraryKind::Fuchsia,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"sync_mutex_lock"}),
+ EventKind::Acquire,
+ LibraryKind::Fuchsia,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"sync_mutex_lock_with_waiter"}),
+ EventKind::Acquire,
+ LibraryKind::Fuchsia,
+ SemanticsKind::PthreadSemantics
+ });
+
+ // TryAcquire
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"spin_trylock"}),
+ EventKind::TryAcquire,
+ LibraryKind::Fuchsia,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"sync_mutex_trylock"}),
+ EventKind::TryAcquire,
+ LibraryKind::Fuchsia,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"sync_mutex_timedlock"}, 2),
+ EventKind::TryAcquire,
+ LibraryKind::Fuchsia,
+ SemanticsKind::PthreadSemantics
+ });
+
+ // Release
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"spin_unlock"}),
+ EventKind::Release,
+ LibraryKind::Fuchsia
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"spin_unlock_restore"}, 3),
+ EventKind::Release,
+ LibraryKind::Fuchsia
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"sync_mutex_unlock"}),
+ EventKind::Release,
+ LibraryKind::Fuchsia
+ });
+
+ // C11-related events
+ // Init
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"mtx_init"}, 2),
+ EventKind::Init,
+ LibraryKind::C11
+ });
+
+ // Acquire
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"mtx_lock"}),
+ EventKind::Acquire,
+ LibraryKind::C11,
+ SemanticsKind::PthreadSemantics
+ });
+
+ // TryAcquire
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"mtx_trylock"}),
+ EventKind::TryAcquire,
+ LibraryKind::C11,
+ SemanticsKind::PthreadSemantics
+ });
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"mtx_timedlock"}, 2),
+ EventKind::TryAcquire,
+ LibraryKind::C11,
+ SemanticsKind::PthreadSemantics
+ });
+
+ // Release
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"mtx_unlock"}),
+ EventKind::Release,
+ LibraryKind::C11
+ });
+
+ // Destroy
+ RegisterEvent(EventDescriptor{
+ MakeFirstArgExtractor({"mtx_destroy"}),
+ EventKind::Destroy,
+ LibraryKind::C11,
+ SemanticsKind::PthreadSemantics
+ });
+ // clang-format on
}
bool shouldRegisterMutexModeling(const CheckerManager &) { return true; }
} // namespace ento
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingAPI.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingAPI.h
index 2e53b71c932eb4..f420920634a87c 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingAPI.h
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingAPI.h
@@ -141,8 +141,6 @@ inline void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
case (EventKind::Destroy):
Out << "Destroy";
break;
- default:
- llvm_unreachable("Unknown event kind");
}
Out << NL;
Out << Sep << "Semantics: ";
@@ -156,8 +154,6 @@ inline void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
case (SemanticsKind::XNUSemantics):
Out << "XNUSemantics";
break;
- default:
- llvm_unreachable("Unknown semantics");
}
Out << NL;
Out << Sep << "Library: ";
diff --git a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
index f857a1246712b6..f1430dd3e7aea8 100644
--- a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
@@ -225,8 +225,6 @@ void PthreadLockChecker::checkPostCall(const CallEvent &Call,
case EventKind::Destroy:
checkDestroyEvent(LastEvent, C);
break;
- default:
- llvm_unreachable("Unknown event kind");
}
}
@@ -262,139 +260,6 @@ void ento::registerPthreadLockChecker(CheckerManager &CM) {
ImplChecker->ChecksEnabled[PthreadLockChecker::CK_PthreadLockChecker] = true;
ImplChecker->CheckNames[PthreadLockChecker::CK_PthreadLockChecker] =
CM.getCurrentCheckerName();
-
- // clang-format off
- // Init
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor(
- {"pthread_mutex_init"}, 2),
- EventKind::Init,
- LibraryKind::Pthread
- });
-
- // Acquire
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor(
- {"pthread_mutex_lock"}),
- EventKind::Acquire,
- LibraryKind::Pthread,
- SemanticsKind::PthreadSemantics
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"pthread_rwlock_rdlock"}),
- EventKind::Acquire,
- LibraryKind::Pthread,
- SemanticsKind::PthreadSemantics
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"pthread_rwlock_wrlock"}),
- EventKind::Acquire,
- LibraryKind::Pthread,
- SemanticsKind::PthreadSemantics
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"lck_mtx_lock"}),
- EventKind::Acquire,
- LibraryKind::Pthread,
- SemanticsKind::XNUSemantics
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"lck_rw_lock_exclusive"}),
- EventKind::Acquire,
- LibraryKind::Pthread,
- SemanticsKind::XNUSemantics
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"lck_rw_lock_shared"}),
- EventKind::Acquire,
- LibraryKind::Pthread,
- SemanticsKind::XNUSemantics
- });
-
- // TryAcquire
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"pthread_mutex_trylock"}),
- EventKind::TryAcquire,
- LibraryKind::Pthread,
- SemanticsKind::PthreadSemantics
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"pthread_rwlock_tryrdlock"}),
- EventKind::TryAcquire,
- LibraryKind::Pthread,
- SemanticsKind::PthreadSemantics
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"pthread_rwlock_trywrlock"}),
- EventKind::TryAcquire,
- LibraryKind::Pthread,
- SemanticsKind::PthreadSemantics
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"lck_mtx_try_lock"}),
- EventKind::TryAcquire,
- LibraryKind::Pthread,
- SemanticsKind::XNUSemantics
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"lck_rw_try_lock_exclusive"}),
- EventKind::TryAcquire,
- LibraryKind::Pthread,
- SemanticsKind::XNUSemantics
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"lck_rw_try_lock_shared"}),
- EventKind::TryAcquire,
- LibraryKind::Pthread,
- SemanticsKind::XNUSemantics
- });
-
- // Release
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"pthread_mutex_unlock"}),
- EventKind::Release,
- LibraryKind::Pthread
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"pthread_rwlock_unlock"}),
- EventKind::Release,
- LibraryKind::Pthread
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"lck_mtx_unlock"}),
- EventKind::Release,
- LibraryKind::Pthread
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"lck_rw_unlock_exclusive"}),
- EventKind::Release,
- LibraryKind::Pthread
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"lck_rw_unlock_shared"}),
- EventKind::Release,
- LibraryKind::Pthread
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"lck_rw_done"}),
- EventKind::Release,
- LibraryKind::Pthread
- });
-
- // Destroy
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"pthread_mutex_destroy"}),
- EventKind::Destroy,
- LibraryKind::Pthread,
- SemanticsKind::PthreadSemantics
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"lck_mtx_destroy"}, 2),
- EventKind::Destroy,
- LibraryKind::Pthread,
- SemanticsKind::XNUSemantics
- });
- // clang-format on
}
bool ento::shouldRegisterPthreadLockChecker(const CheckerManager &CM) {
@@ -406,77 +271,6 @@ void ento::registerFuchsiaLockChecker(CheckerManager &CM) {
ImplChecker->ChecksEnabled[PthreadLockChecker::CK_FuchsiaLockChecker] = true;
ImplChecker->CheckNames[PthreadLockChecker::CK_FuchsiaLockChecker] =
CM.getCurrentCheckerName();
- // clang-format off
- // Init
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"spin_lock_init"}),
- EventKind::Init,
- LibraryKind::Fuchsia
- });
-
- // Acquire
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"spin_lock"}),
- EventKind::Acquire,
- LibraryKind::Fuchsia,
- SemanticsKind::PthreadSemantics
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"spin_lock_save"}, 3),
- EventKind::Acquire,
- LibraryKind::Fuchsia,
- SemanticsKind::PthreadSemantics
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"sync_mutex_lock"}),
- EventKind::Acquire,
- LibraryKind::Fuchsia,
- SemanticsKind::PthreadSemantics
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"sync_mutex_lock_with_waiter"}),
- EventKind::Acquire,
- LibraryKind::Fuchsia,
- SemanticsKind::PthreadSemantics
- });
-
- // TryAcquire
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"spin_trylock"}),
- EventKind::TryAcquire,
- LibraryKind::Fuchsia,
- SemanticsKind::PthreadSemantics
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"sync_mutex_trylock"}),
- EventKind::TryAcquire,
- LibraryKind::Fuchsia,
- SemanticsKind::PthreadSemantics
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"sync_mutex_timedlock"}, 2),
- EventKind::TryAcquire,
- LibraryKind::Fuchsia,
- SemanticsKind::PthreadSemantics
- });
-
- // Release
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"spin_unlock"}),
- EventKind::Release,
- LibraryKind::Fuchsia
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"spin_unlock_restore"}, 3),
- EventKind::Release,
- LibraryKind::Fuchsia
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"sync_mutex_unlock"}),
- EventKind::Release,
- LibraryKind::Fuchsia
- });
- // clang-format on
}
bool ento::shouldRegisterFuchsiaLockChecker(const CheckerManager &CM) {
@@ -488,52 +282,6 @@ void ento::registerC11LockChecker(CheckerManager &CM) {
ImplChecker->ChecksEnabled[PthreadLockChecker::CK_C11LockChecker] = true;
ImplChecker->CheckNames[PthreadLockChecker::CK_C11LockChecker] =
CM.getCurrentCheckerName();
-
- // clang-format off
- // Init
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"mtx_init"}, 2),
- EventKind::Init,
- LibraryKind::C11
- });
-
- // Acquire
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"mtx_lock"}),
- EventKind::Acquire,
- LibraryKind::C11,
- SemanticsKind::PthreadSemantics
- });
-
- // TryAcquire
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"mtx_trylock"}),
- EventKind::TryAcquire,
- LibraryKind::C11,
- SemanticsKind::PthreadSemantics
- });
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"mtx_timedlock"}, 2),
- EventKind::TryAcquire,
- LibraryKind::C11,
- SemanticsKind::PthreadSemantics
- });
-
- // Release
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"mtx_unlock"}),
- EventKind::Release,
- LibraryKind::C11
- });
-
- // Destroy
- RegisterEvent(EventDescriptor{
- MakeFirstArgExtractor({"mtx_destroy"}),
- EventKind::Destroy,
- LibraryKind::C11,
- SemanticsKind::PthreadSemantics
- });
- // clang-format on
}
bool ento::shouldRegisterC11LockChecker(const CheckerManager &CM) {
diff --git a/clang/test/Analysis/analyzer-enabled-checkers.c b/clang/test/Analysis/analyzer-enabled-checkers.c
index 5fe24318a807b3..d02e88def01457 100644
--- a/clang/test/Analysis/analyzer-enabled-checkers.c
+++ b/clang/test/Analysis/analyzer-enabled-checkers.c
@@ -16,6 +16,7 @@
// CHECK-NEXT: core.CallAndMessage
// CHECK-NEXT: core.DivideZero
// CHECK-NEXT: core.DynamicTypePropagation
+// CHECK-NEXT: core.MutexModeling
// CHECK-NEXT: core.NonNullParamChecker
// CHECK-NEXT: core.NonnilStringConstants
// CHECK-NEXT: core.NullDereference
@@ -42,7 +43,6 @@
// CHECK-NEXT: security.insecureAPI.mktemp
// CHECK-NEXT: security.insecureAPI.vfork
// CHECK-NEXT: unix.API
-// CHECK-NEXT: unix.MutexModeling
// CHECK-NEXT: unix.BlockInCriticalSection
// CHECK-NEXT: unix.cstring.CStringModeling
// CHECK-NEXT: unix.DynamicMemoryModeling
diff --git a/clang/test/Analysis/std-c-library-functions-arg-enabled-checkers.c b/clang/test/Analysis/std-c-library-functions-arg-enabled-checkers.c
index c3bacb0706782b..711567671613b3 100644
--- a/clang/test/Analysis/std-c-library-functions-arg-enabled-checkers.c
+++ b/clang/test/Analysis/std-c-library-functions-arg-enabled-checkers.c
@@ -24,6 +24,7 @@
// CHECK-NEXT: core.CallAndMessage
// CHECK-NEXT: core.DivideZero
// CHECK-NEXT: core.DynamicTypePropagation
+// CHECK-NEXT: core.MutexModeling
// CHECK-NEXT: core.NonNullParamChecker
// CHECK-NEXT: core.NonnilStringConstants
// CHECK-NEXT: core.NullDereference
@@ -50,7 +51,6 @@
// CHECK-NEXT: security.insecureAPI.mktemp
// CHECK-NEXT: security.insecureAPI.vfork
// CHECK-NEXT: unix.API
-// CHECK-NEXT: unix.MutexModeling
// CHECK-NEXT: unix.BlockInCriticalSection
// CHECK-NEXT: unix.cstring.CStringModeling
// CHECK-NEXT: unix.DynamicMemoryModeling
>From d9e7ae570521735073b09764884b4eaa36affda9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Fri, 2 Aug 2024 20:40:27 +0200
Subject: [PATCH 19/30] fix default args for MakeMemberExtractor
---
.../StaticAnalyzer/Checkers/MutexModeling/MutexModelingAPI.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingAPI.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingAPI.h
index f420920634a87c..632670a473426e 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingAPI.h
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingAPI.h
@@ -54,8 +54,8 @@ MakeFirstArgExtractor(ArrayRef<StringRef> NameParts, int NumArgsRequired = 1,
}
inline auto MakeMemberExtractor(ArrayRef<StringRef> NameParts,
- int NumArgsRequired = 1,
- CallDescription::Mode MatchAs = CDM::CLibrary) {
+ int NumArgsRequired = 0,
+ CallDescription::Mode MatchAs = CDM::CXXMethod) {
return MemberMutexExtractor{
CallDescription{MatchAs, NameParts, NumArgsRequired}};
}
>From 852e5981ddfe1c80b9187541ad68e7372ea813d5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Fri, 2 Aug 2024 20:40:50 +0200
Subject: [PATCH 20/30] allow events without library
---
.../Checkers/MutexModeling/MutexModelingDomain.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h
index 9dcb3b967dd5ec..5da4c277187359 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling/MutexModelingDomain.h
@@ -33,9 +33,9 @@ enum class EventKind { Init, Acquire, TryAcquire, Release, Destroy };
// checked mutex event the library that event came from. In order to keep the
// external API of multiple distinct checkers (PthreadLockChecker,
// FuchsiaLockChecker and C11LockChecker), this mapping is done here, but if
-// more consumers of this modeling arise, adding all of them here may note be
+// more consumers of this modeling arise, adding all of them here may not be
// feasible and we may need to make this modeling more flexible.
-enum class LibraryKind { Pthread = 0, Fuchsia, C11, NumLibraryKinds };
+enum class LibraryKind { NotApplicable = 0, Pthread, Fuchsia, C11 };
enum class SemanticsKind { NotApplicable = 0, PthreadSemantics, XNUSemantics };
>From 50aec9ef2a30d766649921934e6d70df62515bbc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Fri, 2 Aug 2024 20:41:27 +0200
Subject: [PATCH 21/30] update pthreadlockchecker to support events without a
library
---
.../Checkers/PthreadLockChecker.cpp | 85 +++++++++----------
1 file changed, 39 insertions(+), 46 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
index f1430dd3e7aea8..c2ed0d14187fa2 100644
--- a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
@@ -45,15 +45,7 @@ class PthreadLockChecker : public Checker<check::PostCall> {
bool ChecksEnabled[CK_NumCheckKinds] = {false};
CheckerNameRef CheckNames[CK_NumCheckKinds];
- PthreadLockChecker() { RegisterEvents(); }
-
private:
- std::vector<EventDescriptor> EventsToModel{};
- void RegisterEvents() const {
- for (auto &&Event : EventsToModel) {
- RegisterEvent(Event);
- }
- }
void reportBug(CheckerContext &C, std::unique_ptr<BugType> BT[],
const Expr *MtxExpr, CheckerKind CheckKind,
StringRef Desc) const;
@@ -86,28 +78,33 @@ class PthreadLockChecker : public Checker<check::PostCall> {
"Lock order reversal", "Lock checker"});
}
- [[nodiscard]] constexpr PthreadLockChecker::CheckerKind
+ [[nodiscard]] constexpr std::optional<PthreadLockChecker::CheckerKind>
detectCheckerKind(mutex_modeling::EventMarker EV) const noexcept {
switch (EV.Library) {
+ default:
+ return std::nullopt;
case mutex_modeling::LibraryKind::Pthread:
- return PthreadLockChecker::CK_PthreadLockChecker;
+ return {PthreadLockChecker::CK_PthreadLockChecker};
case mutex_modeling::LibraryKind::Fuchsia:
- return PthreadLockChecker::CK_FuchsiaLockChecker;
+ return {PthreadLockChecker::CK_FuchsiaLockChecker};
case mutex_modeling::LibraryKind::C11:
- return PthreadLockChecker::CK_C11LockChecker;
- default:
- llvm_unreachable("Unknown locking library");
+ return {PthreadLockChecker::CK_C11LockChecker};
}
}
- void checkInitEvent(const EventMarker &LastEvent, CheckerContext &C) const;
- void checkAcquireEvent(const EventMarker &LastEvent, CheckerContext &C) const;
- void checkReleaseEvent(const EventMarker &LastEvent, CheckerContext &C) const;
- void checkDestroyEvent(const EventMarker &LastEvent, CheckerContext &C) const;
+ void checkInitEvent(const EventMarker &LastEvent, CheckerKind Checker,
+ CheckerContext &C) const;
+ void checkAcquireEvent(const EventMarker &LastEvent, CheckerKind Checker,
+ CheckerContext &C) const;
+ void checkReleaseEvent(const EventMarker &LastEvent, CheckerKind Checker,
+ CheckerContext &C) const;
+ void checkDestroyEvent(const EventMarker &LastEvent, CheckerKind Checker,
+ CheckerContext &C) const;
};
} // end anonymous namespace
void PthreadLockChecker::checkInitEvent(const EventMarker &LastEvent,
+ CheckerKind Checker,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
@@ -119,79 +116,74 @@ void PthreadLockChecker::checkInitEvent(const EventMarker &LastEvent,
}
if (*LockState == LockStateKind::Error_DoubleInit) {
- reportBug(C, BT_initlock, LastEvent.EventExpr, detectCheckerKind(LastEvent),
+ reportBug(C, BT_initlock, LastEvent.EventExpr, Checker,
"This lock has already been initialized");
} else if (*LockState == LockStateKind::Error_DoubleInitWhileLocked) {
- reportBug(C, BT_initlock, LastEvent.EventExpr, detectCheckerKind(LastEvent),
+ reportBug(C, BT_initlock, LastEvent.EventExpr, Checker,
"This lock is still being held");
}
}
void PthreadLockChecker::checkAcquireEvent(const EventMarker &LastEvent,
+ CheckerKind Checker,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
const LockStateKind *const LockState =
State->get<LockStates>(LastEvent.MutexRegion);
- if (!LockState) {
+ if (!LockState)
return;
- }
if (*LockState == LockStateKind::Error_DoubleLock) {
- reportBug(C, BT_doublelock, LastEvent.EventExpr,
- detectCheckerKind(LastEvent),
+ reportBug(C, BT_doublelock, LastEvent.EventExpr, Checker,
"This lock has already been acquired");
} else if (*LockState == LockStateKind::Error_LockDestroyed) {
- reportBug(C, BT_destroylock, LastEvent.EventExpr,
- detectCheckerKind(LastEvent),
+ reportBug(C, BT_destroylock, LastEvent.EventExpr, Checker,
"This lock has already been destroyed");
}
}
void PthreadLockChecker::checkReleaseEvent(const EventMarker &LastEvent,
+ CheckerKind Checker,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
const LockStateKind *const LockState =
State->get<LockStates>(LastEvent.MutexRegion);
- if (!LockState) {
+ if (!LockState)
return;
- }
if (*LockState == LockStateKind::Error_DoubleUnlock) {
- reportBug(C, BT_doubleunlock, LastEvent.EventExpr,
- detectCheckerKind(LastEvent),
+ reportBug(C, BT_doubleunlock, LastEvent.EventExpr, Checker,
"This lock has already been unlocked");
} else if (*LockState == LockStateKind::Error_UnlockDestroyed) {
- reportBug(C, BT_destroylock, LastEvent.EventExpr,
- detectCheckerKind(LastEvent),
+ reportBug(C, BT_destroylock, LastEvent.EventExpr, Checker,
"This lock has already been destroyed");
} else if (*LockState == LockStateKind::Error_LockReversal) {
- reportBug(C, BT_lor, LastEvent.EventExpr, detectCheckerKind(LastEvent),
+ reportBug(C, BT_lor, LastEvent.EventExpr, Checker,
"This was not the most recently acquired lock. Possible lock "
"order reversal");
}
}
void PthreadLockChecker::checkDestroyEvent(const EventMarker &LastEvent,
+ CheckerKind Checker,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
const LockStateKind *const LockState =
State->get<LockStates>(LastEvent.MutexRegion);
- if (!LockState || *LockState == LockStateKind::Destroyed) {
+ if (!LockState || *LockState == LockStateKind::Destroyed)
return;
- }
if (*LockState == LockStateKind::Error_DestroyLocked) {
- reportBug(C, BT_destroylock, LastEvent.EventExpr,
- detectCheckerKind(LastEvent), "This lock is still locked");
+ reportBug(C, BT_destroylock, LastEvent.EventExpr, Checker,
+ "This lock is still locked");
} else if (*LockState == LockStateKind::Error_DoubleDestroy)
- reportBug(C, BT_destroylock, LastEvent.EventExpr,
- detectCheckerKind(LastEvent),
+ reportBug(C, BT_destroylock, LastEvent.EventExpr, Checker,
"This lock has already been destroyed");
}
@@ -202,28 +194,29 @@ void PthreadLockChecker::checkPostCall(const CallEvent &Call,
const auto &MTXEvents = State->get<MutexEvents>();
- if (MTXEvents.isEmpty()) {
+ if (MTXEvents.isEmpty())
return;
- }
const auto &LastEvent = MTXEvents.getHead();
- if (!ChecksEnabled[detectCheckerKind(LastEvent)])
+ std::optional<CheckerKind> Checker = detectCheckerKind(LastEvent);
+
+ if (!Checker || !ChecksEnabled[*Checker])
return;
switch (LastEvent.Kind) {
case EventKind::Init:
- checkInitEvent(LastEvent, C);
+ checkInitEvent(LastEvent, *Checker, C);
break;
case EventKind::Acquire:
case EventKind::TryAcquire:
- checkAcquireEvent(LastEvent, C);
+ checkAcquireEvent(LastEvent, *Checker, C);
break;
case EventKind::Release:
- checkReleaseEvent(LastEvent, C);
+ checkReleaseEvent(LastEvent, *Checker, C);
break;
case EventKind::Destroy:
- checkDestroyEvent(LastEvent, C);
+ checkDestroyEvent(LastEvent, *Checker, C);
break;
}
}
>From 1ce45a918e51f83a1b06aca959f191f18aa6bcaa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Fri, 2 Aug 2024 20:41:45 +0200
Subject: [PATCH 22/30] add events for blockincritical
---
.../BlockInCriticalSectionChecker.cpp | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
index 12b61a36cf934c..9ff543a2fc8d39 100644
--- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
@@ -88,6 +88,25 @@ void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call,
// Checker registration
void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) {
mgr.registerChecker<BlockInCriticalSectionChecker>();
+ RegisterEvent(EventDescriptor{
+ mutex_modeling::MakeMemberExtractor({"std", "mutex", "lock"}),
+ EventKind::Acquire, LibraryKind::NotApplicable,
+ SemanticsKind::XNUSemantics});
+ RegisterEvent(EventDescriptor{
+ mutex_modeling::MakeMemberExtractor({"std", "mutex", "unlock"}),
+ EventKind::Release});
+ RegisterEvent(EventDescriptor{
+ mutex_modeling::MakeRAIILockExtractor("lock_guard"), EventKind::Acquire,
+ LibraryKind::NotApplicable, SemanticsKind::XNUSemantics});
+ RegisterEvent(
+ EventDescriptor{mutex_modeling::MakeRAIIReleaseExtractor("lock_guard"),
+ EventKind::Release});
+ 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(
>From f49cf6cbd76d92d3d95c3e2d31c9a93c3941f261 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Fri, 2 Aug 2024 20:44:42 +0200
Subject: [PATCH 23/30] remove wasinlined limitation
---
clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp | 5 -----
1 file changed, 5 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
index f9653ad790b873..8a04c51a9e15ce 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
@@ -407,11 +407,6 @@ MutexModeling::handleEvent(const EventDescriptor &Event, const MemRegion *MTX,
void MutexModeling::checkPostCall(const CallEvent &Call,
CheckerContext &C) const {
- // FIXME: Try to handle cases when the implementation was inlined rather
- // than just giving up.
- if (C.wasInlined)
- return;
-
ProgramStateRef State = C.getState();
for (auto &&Event : RegisteredEvents) {
if (matches(Event.Trigger, Call)) {
>From dbc0a315efd774efd1bd177826cb328c8a2bfb5d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Wed, 11 Sep 2024 10:52:32 +0200
Subject: [PATCH 24/30] fixup! add trylock stub
---
clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
index 8a04c51a9e15ce..e0918f86221224 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
@@ -239,9 +239,8 @@ MutexModeling::ModelingResult MutexModeling::handleTryAcquire(
const EventDescriptor &Event, const MemRegion *MTX, const CallEvent &Call,
ProgramStateRef State, CheckerContext &C) const {
- ModelingResult Result{State};
// Bifurcate the state, and allow a mode where the lock acquisition fails.
- ProgramStateRef LockSucc;
+ ProgramStateRef LockSucc{State};
SVal RetVal = Call.getReturnValue();
if (auto DefinedRetVal = RetVal.getAs<DefinedSVal>()) {
ProgramStateRef LockFail;
@@ -264,8 +263,7 @@ MutexModeling::ModelingResult MutexModeling::handleTryAcquire(
}
// Pass the state where the locking succeeded onwards.
- Result = onSuccessfullAcquire(MTX, Call, LockSucc, C);
- return Result;
+ return onSuccessfullAcquire(MTX, Call, LockSucc, C);
}
MutexModeling::ModelingResult
>From 317a0a938319dcb1e94ac68de81a1d6f5b0a0ebf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Fri, 13 Sep 2024 23:17:39 +0200
Subject: [PATCH 25/30] always add mark for critical section, not just on first
lock
---
.../StaticAnalyzer/Checkers/MutexModeling.cpp | 47 ++++++++++---------
1 file changed, 25 insertions(+), 22 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
index e0918f86221224..1dc51efb1bd847 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
@@ -181,27 +181,25 @@ MutexModeling::onSuccessfullAcquire(const MemRegion *MTX, const CallEvent &Call,
if (!LockState) {
Result.State = Result.State->set<LockStates>(MTX, LockStateKind::Locked);
- Result = markCritSection(Result, MTX, Call, C);
- return Result;
- }
-
- switch (*LockState) {
- case LockStateKind::Unlocked:
- Result.State = Result.State->set<LockStates>(MTX, LockStateKind::Locked);
- Result = markCritSection(Result, MTX, Call, C);
- break;
- case LockStateKind::Locked:
- Result.State =
- Result.State->set<LockStates>(MTX, LockStateKind::Error_DoubleLock);
- break;
- case LockStateKind::Destroyed:
- Result.State =
- Result.State->set<LockStates>(MTX, LockStateKind::Error_LockDestroyed);
- break;
- default:
- break;
+ } else {
+ switch (*LockState) {
+ case LockStateKind::Unlocked:
+ Result.State = Result.State->set<LockStates>(MTX, LockStateKind::Locked);
+ break;
+ case LockStateKind::Locked:
+ Result.State =
+ Result.State->set<LockStates>(MTX, LockStateKind::Error_DoubleLock);
+ break;
+ case LockStateKind::Destroyed:
+ Result.State = Result.State->set<LockStates>(
+ MTX, LockStateKind::Error_LockDestroyed);
+ break;
+ default:
+ break;
+ }
}
+ Result = markCritSection(Result, MTX, Call, C);
return Result;
}
@@ -239,10 +237,13 @@ MutexModeling::ModelingResult MutexModeling::handleTryAcquire(
const EventDescriptor &Event, const MemRegion *MTX, const CallEvent &Call,
ProgramStateRef State, CheckerContext &C) const {
- // Bifurcate the state, and allow a mode where the lock acquisition fails.
ProgramStateRef LockSucc{State};
+ // Bifurcate the state, and allow a mode where the lock acquisition fails.
SVal RetVal = Call.getReturnValue();
- if (auto DefinedRetVal = RetVal.getAs<DefinedSVal>()) {
+ std::optional<DefinedSVal> DefinedRetVal = RetVal.getAs<DefinedSVal>();
+ // Bifurcating the state is only meaningful if the call was not inlined, but
+ // we can still reason about the return value.
+ if (!C.wasInlined && DefinedRetVal) {
ProgramStateRef LockFail;
switch (Event.Semantics) {
case SemanticsKind::PthreadSemantics:
@@ -254,7 +255,6 @@ MutexModeling::ModelingResult MutexModeling::handleTryAcquire(
default:
llvm_unreachable("Unknown TryLock locking semantics");
}
- assert(LockFail && LockSucc && "Bifurcation point in ExplodedGraph");
// This is the bifurcation point in the ExplodedGraph, we do not need to
// return the new ExplodedGraph node because we do not plan on building this
@@ -262,6 +262,9 @@ MutexModeling::ModelingResult MutexModeling::handleTryAcquire(
C.addTransition(LockFail);
}
+ if (!LockSucc)
+ LockSucc = State;
+
// Pass the state where the locking succeeded onwards.
return onSuccessfullAcquire(MTX, Call, LockSucc, C);
}
>From 3407eadfa8f3ab67f09a1527533d7e5ba60a5c0e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Fri, 13 Sep 2024 23:17:52 +0200
Subject: [PATCH 26/30] add modeling for unique_lock
---
.../Checkers/BlockInCriticalSectionChecker.cpp | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
index 9ff543a2fc8d39..9a48f38dee4050 100644
--- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
@@ -101,6 +101,12 @@ void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) {
RegisterEvent(
EventDescriptor{mutex_modeling::MakeRAIIReleaseExtractor("lock_guard"),
EventKind::Release});
+ RegisterEvent(EventDescriptor{
+ mutex_modeling::MakeRAIILockExtractor("unique_lock"), EventKind::Acquire,
+ LibraryKind::NotApplicable, SemanticsKind::XNUSemantics});
+ RegisterEvent(
+ EventDescriptor{mutex_modeling::MakeRAIIReleaseExtractor("unique_lock"),
+ EventKind::Release});
RegisterEvent(EventDescriptor{
mutex_modeling::MakeRAIILockExtractor("scoped_lock"), EventKind::Acquire,
LibraryKind::NotApplicable, SemanticsKind::XNUSemantics});
>From 2877a947198694d230a234c87e870a137893a4f4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Sat, 14 Sep 2024 00:21:31 +0200
Subject: [PATCH 27/30] silence warning about not handling all event kinds
---
clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp | 2 ++
1 file changed, 2 insertions(+)
diff --git a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
index 1dc51efb1bd847..11ff22bd009c6b 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MutexModeling.cpp
@@ -403,6 +403,8 @@ MutexModeling::handleEvent(const EventDescriptor &Event, const MemRegion *MTX,
return handleRelease(Event, MTX, Call, State, C);
case EventKind::Destroy:
return handleDestroy(Event, MTX, Call, State, C);
+ default:
+ llvm_unreachable("Unknown event kind");
}
}
>From d6588af35e8c81e1c35a804e8a40efb4dbba50da Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Fri, 2 Aug 2024 20:45:05 +0200
Subject: [PATCH 28/30] [test fix-testcase] fix block_in_critical_section
testcase
the signature of mtx_timedlock is different according to the
documentation
---
clang/test/Analysis/block-in-critical-section.cpp | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/clang/test/Analysis/block-in-critical-section.cpp b/clang/test/Analysis/block-in-critical-section.cpp
index ee9a708f231a86..5424e8eb409f00 100644
--- a/clang/test/Analysis/block-in-critical-section.cpp
+++ b/clang/test/Analysis/block-in-critical-section.cpp
@@ -41,8 +41,9 @@ int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
struct mtx_t;
+struct timespec;
int mtx_lock(mtx_t *mutex);
-int mtx_timedlock(mtx_t *mutex);
+int mtx_timedlock(mtx_t *mutex, const struct timespec *ts);
int mtx_trylock(mtx_t *mutex);
int mtx_unlock(mtx_t *mutex);
@@ -100,7 +101,7 @@ void testBlockInCriticalSectionWithPthreadMutex(pthread_mutex_t *mutex) {
pthread_mutex_unlock(mutex);
}
-void testBlockInCriticalSectionC11Locks(mtx_t *mutex) {
+void testBlockInCriticalSectionC11Locks(mtx_t *mutex, timespec *ts) {
mtx_lock(mutex); // expected-note 5{{Entering critical section here}}
sleep(3); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
// expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
@@ -114,7 +115,7 @@ void testBlockInCriticalSectionC11Locks(mtx_t *mutex) {
// expected-note at -1 {{Call to blocking function 'recv' inside of critical section}}
mtx_unlock(mutex);
- mtx_timedlock(mutex); // expected-note 5{{Entering critical section here}}
+ mtx_timedlock(mutex, ts); // expected-note 5{{Entering critical section here}}
sleep(3); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
// expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
getc(stream); // expected-warning {{Call to blocking function 'getc' inside of critical section}}
>From bf6c98b517a99ebbc780bb2824731913a551c5e1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Wed, 11 Sep 2024 10:36:02 +0200
Subject: [PATCH 29/30] [test] update pthreadlock_state printing tests to
contain the list of events
---
clang/test/Analysis/pthreadlock_state.c | 53 +++++++++++++++++--
.../Analysis/pthreadlock_state_nottracked.c | 5 ++
2 files changed, 53 insertions(+), 5 deletions(-)
diff --git a/clang/test/Analysis/pthreadlock_state.c b/clang/test/Analysis/pthreadlock_state.c
index ffbb33f6beaf7a..eab1b468aceeb9 100644
--- a/clang/test/Analysis/pthreadlock_state.c
+++ b/clang/test/Analysis/pthreadlock_state.c
@@ -15,24 +15,49 @@ void test(void) {
pthread_mutex_init(&mtx, NULL);
clang_analyzer_printState();
// CHECK: { "checker": "alpha.core.PthreadLockBase", "messages": [
- // CHECK-NEXT: "Mutex states:",
- // CHECK-NEXT: "mtx: unlocked",
+ // CHECK-NEXT: "Mutex event:",
+ // CHECK-NEXT: "Kind: : Init",
+ // CHECK-NEXT: "Semantics: NotApplicable",
+ // CHECK-NEXT: "Library: Pthread",
+ // CHECK-NEXT: "Mutex region: mtx",
+ // CHECK-NEXT: "Mutex states:",
+ // CHECK-NEXT: "mtx: unlocked",
// CHECK-NEXT: ""
// CHECK-NEXT: ]}
pthread_mutex_lock(&mtx);
clang_analyzer_printState();
// CHECK: { "checker": "alpha.core.PthreadLockBase", "messages": [
+ // CHECK-NEXT: "Mutex event:",
+ // CHECK-NEXT: "Kind: : Acquire",
+ // CHECK-NEXT: "Semantics: PthreadSemantics",
+ // CHECK-NEXT: "Library: Pthread",
+ // CHECK-NEXT: "Mutex region: mtx",
+ // CHECK-NEXT: "Kind: : Init",
+ // CHECK-NEXT: "Semantics: NotApplicable",
+ // CHECK-NEXT: "Library: Pthread",
+ // CHECK-NEXT: "Mutex region: mtx",
// CHECK-NEXT: "Mutex states:",
// CHECK-NEXT: "mtx: locked",
- // CHECK-NEXT: "Mutex lock order:",
- // CHECK-NEXT: "mtx",
// CHECK-NEXT: ""
// CHECK-NEXT: ]}
pthread_mutex_unlock(&mtx);
clang_analyzer_printState();
// CHECK: { "checker": "alpha.core.PthreadLockBase", "messages": [
+ // CHECK-NEXT: "Mutex event:",
+ // CHECK-NEXT: "Kind: : Release",
+ // CHECK-NEXT: "Semantics: NotApplicable",
+ // CHECK-NEXT: "Library: Pthread",
+ // CHECK-NEXT: "Mutex region: mtx",
+ // CHECK-NEXT: "Kind: : Acquire",
+ // CHECK-NEXT: "Semantics: PthreadSemantics",
+ // CHECK-NEXT: "Library: Pthread",
+ // CHECK-NEXT: "Mutex region: mtx",
+ // CHECK-NEXT: "Kind: : Init",
+ // CHECK-NEXT: "Semantics: NotApplicable",
+ // CHECK-NEXT: "Library: Pthread",
+ // CHECK-NEXT: "Mutex region: mtx",
// CHECK-NEXT: "Mutex states:",
// CHECK-NEXT: "mtx: unlocked",
// CHECK-NEXT: ""
@@ -41,6 +66,23 @@ void test(void) {
int ret = pthread_mutex_destroy(&mtx);
clang_analyzer_printState();
// CHECK: { "checker": "alpha.core.PthreadLockBase", "messages": [
+ // CHECK-NEXT: "Mutex event:",
+ // CHECK-NEXT: "Kind: : Destroy",
+ // CHECK-NEXT: "Semantics: PthreadSemantics",
+ // CHECK-NEXT: "Library: Pthread",
+ // CHECK-NEXT: "Mutex region: mtx",
+ // CHECK-NEXT: "Kind: : Release",
+ // CHECK-NEXT: "Semantics: NotApplicable",
+ // CHECK-NEXT: "Library: Pthread",
+ // CHECK-NEXT: "Mutex region: mtx",
+ // CHECK-NEXT: "Kind: : Acquire",
+ // CHECK-NEXT: "Semantics: PthreadSemantics",
+ // CHECK-NEXT: "Library: Pthread",
+ // CHECK-NEXT: "Mutex region: mtx",
+ // CHECK-NEXT: "Kind: : Init",
+ // CHECK-NEXT: "Semantics: NotApplicable",
+ // CHECK-NEXT: "Library: Pthread",
+ // CHECK-NEXT: "Mutex region: mtx",
// CHECK-NEXT: "Mutex states:",
// CHECK-NEXT: "mtx: unlocked, possibly destroyed",
// CHECK-NEXT: "Mutexes in unresolved possibly destroyed state:",
@@ -51,9 +93,10 @@ void test(void) {
if (ret)
return;
+ // Assert that the 'possibly destroyed' state is now resolved to 'destroyed'.
clang_analyzer_printState();
// CHECK: { "checker": "alpha.core.PthreadLockBase", "messages": [
- // CHECK-NEXT: "Mutex states:",
+ // CHECK: "Mutex states:",
// CHECK-NEXT: "mtx: destroyed",
// CHECK-NEXT: ""
// CHECK-NEXT: ]}
diff --git a/clang/test/Analysis/pthreadlock_state_nottracked.c b/clang/test/Analysis/pthreadlock_state_nottracked.c
index 44023e7fc15c47..7788377069eb88 100644
--- a/clang/test/Analysis/pthreadlock_state_nottracked.c
+++ b/clang/test/Analysis/pthreadlock_state_nottracked.c
@@ -10,6 +10,11 @@ void test(pthread_mutex_t *mtx) {
int ret = pthread_mutex_destroy(mtx);
clang_analyzer_printState();
// CHECK: { "checker": "alpha.core.PthreadLockBase", "messages": [
+ // CHECK-NEXT: "Mutex event:",
+ // CHECK-NEXT: "Kind: : Destroy",
+ // CHECK-NEXT: "Semantics: PthreadSemantics",
+ // CHECK-NEXT: "Library: Pthread",
+ // CHECK-NEXT: "Mutex region: SymRegion{reg_$[[REG:[0-9]+]]<pthread_mutex_t * mtx>}",
// CHECK-NEXT: "Mutex states:",
// CHECK-NEXT: "SymRegion{reg_$[[REG:[0-9]+]]<pthread_mutex_t * mtx>}: not tracked, possibly destroyed",
// CHECK-NEXT: "Mutexes in unresolved possibly destroyed state:",
>From c9219d624a3d3fe2348a7ee926c7a76fa15c6275 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Fri, 13 Sep 2024 23:17:12 +0200
Subject: [PATCH 30/30] [test] false positive is fixed
---
clang/test/Analysis/block-in-critical-section.cpp | 14 ++++----------
1 file changed, 4 insertions(+), 10 deletions(-)
diff --git a/clang/test/Analysis/block-in-critical-section.cpp b/clang/test/Analysis/block-in-critical-section.cpp
index 5424e8eb409f00..cd5fa1edcbea71 100644
--- a/clang/test/Analysis/block-in-critical-section.cpp
+++ b/clang/test/Analysis/block-in-critical-section.cpp
@@ -294,19 +294,13 @@ void testBlockInCriticalSectionUniqueLockNested() {
sleep(1); // no-warning
}
-void testTrylockCurrentlyFalsePositive(pthread_mutex_t *m) {
- // expected-note at +4 {{Assuming the condition is true}}
- // expected-note at +3 {{Taking true branch}}
- // expected-note at +2 {{Assuming the condition is false}}
- // expected-note at +1 {{Taking false branch}}
- if (pthread_mutex_trylock(m) == 0) { // expected-note 2 {{Entering critical section here}}
- // FIXME: we are entering the critical section only in the true branch
+void testTrylockStateSplitting(pthread_mutex_t *m) {
+ // expected-note at +1 {{Taking true branch}}
+ if (pthread_mutex_trylock(m) == 0) { // expected-note {{Entering critical section here}}
sleep(10); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
// expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
pthread_mutex_unlock(m);
} else {
- sleep(10); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
- // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
- // FIXME: this is a false positive, the lock was not acquired
+ sleep(10); // no-warning
}
}
More information about the cfe-commits
mailing list