High-level answer
You can't just "match" an expression whose value is NULL. AST matching can only inspect the syntax of the argument, so if the argument is not a literal, you don't know if it might be NULL.
Instead, you need to use a flow-sensitive checker that queries the Clang SA constraint engine. The constraint engine tracks values as they flow through the program.
The core of such a checker looks like this:
bool NullArgChecker::evalCall(const CallExpr *CE, CheckerContext &C) const {
ProgramStateRef state = C.getState();
auto SVal = C.getSVal(CE->getArg(0)).getAs<DefinedOrUnknownSVal>();
if (SVal) {
ConditionTruthVal Nullness = state->isNull(*SVal);
if (Nullness.isConstrainedTrue()) {
Given a call expression CE
, we get its first argument, then query the CheckerContext
for the symbolic value SVal
associated with the first argument. We then ask if that value is known to be NULL.
Complete example
Here is a complete example checker that reports a warning every time it sees a value known to be NULL being passed as the first argument of any function.
NullArgChecker.cpp:
// NullArgChecker.cpp
// https://stackoverflow.com/questions/57665383/how-can-i-match-a-pointer-to-a-null-object
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
using namespace clang;
using namespace ento;
namespace {
class NullArgChecker : public Checker< eval::Call > {
mutable std::unique_ptr<BuiltinBug> BT_nullarg;
public:
NullArgChecker() {}
bool evalCall(const CallExpr *CE, CheckerContext &C) const;
};
} // end anonymous namespace
bool NullArgChecker::evalCall(const CallExpr *CE, CheckerContext &C) const {
ProgramStateRef state = C.getState();
auto SVal = C.getSVal(CE->getArg(0)).getAs<DefinedOrUnknownSVal>();
if (SVal) {
// This is the core of this example checker: we query the constraint
// engine to see if the symbolic value associated with the first
// argument is known to be NULL along the current path.
ConditionTruthVal Nullness = state->isNull(*SVal);
if (Nullness.isConstrainedTrue()) {
// Create a warning for this condition.
ExplodedNode *N = C.generateErrorNode();
if (N) {
if (!BT_nullarg) {
BT_nullarg.reset(new BuiltinBug(
this, "Null Argument", "The first argument is NULL."));
}
C.emitReport(llvm::make_unique<BugReport>(
*BT_nullarg, BT_nullarg->getDescription(), N));
}
}
}
return false;
}
void ento::registerNullArgChecker(CheckerManager &mgr) {
mgr.registerChecker<NullArgChecker>();
}
bool ento::shouldRegisterNullArgChecker(const LangOptions &LO) {
return true;
}
Changes to other files to hook this in:
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -148,6 +148,10 @@ def NonnullGlobalConstantsChecker: Checker<"NonnilStringCon
stants">,
let ParentPackage = CoreAlpha in {
+def NullArgChecker : Checker<"NullArg">,
+ HelpText<"Check for passing a NULL argument">,
+ Documentation<NotDocumented>;
+
def BoolAssignmentChecker : Checker<"BoolAssignment">,
HelpText<"Warn about assigning non-{0,1} values to Boolean variables">,
Documentation<HasAlphaDocumentation>;
--- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -62,6 +62,7 @@ add_clang_library(clangStaticAnalyzerCheckers
NonNullParamChecker.cpp
NonnullGlobalConstantsChecker.cpp
NullabilityChecker.cpp
+ NullArgChecker.cpp
NumberObjectConversionChecker.cpp
ObjCAtSyncChecker.cpp
ObjCAutoreleaseWriteChecker.cpp
Example input to test it on:
// nullargpp.cpp
// Testing NullArg checker with C++.
#include <stddef.h> // NULL
void somefunc(int*);
void nullarg1()
{
somefunc(NULL); // reported
somefunc(0); // reported
somefunc(nullptr); // reported
}
void nullarg2()
{
int *p = 0;
somefunc(p); // reported
}
void nullarg3(int *p)
{
if (p) {
somefunc(p); // not reported
}
else {
somefunc(p); // reported
}
}
void not_nullarg(int *p)
{
somefunc(p); // not reported
}
Example run:
$ g++ -std=c++11 -E -o nullargpp.ii nullargpp.cpp
$ ~/bld/llvm-project/build/bin/clang -cc1 -analyze -analyzer-checker=alpha.core.NullArg nullargpp.ii
nullargpp.cpp:10:3: warning: The first argument is NULL
somefunc(
^~~~~~~~~
nullargpp.cpp:11:3: warning: The first argument is NULL
somefunc(0);
^~~~~~~~~~~
nullargpp.cpp:12:3: warning: The first argument is NULL
somefunc(nullptr);
^~~~~~~~~~~~~~~~~
nullargpp.cpp:18:3: warning: The first argument is NULL
somefunc(p);
^~~~~~~~~~~
nullargpp.cpp:27:5: warning: The first argument is NULL
somefunc(p);
^~~~~~~~~~~
5 warnings generated.
For maximum specificity, the above changes were made to llvm-project commit 05efe0fdc4 (March 2019), running on Linux, but should work with any Clang v9.