[clang] [NFC][analyzer] Multipart checker refactor 2: NullabilityChecker (PR #132250)
Balazs Benics via cfe-commits
cfe-commits at lists.llvm.org
Fri Mar 21 07:07:31 PDT 2025
=?utf-8?q?Donát?= Nagy <donat.nagy at ericsson.com>,
=?utf-8?q?Donát?= Nagy <donat.nagy at ericsson.com>,
=?utf-8?q?Donát?= Nagy <donat.nagy at ericsson.com>
Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/132250 at github.com>
================
@@ -112,25 +112,30 @@ class NullabilityChecker
void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
const char *Sep) const override;
- enum CheckKind {
- CK_NullPassedToNonnull,
- CK_NullReturnedFromNonnull,
- CK_NullableDereferenced,
- CK_NullablePassedToNonnull,
- CK_NullableReturnedFromNonnull,
- CK_NumCheckKinds
+ // FIXME: This enumeration of checker parts is extremely similar to the
+ // ErrorKind enum. It would be nice to unify them to simplify the code.
+ enum : CheckerPartIdx {
+ NullPassedToNonnullChecker,
+ NullReturnedFromNonnullChecker,
+ NullableDereferencedChecker,
+ NullablePassedToNonnullChecker,
+ NullableReturnedFromNonnullChecker,
+ NumCheckerParts
};
- bool ChecksEnabled[CK_NumCheckKinds] = {false};
- CheckerNameRef CheckNames[CK_NumCheckKinds];
- mutable std::unique_ptr<BugType> BTs[CK_NumCheckKinds];
-
- const std::unique_ptr<BugType> &getBugType(CheckKind Kind) const {
- if (!BTs[Kind])
- BTs[Kind].reset(new BugType(CheckNames[Kind], "Nullability",
- categories::MemoryError));
- return BTs[Kind];
- }
+ // FIXME: Currently the `Description` fields of these `BugType`s are all
+ // identical ("Nullability") -- they should be more descriptive than this.
+ BugType BugTypes[NumCheckerParts] = {
+ {this, NullPassedToNonnullChecker, "Nullability",
+ categories::MemoryError},
+ {this, NullReturnedFromNonnullChecker, "Nullability",
+ categories::MemoryError},
+ {this, NullableDereferencedChecker, "Nullability",
+ categories::MemoryError},
+ {this, NullablePassedToNonnullChecker, "Nullability",
+ categories::MemoryError},
+ {this, NullableReturnedFromNonnullChecker, "Nullability",
+ categories::MemoryError}};
----------------
steakhal wrote:
I have something like this in mind:
```diff
diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugType.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugType.h
index 3a635e0d0125..75c7786aea3e 100644
--- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugType.h
+++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugType.h
@@ -29,7 +29,7 @@ class BugType {
private:
struct CheckerPartRef {
const CheckerBase *Checker;
- CheckerPartIdx Idx;
+ const CheckerPart &Part;
};
using CheckerNameInfo = std::variant<CheckerNameRef, CheckerPartRef>;
@@ -55,14 +55,14 @@ public:
// pointer to the checker and later use that to query the name.
BugType(const CheckerBase *Checker, StringRef Desc,
StringRef Cat = categories::LogicError, bool SuppressOnSink = false)
- : CheckerName(CheckerPartRef{Checker, DefaultPart}), Description(Desc),
- Category(Cat), SuppressOnSink(SuppressOnSink) {}
+ : CheckerName(Checker->getName()), Description(Desc), Category(Cat),
+ SuppressOnSink(SuppressOnSink) {}
// Constructor that can be called from the constructor of a checker object
// when it has multiple parts with separate names. We save the name and the
// part index to be able to query the name of that part later.
- BugType(const CheckerBase *Checker, CheckerPartIdx Idx, StringRef Desc,
+ BugType(const CheckerBase *Checker, const CheckerPart &Part, StringRef Desc,
StringRef Cat = categories::LogicError, bool SuppressOnSink = false)
- : CheckerName(CheckerPartRef{Checker, Idx}), Description(Desc),
+ : CheckerName(CheckerPartRef{Checker, Part}), Description(Desc),
Category(Cat), SuppressOnSink(SuppressOnSink) {}
virtual ~BugType() = default;
@@ -72,8 +72,8 @@ public:
if (const auto *CNR = std::get_if<CheckerNameRef>(&CheckerName))
return *CNR;
- auto [Checker, Idx] = std::get<CheckerPartRef>(CheckerName);
- return Checker->getName(Idx);
+ auto [Checker, Part] = std::get<CheckerPartRef>(CheckerName);
+ return Checker->getName(Part);
}
/// isSuppressOnSink - Returns true if bug reports associated with this bug
diff --git a/clang/include/clang/StaticAnalyzer/Core/Checker.h b/clang/include/clang/StaticAnalyzer/Core/Checker.h
index a54c5bee612f..e63b3454b853 100644
--- a/clang/include/clang/StaticAnalyzer/Core/Checker.h
+++ b/clang/include/clang/StaticAnalyzer/Core/Checker.h
@@ -17,6 +17,7 @@
#include "clang/Basic/LangOptions.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
+#include "llvm/ADT/DenseMap.h"
#include "llvm/Support/Casting.h"
namespace clang {
@@ -488,31 +489,30 @@ class CheckerBase : public ProgramPointTag {
/// A single checker class (i.e. a subclass of `CheckerBase`) can implement
/// multiple user-facing checkers that have separate names and can be enabled
/// separately, but are backed by the same singleton checker object.
- SmallVector<std::optional<CheckerNameRef>, 1> RegisteredNames;
+ llvm::SmallDenseMap<const CheckerPart *, CheckerNameRef> RegisteredNames;
+ CheckerNameRef FirstRegisteredName;
friend class ::clang::ento::CheckerManager;
public:
- CheckerNameRef getName(CheckerPartIdx Idx = DefaultPart) const {
- assert(Idx < RegisteredNames.size() && "Checker part index is too large!");
- std::optional<CheckerNameRef> Name = RegisteredNames[Idx];
- assert(Name && "Requested checker part is not registered!");
- return *Name;
+ CheckerNameRef getName() const {
+ assert(!RegisteredNames.empty());
+ return FirstRegisteredName;
}
- bool isPartEnabled(CheckerPartIdx Idx) const {
- return Idx < RegisteredNames.size() && RegisteredNames[Idx].has_value();
+ CheckerNameRef getName(const CheckerPart &Part) const {
+ auto It = RegisteredNames.find(&Part);
+ assert(It != RegisteredNames.end() &&
+ "Requested checker part is not registered!");
+ return It->second;
}
- void registerCheckerPart(CheckerPartIdx Idx, CheckerNameRef Name) {
- // Paranoia: notice if e.g. UINT_MAX is passed as a checker part index.
- assert(Idx < 256 && "Checker part identifiers should be small integers.");
+ void registerCheckerPart(const CheckerPart &Part, CheckerNameRef Name) {
+ if (RegisteredNames.empty())
+ FirstRegisteredName = Name;
- if (Idx >= RegisteredNames.size())
- RegisteredNames.resize(Idx + 1, std::nullopt);
-
- assert(!RegisteredNames[Idx] && "Repeated registration of checker a part!");
- RegisteredNames[Idx] = Name;
+ bool Inserted = RegisteredNames.insert({&Part, Name}).second;
+ assert(Inserted && "Repeated registration of checker a part!");
}
StringRef getTagDescription() const override {
@@ -521,11 +521,10 @@ public:
// checker _class_. Ideally this should be recognizable identifier of the
// whole class, but for this debugging purpose it's sufficient to use the
// name of the first registered checker part.
- for (const auto &OptName : RegisteredNames)
- if (OptName)
- return *OptName;
-
- return "Unregistered checker";
+ assert(!RegisteredNames.empty());
+ if (RegisteredNames.empty())
+ return "Unregistered checker";
+ return FirstRegisteredName;
}
/// Debug state dump callback, see CheckerManager::runCheckersForPrintState.
diff --git a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h
index 03ffadd346d0..b0ffef197246 100644
--- a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h
+++ b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h
@@ -116,18 +116,18 @@ public:
operator StringRef() const { return Name; }
};
-/// A single checker class (and its singleton instance) can act as the
-/// implementation of several (user-facing or modeling) checker parts that
-/// have shared state and logic, but have their own names and can be enabled or
-/// disabled separately.
-/// Each checker class that implement multiple parts introduces its own enum
-/// type to assign small numerical indices (0, 1, 2 ...) to their parts. The
-/// type alias 'CheckerPartIdx' is conceptually the union of these enum types.
-using CheckerPartIdx = unsigned;
-
-/// If a checker doesn't have multiple parts, then its single part is
-/// represented by this index.
-constexpr inline CheckerPartIdx DefaultPart = 0;
+// / A single checker class (and its singleton instance) can act as the
+// / implementation of several (user-facing or modeling) checker parts that
+// / have shared state and logic, but have their own names and can be enabled or
+// / disabled separately.
+// / Each checker class that implement multiple parts introduces its own enum
+// / type to assign small numerical indices (0, 1, 2 ...) to their parts. The
+// / type alias 'CheckerPartIdx' is conceptually the union of these enum types.
+struct CheckerPart {
+ const CheckerBase &Checker;
+ bool IsEnabled = false;
+ explicit CheckerPart(const CheckerBase *Checker) : Checker(*Checker) {}
+};
enum class ObjCMessageVisitKind {
Pre,
@@ -196,11 +196,12 @@ public:
void reportInvalidCheckerOptionValue(const CheckerBase *C,
StringRef OptionName,
StringRef ExpectedValueDesc) const {
- reportInvalidCheckerOptionValue(C, DefaultPart, OptionName,
+ reportInvalidCheckerOptionValue(C, /*Part=*/nullptr, OptionName,
ExpectedValueDesc);
}
- void reportInvalidCheckerOptionValue(const CheckerBase *C, CheckerPartIdx Idx,
+ void reportInvalidCheckerOptionValue(const CheckerBase *C,
+ const CheckerPart *Part,
StringRef OptionName,
StringRef ExpectedValueDesc) const;
@@ -221,12 +222,13 @@ public:
/// existing checker object (while registering their names).
///
/// \returns a pointer to the checker object.
- template <typename CHECKER, CheckerPartIdx Idx = DefaultPart, typename... AT>
+ template <typename CHECKER, CheckerPart(CHECKER::*PartSelector) = nullptr,
+ typename... AT>
CHECKER *registerChecker(AT &&...Args) {
// This assert could be removed but then we need to make sure that calls
// registering different parts of the same checker pass the same arguments.
static_assert(
- Idx == DefaultPart || !sizeof...(AT),
+ PartSelector == nullptr || !sizeof...(AT),
"Argument forwarding isn't supported with multi-part checkers!");
CheckerTag Tag = getTag<CHECKER>();
@@ -240,7 +242,10 @@ public:
}
CHECKER *Result = static_cast<CHECKER *>(Ref.get());
- Result->registerCheckerPart(Idx, CurrentCheckerName);
+ CheckerPart &Part = Result->*PartSelector;
+ Result->registerCheckerPart(Part, CurrentCheckerName);
+ assert(!Part.IsEnabled);
+ Part.IsEnabled = true;
return Result;
}
diff --git a/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp
index 3dd57732305b..a765f30a71a1 100644
--- a/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp
@@ -34,14 +34,11 @@ class DivZeroChecker : public Checker<check::PreStmt<BinaryOperator>> {
public:
/// This checker class implements several user facing checkers
- enum : CheckerPartIdx {
- DivideZeroChecker,
- TaintedDivChecker,
- NumCheckerParts
- };
- BugType BugTypes[NumCheckerParts] = {
- {this, DivideZeroChecker, "Division by zero"},
- {this, TaintedDivChecker, "Division by zero", categories::TaintedData}};
+ CheckerPart DivideZeroChecker{this};
+ CheckerPart TaintedDivChecker{this};
+ const BugType DivisionByZeroBug{this, DivideZeroChecker, "Division by zero"};
+ const BugType DivisionByTaintedBug{
+ this, TaintedDivChecker, "Division by zero", categories::TaintedData};
void checkPreStmt(const BinaryOperator *B, CheckerContext &C) const;
};
@@ -56,11 +53,11 @@ static const Expr *getDenomExpr(const ExplodedNode *N) {
void DivZeroChecker::reportBug(StringRef Msg, ProgramStateRef StateZero,
CheckerContext &C) const {
- if (!isPartEnabled(DivideZeroChecker))
+ if (!DivideZeroChecker.IsEnabled)
return;
if (ExplodedNode *N = C.generateErrorNode(StateZero)) {
- auto R = std::make_unique<PathSensitiveBugReport>(
- BugTypes[DivideZeroChecker], Msg, N);
+ auto R =
+ std::make_unique<PathSensitiveBugReport>(DivisionByZeroBug, Msg, N);
bugreporter::trackExpressionValue(N, getDenomExpr(N), *R);
C.emitReport(std::move(R));
}
@@ -69,11 +66,11 @@ void DivZeroChecker::reportBug(StringRef Msg, ProgramStateRef StateZero,
void DivZeroChecker::reportTaintBug(
StringRef Msg, ProgramStateRef StateZero, CheckerContext &C,
llvm::ArrayRef<SymbolRef> TaintedSyms) const {
- if (!isPartEnabled(TaintedDivChecker))
+ if (!TaintedDivChecker.IsEnabled)
return;
if (ExplodedNode *N = C.generateNonFatalErrorNode(StateZero)) {
- auto R = std::make_unique<PathSensitiveBugReport>(
- BugTypes[TaintedDivChecker], Msg, N);
+ auto R =
+ std::make_unique<PathSensitiveBugReport>(DivisionByTaintedBug, Msg, N);
bugreporter::trackExpressionValue(N, getDenomExpr(N), *R);
for (auto Sym : TaintedSyms)
R->markInteresting(Sym);
@@ -127,13 +124,13 @@ void DivZeroChecker::checkPreStmt(const BinaryOperator *B,
}
void ento::registerDivZeroChecker(CheckerManager &Mgr) {
- Mgr.registerChecker<DivZeroChecker, DivZeroChecker::DivideZeroChecker>();
+ Mgr.registerChecker<DivZeroChecker, &DivZeroChecker::DivideZeroChecker>();
}
bool ento::shouldRegisterDivZeroChecker(const CheckerManager &) { return true; }
void ento::registerTaintedDivChecker(CheckerManager &Mgr) {
- Mgr.registerChecker<DivZeroChecker, DivZeroChecker::TaintedDivChecker>();
+ Mgr.registerChecker<DivZeroChecker, &DivZeroChecker::TaintedDivChecker>();
}
bool ento::shouldRegisterTaintedDivChecker(const CheckerManager &) {
diff --git a/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp
index 5b0d303ee5bb..5a58e4cc6553 100644
--- a/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp
@@ -42,13 +42,14 @@ namespace {
class VirtualCallChecker
: public Checker<check::BeginFunction, check::EndFunction, check::PreCall> {
public:
- enum : CheckerPartIdx { PureChecker, ImpureChecker, NumCheckerParts };
-
- BugType BugTypes[NumCheckerParts] = {
- {this, PureChecker, "Pure virtual method call",
- categories::CXXObjectLifecycle},
- {this, ImpureChecker, "Unexpected loss of virtual dispatch",
- categories::CXXObjectLifecycle}};
+ CheckerPart PureChecker{this};
+ CheckerPart ImpureChecker{this};
+ const BugType CallingPureVirtualMethodBug{this, PureChecker,
+ "Pure virtual method call",
+ categories::CXXObjectLifecycle};
+ const BugType UnexpectedVirtualDispatchBug{
+ this, ImpureChecker, "Unexpected loss of virtual dispatch",
+ categories::CXXObjectLifecycle};
bool ShowFixIts = false;
@@ -147,15 +148,14 @@ void VirtualCallChecker::checkPreCall(const CallEvent &Call,
if (!N)
return;
- const CheckerPartIdx Part = IsPure ? PureChecker : ImpureChecker;
-
- if (!isPartEnabled(Part)) {
+ const CheckerPart &Part = IsPure ? PureChecker : ImpureChecker;
+ if (!Part.IsEnabled) {
// The respective check is disabled.
return;
}
-
- auto Report =
- std::make_unique<PathSensitiveBugReport>(BugTypes[Part], OS.str(), N);
+ const BugType &Bug =
+ IsPure ? CallingPureVirtualMethodBug : UnexpectedVirtualDispatchBug;
+ auto Report = std::make_unique<PathSensitiveBugReport>(Bug, OS.str(), N);
if (ShowFixIts && !IsPure) {
// FIXME: These hints are valid only when the virtual call is made
@@ -210,7 +210,7 @@ void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction,
}
void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) {
- Mgr.registerChecker<VirtualCallChecker, VirtualCallChecker::PureChecker>();
+ Mgr.registerChecker<VirtualCallChecker, &VirtualCallChecker::PureChecker>();
}
bool ento::shouldRegisterPureVirtualCallChecker(const CheckerManager &Mgr) {
@@ -219,7 +219,7 @@ bool ento::shouldRegisterPureVirtualCallChecker(const CheckerManager &Mgr) {
void ento::registerVirtualCallChecker(CheckerManager &Mgr) {
auto *Chk = Mgr.registerChecker<VirtualCallChecker,
- VirtualCallChecker::ImpureChecker>();
+ &VirtualCallChecker::ImpureChecker>();
Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption(
Mgr.getCurrentCheckerName(), "ShowFixIts");
}
diff --git a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp
index 7ae86f133904..c8281df38427 100644
--- a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp
@@ -50,11 +50,11 @@ bool CheckerManager::hasPathSensitiveCheckers() const {
}
void CheckerManager::reportInvalidCheckerOptionValue(
- const CheckerBase *C, CheckerPartIdx Idx, StringRef OptionName,
+ const CheckerBase *C, const CheckerPart *Part, StringRef OptionName,
StringRef ExpectedValueDesc) const {
getDiagnostics().Report(diag::err_analyzer_checker_option_invalid_input)
- << (llvm::Twine(C->getName(Idx)) + ":" + OptionName).str()
+ << (llvm::Twine(C->getName(*Part)) + ":" + OptionName).str()
<< ExpectedValueDesc;
}
```
https://github.com/llvm/llvm-project/pull/132250
More information about the cfe-commits
mailing list