Clang静态程序分析

Clang的静态分析器通过模拟源码执行路径,利用ProgramState和ExplodedGraph来跟踪变量和表达式状态。Checker通过访问分析引擎交互,检查条件并可能报告bug。SVal用于表示表达式的语义值,包括具体值、符号值和内存地址。创建Checker需明确追踪事件和状态管理。分析器通过可达性分析发现bug,同时使用不可变的ProgramState和ExplodedNode确保分析的正确性。Checker之间通过visitor接口协作,当发现bug时,通过BugReporter报告。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

程序分析

Clang checker静态分析

10.247.7.109

分析器就是一个源码模拟器,跟踪所有可能的执行路径。程序执行过程中的状态,比如变量的值或者一个表达式结果,通过ProgramState进行封装,程序中的一个点就是ProgramPoint,ProgramPoint和ProgramState结合起来得到的就是ExplodedGraph中的一个节点 。

单个Checker通过操作分析状态来工作,它们通过访问接口和分析引擎交互。比如说GRExprEngine通过调用PreVisitCallExpr()方法指示Checker去分析CallExpr,然后Checker就会去检查所有的先决条件是否满足。Checker在检查过程中可能什么也不做,也可能更新状态,得到新的ProgramState和ExplodedNode。一旦它发现了一个bug,就会将报错路径中最后一个ExplodedNode提供给BugReporter,并通知BugReporter报告这个bug。

SVal对象用来表示表达式的语义值。能表示具体整型,符号值,或者内存地址(内存范围)。也就是不同值、符号或其他的联合。

创建checker有两点建议:

  1. 需要追踪什么事件?
  2. Checker需要存储什么状态?

程序状态:变量和表达式的值。封装为ProgramState。

程序点:程序执行的位置。封装为ProgramPoint。

扩展图:程序状态和程序点是扩展图中的一个节点。扩展图封装为ExplodedGraph。

控制流图:扩展控制流图的边可以引申出扩展图。

静态分析器对扩展图进行可达性分析,从根节点(包含程序入口点和初始状态)开始,通过分析表达式来模拟传输过程。表达式的分析可以引起状态变化,从而得到一个新的程序点和新的状态构成的新的扩展图节点。在访问节点时,如果满足bug条件,则可以找出一个程序的bug。

静态分析器通过状态的分支跟踪多个执行路径。在true的分支上,分支条件被认为是true,在false的分支上,分支条件被认为是false。这种假设为程序的数值创建了约束,这些约束被记录在ProgramState中(并且被ConstraintManager管理)。

ProgramState和ExplodedNode一旦被创建,就不可变。这非常关键,因为ExplodedGraph表示着从根节点开始已分析程序的行为。为了方便,使用ImmutableMaps数据结构来进行交互。

最后,独立的Checkers也通过管理分析状态来工作。checker引擎通过visitor接口来与它们进行交互。例如,GRExprEngine调用PreVisitCallExpr()函数来告诉checker即将分析一个CallExpr,然后checker会check所有可能不满足的preconditions。

Checkers什么也不会做,也不会生成ProgramState和ExplodedNode。如果发现一个bug,就会报告给BugReporter对象,并提供路径中最后一个触发问题的ExplodedNode。

Bug Reports

最常用的两个类BugType和BugReport。

BugType表示bug的类型。

BugReport用于表示指定的bug。需要三个参数才能创建一个BugReport:

  1.  Bug类型。BugType的实例。
    
  2.  简短描述字符串。Bug描述,用于显示。
    
  3.  Bug出现的context。包括bug在程序中的位置和程序状态。封装为ExplodedNode。
    

为了获得正确的ExplodedNode,必须决定在路径上是否继续。这取决于bug是否会阻止程序继续分析。如果是资源泄露,则不应该停止。如果是空指针,则应该停止分析。

如果分析能继续,正确的ExplodedNode是最近使用的,不需要修改就可以传给BugReport。可以调用CheckerContext::addTransition来获取ExplodedNode。

如果分析不能继续,需要将当前状态传递给sink node,这样后面不会继续分析。可以通过调用CheckerContext::generateSink函数来实现。这个函数和CheckerContext::addTransition一样,可以返回ExplodedNode,但是将状态标记为sink node。

一旦创建好了BugReport,可以调用CheckerContext::emitReport将其传给Analyzer core。

store

Store在每个State下存在,存储着内存位置的SVal到变量的值的SVal的映射,Store允许我们使用Bind一类的方法向store中手动绑定某一对映射,同样的也允许我们使用removeBinding手动移除某一对映射

ProgramState分为五部分内容:这里需要先了解一个内容,clang的内存模型的设计是var->loc->sval,也就是变量到内存到符号。

     1.Environment:包含了当前节点能引用到的所有表达式和他们的符号的映射关系

     2.Region Store:包含了内存区域到符号的映射关系。

     3.Range Constraints:约束

     4.Taint:从不安全的来源获得的符号值的登记表

     5.Generic Data Map:存储用户定义的数据

http://3ms.huawei.com/km/blogs/details/9661651

http://3ms.huawei.com/km/blogs/details/8996913

  1. 除0

CheckerContext: 用于为当前执行的checker提供上下文信息

namespace {
class DivZeroChecker : public Checker< check::PreStmt<BinaryOperator> > {
  mutable std::unique_ptr<BuiltinBug> BT;
  void reportBug(const char *Msg, ProgramStateRef StateZero, CheckerContext &C,
                 std::unique_ptr<BugReporterVisitor> Visitor = nullptr) const;

public:
  void checkPreStmt(const BinaryOperator *B, CheckerContext &C) const;
};
} // end anonymous namespace

static const Expr *getDenomExpr(const ExplodedNode *N) {
  const Stmt *S = N->getLocationAs<PreStmt>()->getStmt();
  if (const auto *BE = dyn_cast<BinaryOperator>(S))
    return BE->getRHS();
  return nullptr;
}

void DivZeroChecker::reportBug(
    const char *Msg, ProgramStateRef StateZero, CheckerContext &C,
    std::unique_ptr<BugReporterVisitor> Visitor) const {
  if (ExplodedNode *N = C.generateErrorNode(StateZero)) {
    if (!BT)
      BT.reset(new BuiltinBug(this, "Division by zero"));

    auto R = std::make_unique<PathSensitiveBugReport>(*BT, Msg, N);
    R->addVisitor(std::move(Visitor));
    bugreporter::trackExpressionValue(N, getDenomExpr(N), *R);
    C.emitReport(std::move(R));
  }
}

void DivZeroChecker::checkPreStmt(const BinaryOperator *B,
                                  CheckerContext &C) const {
  BinaryOperator::Opcode Op = B->getOpcode();
  if (Op != BO_Div &&
      Op != BO_Rem &&
      Op != BO_DivAssign &&
      Op != BO_RemAssign)
    return;

  if (!B->getRHS()->getType()->isScalarType())
    return;

  SVal Denom = C.getSVal(B->getRHS());
  Optional<DefinedSVal> DV = Denom.getAs<DefinedSVal>();

  // Divide-by-undefined handled in the generic checking for uses of
 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值