如何将指向空对象的指针匹配?

Tom*_*ter 6 clang-static-analyzer

我想在所有方面匹配函数的特定参数可以为null。现在我正在使用

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

但是,这与以下代码不匹配:

void* nullObj = nullptr;
function(nullptr, false, false, nullObj);
Run Code Online (Sandbox Code Playgroud)

是否可以/容易地跟踪并匹配它?现在,我有一个非常简单的匹配器,但我想这种分析需要更多的逻辑吗?

Sco*_*eak 1

高水平答案

您不能只“匹配”值为 NULL 的表达式。AST 匹配只能检查参数的语法,因此如果参数不是文字,您不知道它是否可能为 NULL。

相反,您需要使用流敏感检查器来查询 Clang SA 约束引擎。约束引擎跟踪流经程序的值。

这种检查器的核心如下所示:

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()) {
Run Code Online (Sandbox Code Playgroud)

给定一个调用表达式CE,我们获取它的第一个参数,然后查询与第一个参数关联的CheckerContext符号值。SVal然后我们询问该值是否已知为 NULL。

完整示例

下面是一个完整的示例检查器,每次看到已知为 NULL 的值作为任何函数的第一个参数传递时都会报告警告。

NullArgChecker.cpp:

// NullArgChecker.cpp
// /sf/ask/4036576841/

#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;
}
Run Code Online (Sandbox Code Playgroud)

更改其他文件以将其挂钩:

--- 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
Run Code Online (Sandbox Code Playgroud)

用于测试的示例输入:

// 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
}
Run Code Online (Sandbox Code Playgroud)

运行示例:

$ 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.
Run Code Online (Sandbox Code Playgroud)

为了获得最大的特异性,上述更改是对 llvm-project 提交 05efe0fdc4(2019 年 3 月)进行的,在 Linux 上运行,但应该适用于任何 Clang v9。