clang - 如何使用 Clang 的静态分析器跟踪变量?

标签 clang static-analysis clang-static-analyzer

假设我正在使用以下 C 代码片段:

void inc(int *num) {*num++;}
void dec(int *num) {*num--;}

void f(int var) {
    inc(&var);
    dec(&var);
}

通过使用静态分析器,我希望能够判断 var 的值在函数执行期间是否没有改变。我知道我必须自己保持它的状态(这是编写 Clang 检查器的重点),但是我在获取此变量的唯一引用时遇到了麻烦。

例如:如果我使用以下 API
void MySimpleChecker::checkPostCall(const CallEvent &Call,
                                    CheckerContext &C) const {
    SymbolRef MyArg = Call.getArgSVal(0).getAsSymbol();
}

我希望它在我的检查器的上下文中返回一个指向该符号表示的指针。但是,我总是收到 0 以这种方式使用它进入 MyArg。这发生在 pre 和 post 回调中的 inc 和 dec 函数中。

我在这里缺少什么?我弄错了哪些概念?

注意:我目前正在阅读 Clang CFE Internals Manual我已经阅读了优秀的 How to Write a Checker in 24 Hours Material 。到目前为止,我仍然找不到我的答案。

最佳答案

问题的解释

具体来说,您要计算对 inc 的调用次数。和 dec应用于每个变量并在它们不平衡函数中的某些路径时报告。

通常,您想知道如何将抽象值(这里是数字)与程序变量相关联,并能够沿每个执行路径更新和查询该值。

高层回答

而教程检查器 SimpleStreamChecker.cpp 将抽象值与存储在变量中的值相关联,这里我们希望将抽象值与变量本身相关联。就是这样 IteratorChecker.cpp 在跟踪容器时会这样做,所以我的解决方案基于它。

在静态分析器的抽象状态中,每个变量都由 MemRegion 表示。目的。所以第一步是制作一张 map ,其中MemRegion是关键:

REGISTER_MAP_WITH_PROGRAMSTATE(TrackVarMap, MemRegion const *, int)

接下来,当我们有一个 SVal 对应于指向变量的指针,我们可以使用 SVal::getAsRegion 得到对应的MemRegion .例如,给定 CallEvent , call ,第一个参数是一个指针,我们可以这样做:

    if (MemRegion const *region = call.getArgSVal(0).getAsRegion()) {

获取 region指针指向的那个。

然后,我们可以使用 region 访问我们的 map 。作为其关键:

      state = state->set<TrackVarMap>(region, newValue);

最后,在 checkDeadSymbols ,我们使用 SymbolReaper::isLiveRegion 检测区域(变量)何时超出范围:

  const TrackVarMapTy &Map = state->get<TrackVarMap>();
  for (auto const &I : Map) {
    MemRegion const *region = I.first;
    int delta = I.second;
    if (SymReaper.isLiveRegion(region) || (delta==0))
      continue;              // Not dead, or unchanged; skip.

完整示例

为了演示,这里有一个完整的检查器,报告 inc 的不平衡使用。和 dec :

// TrackVarChecker.cpp
// https://stackoverflow.com/questions/23448540/how-to-keep-track-of-a-variable-with-clangs-static-analyzer

#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/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"

using namespace clang;
using namespace ento;

namespace {
class TrackVarChecker
  : public Checker< check::PostCall,
                    check::DeadSymbols >
{
  mutable IdentifierInfo *II_inc, *II_dec;
  mutable std::unique_ptr<BuiltinBug> BT_modified;

public:
  TrackVarChecker() : II_inc(nullptr), II_dec(nullptr) {}

  void checkPostCall(CallEvent const &Call, CheckerContext &C) const;
  void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
};
} // end anonymous namespace

// Map from memory region corresponding to a variable (that is, the
// variable itself, not its current value) to the difference between its
// current and original value.
REGISTER_MAP_WITH_PROGRAMSTATE(TrackVarMap, MemRegion const *, int)

void TrackVarChecker::checkPostCall(CallEvent const &call, CheckerContext &C) const
{
  const FunctionDecl *FD = dyn_cast<FunctionDecl>(call.getDecl());
  if (!FD || FD->getKind() != Decl::Function) {
    return;
  }

  ASTContext &Ctx = C.getASTContext();
  if (!II_inc) {
    II_inc = &Ctx.Idents.get("inc");
  }
  if (!II_dec) {
    II_dec = &Ctx.Idents.get("dec");
  }

  if (FD->getIdentifier() == II_inc || FD->getIdentifier() == II_dec) {
    // We expect the argument to be a pointer.  Get the memory region
    // that the pointer points at.
    if (MemRegion const *region = call.getArgSVal(0).getAsRegion()) {
      // Increment the associated value, creating it first if needed.
      ProgramStateRef state = C.getState();
      int delta = (FD->getIdentifier() == II_inc)? +1 : -1;
      int const *curp = state->get<TrackVarMap>(region);
      int newValue = (curp? *curp : 0) + delta;
      state = state->set<TrackVarMap>(region, newValue);
      C.addTransition(state);
    }
  }
}

void TrackVarChecker::checkDeadSymbols(
  SymbolReaper &SymReaper, CheckerContext &C) const
{
  ProgramStateRef state = C.getState();
  const TrackVarMapTy &Map = state->get<TrackVarMap>();
  for (auto const &I : Map) {
    // Check for a memory region (variable) going out of scope that has
    // a non-zero delta.
    MemRegion const *region = I.first;
    int delta = I.second;
    if (SymReaper.isLiveRegion(region) || (delta==0)) {
      continue;              // Not dead, or unchanged; skip.
    }

    //llvm::errs() << region << " dead with delta " << delta << "\n";
    if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
      if (!BT_modified) {
        BT_modified.reset(
          new BuiltinBug(this, "Delta not zero",
                         "Variable changed from its original value."));
      }
      C.emitReport(llvm::make_unique<BugReport>(
        *BT_modified, BT_modified->getDescription(), N));
    }
  }
}

void ento::registerTrackVarChecker(CheckerManager &mgr) {
  mgr.registerChecker<TrackVarChecker>();
}

bool ento::shouldRegisterTrackVarChecker(const LangOptions &LO) {
  return true;
}

要将其连接到 Clang 的其余部分,请将条目添加到:
  • clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
  • clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt

  • 测试它的示例输入:

    // trackvar.c
    // Test for TrackVarChecker.
    
    // The behavior of these functions is hardcoded in the checker.
    void inc(int *num);
    void dec(int *num);
    
    void call_inc(int var) {
      inc(&var);
    } // reported
    
    void call_inc_dec(int var) {
      inc(&var);
      dec(&var);
    } // NOT reported
    
    void if_inc(int var) {
      if (var > 2) {
        inc(&var);
      }
    } // reported
    
    void indirect_inc(int val) {
      int *p = &val;
      inc(p);
    } // reported
    

    示例运行:

    $ gcc -E -o trackvar.i trackvar.c
    $ ~/bld/llvm-project/build/bin/clang -cc1 -analyze -analyzer-checker=alpha.core.TrackVar trackvar.i
    trackvar.c:10:1: warning: Variable changed from its original value
    }
    ^
    trackvar.c:21:1: warning: Variable changed from its original value
    }
    ^
    trackvar.c:26:1: warning: Variable changed from its original value
    }
    ^
    3 warnings generated.
    

    关于clang - 如何使用 Clang 的静态分析器跟踪变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23448540/

    相关文章:

    c++ - 为什么在 Clang 中使用 __attribute__((__format__ (__printf__,...) 来避免格式​​字符串不是字符串文字?

    c++ - 在我的项目中使用预编译头文件(clang/llvm,但也包括 gcc)。如何在 make 中部署 -include 选项?

    c - 是否有静态分析工具来计算使用定义链?

    c++ - 用于检测由于类型提升导致的无限循环的静态分析工具?

    xcode - 在 Travis 上使静态分析失败导致构建失败

    objective-c - 是否可以抑制 Xcode 4 静态分析器警告?

    c - 如何启用 clang 静态分析器的 "alpha.security.taint check"检查器

    c++ - clang 6 不支持 unordered_map::merge?

    如果至少未使用 -O2,则 clang 链接器将失败

    c++ - 使用 libclang 从内存中的 C 代码生成程序集