[analyzer] Prettify checker registration and unittest code (#147797)

This commit tweaks the interface of `CheckerRegistry::addChecker` to
make it more practical for plugins and tests:
- The parameter `IsHidden` now defaults to `false` even in the
  non-templated overload (because setting it to true is unusual,
  especially in plugins).
- The parameter `DocsUri` defaults to the dummy placeholder string
  `"NoDocsUri"` because (as of now) nothing queries its value from the
  checker registry (it's only used by the logic that generates the
  clang-tidy documentation, but that loads it directly from `Checkers.td`
  without involving the `CheckerRegistry`), so there is no reason to
  demand specifying this value.

In addition to propagating these changes, this commit clarifies,
corrects and extends lots of comments and performs various minor code
quality improvements in the code of unit tests and example plugins.

I originally wrote the bulk of this commit when I was planning to add an
extra parameter to `addChecker` in order to implement some technical
details of the CheckerFamily framework. At the end I decided against
adding that extra parameter, so this cleanup was left out of the PR
https://github.com/llvm/llvm-project/pull/139256 and I'm merging it now
as a separate commit (after minor tweaks).

This commit is mostly NFC: the only functional change is that the
analyzer will be compatible with plugins that rely on the default
argument values and don't specify `IsHidden` or `DocsUri`. (But existing
plugin code will remain valid as well.)
This commit is contained in:
Donát Nagy 2025-07-22 13:36:58 +02:00 committed by GitHub
parent f78c4ce55b
commit a807e8ea9f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 89 additions and 95 deletions

View File

@ -38,29 +38,29 @@
// function clang_registerCheckers. For example:
//
// extern "C"
// void clang_registerCheckers (CheckerRegistry &registry) {
// registry.addChecker<MainCallChecker>("example.MainCallChecker",
// "Disallows calls to functions called main");
// void clang_registerCheckers(CheckerRegistry &Registry) {
// Registry.addChecker<MainCallChecker>(
// "example.MainCallChecker",
// "Disallows calls to functions called main");
// }
//
// The first method argument is the full name of the checker, including its
// enclosing package. By convention, the registered name of a checker is the
// name of the associated class (the template argument).
// The second method argument is a short human-readable description of the
// checker.
// The first argument of this templated method is the full name of the checker
// (including its package), while the second argument is a short description
// that is printed by `-analyzer-checker-help`.
//
// The clang_registerCheckers function may add any number of checkers to the
// registry. If any checkers require additional initialization, use the three-
// argument form of CheckerRegistry::addChecker.
// A plugin may register several separate checkers by calling `addChecker()`
// multiple times. If a checker requires custom registration functions (e.g.
// checker option handling) use the non-templated overload of `addChecker` that
// takes two callback functions as the first two parameters.
//
// To load a checker plugin, specify the full path to the dynamic library as
// the argument to the -load option in the cc1 frontend. You can then enable
// your custom checker using the -analyzer-checker:
//
// clang -cc1 -load </path/to/plugin.dylib> -analyze
// -analyzer-checker=<example.MainCallChecker>
// clang -cc1 -load /path/to/plugin.dylib -analyze
// -analyzer-checker=example.MainCallChecker
//
// For a complete working example, see examples/analyzer-plugin.
// For complete examples, see clang/lib/Analysis/plugins/SampleAnalyzer
#ifndef CLANG_ANALYZER_API_VERSION_STRING
// FIXME: The Clang version string is not particularly granular;
@ -108,30 +108,25 @@ private:
mgr.template registerChecker<T>();
}
template <typename T> static bool returnTrue(const CheckerManager &mgr) {
return true;
}
static bool returnTrue(const CheckerManager &) { return true; }
public:
/// Adds a checker to the registry. Use this non-templated overload when your
/// checker requires custom initialization.
void addChecker(RegisterCheckerFn Fn, ShouldRegisterFunction sfn,
StringRef FullName, StringRef Desc, StringRef DocsUri,
bool IsHidden);
/// Adds a checker to the registry.
/// Use this for a checker defined in a plugin if it requires custom
/// registration functions (e.g. for handling checker options).
/// NOTE: As of now `DocsUri` is never queried from the checker registry.
void addChecker(RegisterCheckerFn Fn, ShouldRegisterFunction Sfn,
StringRef FullName, StringRef Desc,
StringRef DocsUri = "NoDocsUri", bool IsHidden = false);
/// Adds a checker to the registry. Use this templated overload when your
/// checker does not require any custom initialization.
/// This function isn't really needed and probably causes more headaches than
/// the tiny convenience that it provides, but external plugins might use it,
/// and there isn't a strong incentive to remove it.
/// Adds a checker to the registry.
/// Use this for a checker defined in a plugin if it doesn't require custom
/// registration functions.
template <class T>
void addChecker(StringRef FullName, StringRef Desc, StringRef DocsUri,
bool IsHidden = false) {
// Avoid MSVC's Compiler Error C2276:
// http://msdn.microsoft.com/en-us/library/850cstw1(v=VS.80).aspx
void addChecker(StringRef FullName, StringRef Desc,
StringRef DocsUri = "NoDocsUri", bool IsHidden = false) {
addChecker(&CheckerRegistry::initializeManager<CheckerManager, T>,
&CheckerRegistry::returnTrue<T>, FullName, Desc, DocsUri,
IsHidden);
&CheckerRegistry::returnTrue, FullName, Desc, DocsUri, IsHidden);
}
/// Makes the checker with the full name \p fullName depend on the checker

View File

@ -2,6 +2,9 @@
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"
// This barebones plugin is used by clang/test/Analysis/checker-plugins.c
// to test dependency handling among checkers loaded from plugins.
using namespace clang;
using namespace ento;
@ -15,12 +18,12 @@ struct DependendentChecker : public Checker<check::BeginFunction> {
} // end anonymous namespace
// Register plugin!
extern "C" void clang_registerCheckers(CheckerRegistry &registry) {
registry.addChecker<Dependency>("example.Dependency", "", "");
registry.addChecker<DependendentChecker>("example.DependendentChecker", "",
"");
extern "C" void clang_registerCheckers(CheckerRegistry &Registry) {
Registry.addChecker<Dependency>("example.Dependency", "MockDescription");
Registry.addChecker<DependendentChecker>("example.DependendentChecker",
"MockDescription");
registry.addDependency("example.DependendentChecker", "example.Dependency");
Registry.addDependency("example.DependendentChecker", "example.Dependency");
}
extern "C" const char clang_analyzerAPIVersionString[] =

View File

@ -5,6 +5,9 @@
using namespace clang;
using namespace ento;
// This barebones plugin is used by clang/test/Analysis/checker-plugins.c
// to test option handling on checkers loaded from plugins.
namespace {
struct MyChecker : public Checker<check::BeginFunction> {
void checkBeginFunction(CheckerContext &Ctx) const {}
@ -25,13 +28,11 @@ bool shouldRegisterMyChecker(const CheckerManager &mgr) { return true; }
} // end anonymous namespace
// Register plugin!
extern "C" void clang_registerCheckers(CheckerRegistry &registry) {
registry.addChecker(registerMyChecker, shouldRegisterMyChecker,
"example.MyChecker", "Example Description",
"example.mychecker.documentation.nonexistent.html",
/*isHidden*/false);
extern "C" void clang_registerCheckers(CheckerRegistry &Registry) {
Registry.addChecker(registerMyChecker, shouldRegisterMyChecker,
"example.MyChecker", "Example Description");
registry.addCheckerOption(/*OptionType*/ "bool",
Registry.addCheckerOption(/*OptionType*/ "bool",
/*CheckerFullName*/ "example.MyChecker",
/*OptionName*/ "ExampleOption",
/*DefaultValStr*/ "false",

View File

@ -3,12 +3,16 @@
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"
// This simple plugin is used by clang/test/Analysis/checker-plugins.c
// to test the use of a checker that is defined in a plugin.
using namespace clang;
using namespace ento;
namespace {
class MainCallChecker : public Checker<check::PreStmt<CallExpr>> {
mutable std::unique_ptr<BugType> BT;
const BugType BT{this, "call to main", "example analyzer plugin"};
public:
void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
@ -33,21 +37,17 @@ void MainCallChecker::checkPreStmt(const CallExpr *CE,
if (!N)
return;
if (!BT)
BT.reset(new BugType(this, "call to main", "example analyzer plugin"));
auto report =
std::make_unique<PathSensitiveBugReport>(*BT, BT->getDescription(), N);
std::make_unique<PathSensitiveBugReport>(BT, BT.getDescription(), N);
report->addRange(Callee->getSourceRange());
C.emitReport(std::move(report));
}
}
// Register plugin!
extern "C" void clang_registerCheckers(CheckerRegistry &registry) {
registry.addChecker<MainCallChecker>(
"example.MainCallChecker", "Disallows calls to functions called main",
"");
extern "C" void clang_registerCheckers(CheckerRegistry &Registry) {
Registry.addChecker<MainCallChecker>("example.MainCallChecker",
"Example Description");
}
extern "C" const char clang_analyzerAPIVersionString[] =

View File

@ -91,8 +91,7 @@ void addBlockEntranceTester(AnalysisASTConsumer &AnalysisConsumer,
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
Registry.addChecker(&registerChecker<BlockEntranceCallbackTester>,
&shouldAlwaysRegister, "test.BlockEntranceTester",
"EmptyDescription", "EmptyDocsUri",
/*IsHidden=*/false);
"EmptyDescription");
});
}
@ -102,8 +101,7 @@ void addBranchConditionTester(AnalysisASTConsumer &AnalysisConsumer,
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
Registry.addChecker(&registerChecker<BranchConditionCallbackTester>,
&shouldAlwaysRegister, "test.BranchConditionTester",
"EmptyDescription", "EmptyDocsUri",
/*IsHidden=*/false);
"EmptyDescription");
});
}

View File

@ -120,7 +120,7 @@ public:
std::move(ExpectedDiags), Compiler.getSourceManager()));
AnalysisConsumer->AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
Registry.addChecker<InterestingnessTestChecker>("test.Interestingness",
"Description", "");
"MockDescription");
});
Compiler.getAnalyzerOpts().CheckersAndPackages = {
{"test.Interestingness", true}};

View File

@ -616,8 +616,8 @@ void addCallDescChecker(AnalysisASTConsumer &AnalysisConsumer,
AnalyzerOptions &AnOpts) {
AnOpts.CheckersAndPackages = {{"test.CallDescChecker", true}};
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
Registry.addChecker<CallDescChecker>("test.CallDescChecker", "Description",
"");
Registry.addChecker<CallDescChecker>("test.CallDescChecker",
"MockDescription");
});
}

View File

@ -56,7 +56,7 @@ void addCXXDeallocatorChecker(AnalysisASTConsumer &AnalysisConsumer,
AnOpts.CheckersAndPackages = {{"test.CXXDeallocator", true}};
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
Registry.addChecker<CXXDeallocatorChecker>("test.CXXDeallocator",
"Description", "");
"MockDescription");
});
}

View File

@ -33,10 +33,8 @@ void addEvalFooCheckers(AnalysisASTConsumer &AnalysisConsumer,
AnOpts.CheckersAndPackages = {{"test.EvalFoo1", true},
{"test.EvalFoo2", true}};
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
Registry.addChecker<EvalCallFoo1>("test.EvalFoo1", "EmptyDescription",
"EmptyDocsUri");
Registry.addChecker<EvalCallFoo2>("test.EvalFoo2", "EmptyDescription",
"EmptyDocsUri");
Registry.addChecker<EvalCallFoo1>("test.EvalFoo1", "MockDescription");
Registry.addChecker<EvalCallFoo2>("test.EvalFoo2", "MockDescription");
});
}
} // namespace

View File

@ -78,7 +78,7 @@ void addExprEngineVisitPreChecker(AnalysisASTConsumer &AnalysisConsumer,
AnOpts.CheckersAndPackages = {{"ExprEngineVisitPreChecker", true}};
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
Registry.addChecker<ExprEngineVisitPreChecker>("ExprEngineVisitPreChecker",
"Desc", "DocsURI");
"MockDescription");
});
}
@ -87,7 +87,7 @@ void addExprEngineVisitPostChecker(AnalysisASTConsumer &AnalysisConsumer,
AnOpts.CheckersAndPackages = {{"ExprEngineVisitPostChecker", true}};
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
Registry.addChecker<ExprEngineVisitPostChecker>(
"ExprEngineVisitPostChecker", "Desc", "DocsURI");
"ExprEngineVisitPostChecker", "MockDescription");
});
}
@ -95,8 +95,8 @@ void addMemAccessChecker(AnalysisASTConsumer &AnalysisConsumer,
AnalyzerOptions &AnOpts) {
AnOpts.CheckersAndPackages = {{"MemAccessChecker", true}};
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
Registry.addChecker<MemAccessChecker>("MemAccessChecker", "Desc",
"DocsURI");
Registry.addChecker<MemAccessChecker>("MemAccessChecker",
"MockDescription");
});
}

View File

@ -92,8 +92,8 @@ void addFalsePositiveGenerator(AnalysisASTConsumer &AnalysisConsumer,
AnOpts.CheckersAndPackages = {{"test.FalsePositiveGenerator", true},
{"debug.ViewExplodedGraph", false}};
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
Registry.addChecker<FalsePositiveGenerator>(
"test.FalsePositiveGenerator", "EmptyDescription", "EmptyDocsUri");
Registry.addChecker<FalsePositiveGenerator>("test.FalsePositiveGenerator",
"MockDescription");
});
}

View File

@ -46,7 +46,7 @@ void addDescriptiveNameChecker(AnalysisASTConsumer &AnalysisConsumer,
AnOpts.CheckersAndPackages = {{"DescriptiveNameChecker", true}};
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
Registry.addChecker<DescriptiveNameChecker>("DescriptiveNameChecker",
"Desc", "DocsURI");
"MockDescription");
});
}

View File

@ -140,7 +140,7 @@ void addNonThoroughStatefulChecker(AnalysisASTConsumer &AnalysisConsumer,
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
Registry
.addChecker<StatefulChecker<NonThoroughErrorNotPreventedFuncVisitor>>(
"test.StatefulChecker", "Description", "");
"test.StatefulChecker", "MockDescription");
});
}
@ -233,7 +233,7 @@ void addThoroughStatefulChecker(AnalysisASTConsumer &AnalysisConsumer,
AnOpts.CheckersAndPackages = {{"test.StatefulChecker", true}};
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
Registry.addChecker<StatefulChecker<ThoroughErrorNotPreventedFuncVisitor>>(
"test.StatefulChecker", "Description", "");
"test.StatefulChecker", "MockDescription");
});
}

View File

@ -37,7 +37,7 @@ void addFlagFlipperChecker(AnalysisASTConsumer &AnalysisConsumer,
AnOpts.CheckersAndPackages = {{"test.FlipFlagOnCheckLocation", true}};
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
Registry.addChecker<FlipFlagOnCheckLocation>("test.FlipFlagOnCheckLocation",
"Description", "");
"MockDescription");
});
}

View File

@ -44,7 +44,7 @@ void addCustomChecker(AnalysisASTConsumer &AnalysisConsumer,
AnalyzerOptions &AnOpts) {
AnOpts.CheckersAndPackages = {{"test.CustomChecker", true}};
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
Registry.addChecker<CustomChecker>("test.CustomChecker", "Description", "");
Registry.addChecker<CustomChecker>("test.CustomChecker", "MockDescription");
});
}
@ -73,8 +73,8 @@ void addLocIncDecChecker(AnalysisASTConsumer &AnalysisConsumer,
AnalyzerOptions &AnOpts) {
AnOpts.CheckersAndPackages = {{"test.LocIncDecChecker", true}};
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
Registry.addChecker<CustomChecker>("test.LocIncDecChecker", "Description",
"");
Registry.addChecker<CustomChecker>("test.LocIncDecChecker",
"MockDescription");
});
}
@ -119,10 +119,10 @@ bool shouldRegisterCheckerRegistrationOrderPrinter(const CheckerManager &mgr) {
void addCheckerRegistrationOrderPrinter(CheckerRegistry &Registry) {
Registry.addChecker(registerCheckerRegistrationOrderPrinter,
shouldRegisterCheckerRegistrationOrderPrinter,
"test.RegistrationOrder", "Description", "", false);
"test.RegistrationOrder", "Description");
}
#define UNITTEST_CHECKER(CHECKER_NAME, DIAG_MSG) \
#define UNITTEST_CHECKER(CHECKER_NAME) \
class CHECKER_NAME : public Checker<check::PreStmt<DeclStmt>> { \
public: \
void checkPreStmt(const DeclStmt *DS, CheckerContext &C) const {} \
@ -137,11 +137,11 @@ void addCheckerRegistrationOrderPrinter(CheckerRegistry &Registry) {
} \
void add##CHECKER_NAME(CheckerRegistry &Registry) { \
Registry.addChecker(register##CHECKER_NAME, shouldRegister##CHECKER_NAME, \
"test." #CHECKER_NAME, "Description", "", false); \
"test." #CHECKER_NAME, "Description"); \
}
UNITTEST_CHECKER(StrongDep, "Strong")
UNITTEST_CHECKER(Dep, "Dep")
UNITTEST_CHECKER(StrongDep)
UNITTEST_CHECKER(Dep)
bool shouldRegisterStrongFALSE(const CheckerManager &mgr) {
return false;
@ -154,7 +154,7 @@ void addDep(AnalysisASTConsumer &AnalysisConsumer,
{"test.RegistrationOrder", true}};
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
Registry.addChecker(registerStrongDep, shouldRegisterStrongFALSE,
"test.Strong", "Description", "", false);
"test.Strong", "Description");
addStrongDep(Registry);
addDep(Registry);
addCheckerRegistrationOrderPrinter(Registry);
@ -172,7 +172,7 @@ TEST(RegisterDeps, UnsatisfiedDependency) {
// Weak checker dependencies.
//===----------------------------------------------------------------------===//
UNITTEST_CHECKER(WeakDep, "Weak")
UNITTEST_CHECKER(WeakDep)
void addWeakDepCheckerBothEnabled(AnalysisASTConsumer &AnalysisConsumer,
AnalyzerOptions &AnOpts) {
@ -225,8 +225,8 @@ void addWeakDepCheckerDepUnspecified(AnalysisASTConsumer &AnalysisConsumer,
});
}
UNITTEST_CHECKER(WeakDep2, "Weak2")
UNITTEST_CHECKER(Dep2, "Dep2")
UNITTEST_CHECKER(WeakDep2)
UNITTEST_CHECKER(Dep2)
void addWeakDepHasWeakDep(AnalysisASTConsumer &AnalysisConsumer,
AnalyzerOptions &AnOpts) {

View File

@ -68,8 +68,7 @@ static void addSimplifyChecker(AnalysisASTConsumer &AnalysisConsumer,
AnalyzerOptions &AnOpts) {
AnOpts.CheckersAndPackages = {{"SimplifyChecker", true}};
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
Registry.addChecker<SimplifyChecker>("SimplifyChecker", "EmptyDescription",
"EmptyDocsUri");
Registry.addChecker<SimplifyChecker>("SimplifyChecker", "MockDescription");
});
}

View File

@ -139,10 +139,10 @@ class SValTest : public testing::TestWithParam<TestClangConfig> {};
\
void add##NAME##SValCollector(AnalysisASTConsumer &AnalysisConsumer, \
AnalyzerOptions &AnOpts) { \
AnOpts.CheckersAndPackages = {{"test.##NAME##SValCollector", true}}; \
AnOpts.CheckersAndPackages = {{"test." #NAME "SValColl", true}}; \
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) { \
Registry.addChecker<NAME##SValCollector>("test.##NAME##SValCollector", \
"Description", ""); \
Registry.addChecker<NAME##SValCollector>("test." #NAME "SValColl", \
"MockDescription"); \
}); \
} \
\

View File

@ -49,9 +49,9 @@ void addTestReturnValueUnderConstructionChecker(
AnOpts.CheckersAndPackages =
{{"test.TestReturnValueUnderConstruction", true}};
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
Registry.addChecker<TestReturnValueUnderConstructionChecker>(
"test.TestReturnValueUnderConstruction", "", "");
});
Registry.addChecker<TestReturnValueUnderConstructionChecker>(
"test.TestReturnValueUnderConstruction", "MockDescription");
});
}
TEST(TestReturnValueUnderConstructionChecker,