Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I match a pointer to a null object?

I'd like to match on all the ways a particular argument to a function can be null. Right now I'm using

hasArgument(
        3,
        anyOf(
            cxxNullPtrLiteralExpr()
            ,integerLiteral() // Technically this would alert on a constant pointer; but that's madness
        )
    )

However this doesn't match the following code:

void* nullObj = nullptr;
function(nullptr, false, false, nullObj);

Is it possible/easy to track this and match it? Right now I have a very simpler matcher but I guess this type of analysis requires considerably more logic?

like image 294
Tom Ritter Avatar asked Aug 26 '19 21:08

Tom Ritter


1 Answers

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.

like image 191
Scott McPeak Avatar answered Sep 28 '22 16:09

Scott McPeak