LLVM 之 Clang 源码分析篇(6):clang::ento::CheckerContext 类

Author: stormQ

Created: Thursday, 27. May 2021 11:14PM

Last Modified: Friday, 28. May 2021 08:06PM



摘要

本文基于release/12.x版本的 LLVM 源码,研究了clang::ento::CheckerContext类中各数据成员表示的意义以及成员函数的作用。从而,有助于理解 Clang 静态分析器源码实现的其他相关部分。


研究过程


clang::ento::CheckerContext的源码实现目录如下:


数据成员

step 1: 数据成员Eng

待补充

step 2: 数据成员Pred

数据成员Pred的定义如下:

25   /// The current exploded(symbolic execution) graph node.
26   ExplodedNode *Pred;

调试plugin.core.DivideZero检查器,在执行到DivZeroChecker.cpp:88行——bool TaintedD = isTainted(C.getState(), *DV);语句时,打印Pred并查看当前的可达程序图。

打印Pred的值:

(gdb) p C.getPredecessor()
$1 = (clang::ento::ExplodedNode *) 0x5555556f5b38

打印Pred的节点标识符:

(gdb) p C.getPredecessor().Id
$2 = 47

生成当前的可达程序图:

(gdbp C.Eng.ViewGraph(0)

可达程序图转换为.png格式(示例):

$ dot -Tpng /tmp/ExprEngine-92cb48.dot -o ExprEngine_92cb48.png

可达程序图如下(部分):

通过生成的可达程序图可以看出,当前可达程序图中包含两条执行路径。其中一条执行路径上有一个节点标识符为 47(即上图中的“node_id”: 47)的节点,并且该节点的后面无其他节点。而回调函数中C.Pred的节点标识符也是 47。

因此,类clang::ento::CheckerContext中的数据成员Pred的作用为:表示可达程序图中该执行路径上的当前节点(后面无其他节点)。

注:笔者猜想,该数据成员的名称之所以命名为Pred,是因为对于将来扩展的新节点而言当前节点可以作为其前继节点。

step 3: 数据成员Changed

数据成员Changed的定义如下:

27   /// The flag is true if the (state of the execution) has been modified
28   /// by the checker using this context. For example, a new transition has been
29   /// added or a bug report issued.
30   bool Changed;

通过搜索源码发现,成员函数CheckerContext()emitReport()addTransitionImpl()会设置该数据成员的值。

成员函数CheckerContext()中设置该数据成员的相关源码如下(定义在 clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h 文件中):

40   CheckerContext(NodeBuilder &builder,
41                  ExprEngine &eng,
42                  ExplodedNode *pred,
43                  const ProgramPoint &loc,
44                  bool wasInlined = false)
45     : Eng(eng),
46       Pred(pred),
47       Changed(false),
48       Location(loc),
49       NB(builder),
50       wasInlined(wasInlined) {
// 省略 ...
53   }

从上面的代码中可以看出,数据成员Changed的初始值为false

成员函数emitReport()中设置该数据成员的相关源码如下(定义在 clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h 文件中):

241   /// Emit the diagnostics report.
242   void emitReport(std::unique_ptr<BugReport> R) {
243     Changed = true;
// 省略 ...
245   }

从上面的代码中可以看出,只要调用了成员函数emitReport(),数据成员Changed的值会被设置为true

成员函数addTransitionImpl()中设置该数据成员的相关源码如下(定义在 clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h 文件中):

374   ExplodedNode *addTransitionImpl(ProgramStateRef State,
375                                  bool MarkAsSink,
376                                  ExplodedNode *P = nullptr,
377                                  const ProgramPointTag *Tag = nullptr)
 
{
// 省略 ...
390     if (!State || (State == Pred->getState() && !Tag && !MarkAsSink))
391       return Pred;
392 
393     Changed = true;
// 省略 ...
404   }

从上面的代码中可以看出,如果调用了成员函数addTransitionImpl()并且成功地创建了新节点,那么数据成员Changed的值会被设置为true

因此,类clang::ento::CheckerContext中的数据成员Changed的作用为:表示该检查器是否更改了执行状态。如果值为true,表示该检查器成功创建了新节点或者报告了程序错误。

step 4: 数据成员Location

待补充

step 5: 数据成员NB

待补充

step 6: 数据成员wasInlined

待补充

返回上一级


成员函数

step 1: 成员函数getSVal()

该函数的源码实现如下(定义在 clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h 文件中):

145   /// Get the value of arbitrary expressions at this point in the path.
146   SVal getSVal(const Stmt *S) const {
147     return Pred->getSVal(S);
148   }

由于类clang::ento::CheckerContext中的数据成员Pred的作用为:表示可达程序图中该执行路径上的当前节点(后面无其他节点)。

因此,类clang::ento::CheckerContext中的成员函数getSVal()的作用为:获取语句S在当前节点中对应的符号。

step 2: 成员函数getState()

该函数的源码实现如下(定义在 clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h 文件中):

71   const ProgramStateRef &getState() const return Pred->getState(); }

由于类clang::ento::CheckerContext中的数据成员Pred的作用为:表示可达程序图中该执行路径上的当前节点(后面无其他节点)。

clang::ento::CheckerContext中的成员函数getState()的作用为:获取当前节点的程序状态。

step 3: 成员函数addTransition(ProgramStateRef, const ProgramPointTag *)

该函数的源码实现如下(定义在 clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h 文件中):

157   /// Generates a new transition in the program state graph
158   /// (ExplodedGraph). Uses the default CheckerContext predecessor node.
159   ///
160   /// @param State The state of the generated node. If not specified, the state
161   ///        will not be changed, but the new node will have the checker's tag.
162   /// @param Tag The tag is used to uniquely identify the creation site. If no
163   ///        tag is specified, a default tag, unique to the given checker,
164   ///        will be used. Tags are used to prevent states generated at
165   ///        different sites from caching out.
166   ExplodedNode *addTransition(ProgramStateRef State = nullptr,
167                               const ProgramPointTag *Tag = nullptr)
 
{
168     return addTransitionImpl(State ? State : getState(), falsenullptr, Tag);
169   }

从上面的代码可以看出,该函数是通过成员函数addTransitionImpl()实现其功能的。

成员函数addTransitionImpl()的源码实现如下(定义在 clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h 文件中):

374   ExplodedNode *addTransitionImpl(ProgramStateRef State,
375                                  bool MarkAsSink,
376                                  ExplodedNode *P = nullptr,
377                                  const ProgramPointTag *Tag = nullptr)
 
{
378     // The analyzer may stop exploring if it sees a state it has previously
379     // visited ("cache out"). The early return here is a defensive check to
380     // prevent accidental caching out by checker API clients. Unless there is a
381     // tag or the client checker has requested that the generated node be
382     // marked as a sink, we assume that a client requesting a transition to a
383     // state that is the same as the predecessor state has made a mistake. We
384     // return the predecessor rather than cache out.
385     //
386     // TODO: We could potentially change the return to an assertion to alert
387     // clients to their mistake, but several checkers (including
388     // DereferenceChecker, CallAndMessageChecker, and DynamicTypePropagation)
389     // rely upon the defensive behavior and would need to be updated.
390     if (!State || (State == Pred->getState() && !Tag && !MarkAsSink))
391       return Pred;
392 
393     Changed = true;
394     const ProgramPoint &LocalLoc = (Tag ? Location.withTag(Tag) : Location);
395     if (!P)
396       P = Pred;
397 
398     ExplodedNode *node;
399     if (MarkAsSink)
400       node = NB.generateSink(LocalLoc, State, P);
401     else
402       node = NB.generateNode(LocalLoc, State, P);
403     return node;
404   }

由于传给addTransitionImpl()函数的参数bool MarkAsSinkExplodedNode *P的值分别为falsenullptr。所以,成员函数addTransition(ProgramStateRef, const ProgramPointTag *)的代码逻辑为:以当前节点作为前继节点创建新节点,并且新节点不作为该执行路径上的最后一个节点,意味着符号执行引擎可以继续探索该执行路径。

因此,类clang::ento::CheckerContext中的成员函数addTransition(ProgramStateRef, const ProgramPointTag *)的作用为:以当前节点作为前继节点扩展可达程序图。如果成功,则返回新创建的节点,并且符号执行引擎可以继续探索该执行路径;否则,返回当前节点。

需要注意的是, 在某些情况下,调用该成员函数实际上不会真正地扩展可达程序图。这些情况如下表所示。

实际不会生成新节点的情况
C.addTransition((ProgramStateRef)nullptr);
C.addTransition((ProgramStateRef)nullptr, (const ProgramPointTag *)nullptr);
C.addTransition(C.getState());
C.addTransition(C.getState(), (const ProgramPointTag *)nullptr);

另外,需要注意的是, 该成员函数可用于创建并行节点(这些并行节点的前继节点都是当前节点)。

step 4: 成员函数addTransition(ProgramStateRef, ExplodedNode *, const ProgramPointTag *)

该函数的源码实现如下(定义在 clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h 文件中):

171   /// Generates a new transition with the given predecessor.
172   /// Allows checkers to generate a chain of nodes.
173   ///
174   /// @param State The state of the generated node.
175   /// @param Pred The transition will be generated from the specified Pred node
176   ///             to the newly generated node.
177   /// @param Tag The tag to uniquely identify the creation site.
178   ExplodedNode *addTransition(ProgramStateRef State, ExplodedNode *Pred,
179                               const ProgramPointTag *Tag = nullptr) {
180     return addTransitionImpl(State, false, Pred, Tag);
181   }

由于传给addTransitionImpl()函数的参数bool MarkAsSinkExplodedNode *P的值分别为falsePred。所以,上述代码的逻辑为:以指定节点作为前继节点创建新节点,并且新节点不作为该执行路径上的最后一个节点,意味着符号执行引擎可以继续探索该执行路径。

因此,类clang::ento::CheckerContext中的成员函数addTransition(ProgramStateRef, ExplodedNode *, const ProgramPointTag *)的作用为:以指定节点作为前继节点扩展可达程序图。如果成功,则返回新创建的节点,并且符号执行引擎可以继续探索该执行路径;否则,返回当前节点。

需要注意的是, 在某些情况下,调用该成员函数实际上不会真正地扩展可达程序图。这些情况如下表所示。

实际不会生成新节点的情况
C.addTransition((ProgramStateRef)nullptr, (ExplodedNode *)nullptr);
C.addTransition((ProgramStateRef)nullptr, (ExplodedNode *)nullptr, (const ProgramPointTag *)nullptr);
C.addTransition((ProgramStateRef)nullptr, C.getPredecessor());
C.addTransition((ProgramStateRef)nullptr, C.getPredecessor(), (const ProgramPointTag *)nullptr);
C.addTransition(C.getState(), C.getPredecessor());
C.addTransition(C.getState(), C.getPredecessor(), (const ProgramPointTag *)nullptr);
C.addTransition(C.getState(), (ExplodedNode *)nullptr);
C.addTransition(C.getState(), (ExplodedNode *)nullptr, (const ProgramPointTag *)nullptr);

另外,需要注意的是, 该成员函数可用于创建串行节点。

step 5: 成员函数generateSink()

该函数的源码实现如下(定义在 clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h 文件中):

183   /// Generate a sink node. Generating a sink stops exploration of the
184   /// given path. To create a sink node for the purpose of reporting an error,
185   /// checkers should use generateErrorNode() instead.
186   ExplodedNode *generateSink(ProgramStateRef State, ExplodedNode *Pred,
187                              const ProgramPointTag *Tag = nullptr)
 
{
188     return addTransitionImpl(State ? State : getState(), true, Pred, Tag);
189   }

由于传给addTransitionImpl()函数的参数bool MarkAsSinkExplodedNode *P的值分别为truePred。所以,上述代码的逻辑为:以指定节点作为前继节点创建新节点,并且新节点作为该执行路径上的最后一个节点,意味着符号执行引擎不会继续探索该执行路径。

因此,类clang::ento::CheckerContext中的成员函数generateSink()的作用为:以指定节点作为前继节点扩展可达程序图。如果成功,则返回新创建的节点,并且符号执行引擎不会继续探索该执行路径;否则,返回当前节点。

step 6: 成员函数generateErrorNode()

该函数的源码实现如下(定义在 clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h 文件中):

199   /// Generate a transition to a node that will be used to report
200   /// an error. This node will be a sink. That is, it will stop exploration of
201   /// the given path.
202   ///
203   /// @param State The state of the generated node.
204   /// @param Tag The tag to uniquely identify the creation site. If null,
205   ///        the default tag for the checker will be used.
206   ExplodedNode *generateErrorNode(ProgramStateRef State = nullptr,
207                                   const ProgramPointTag *Tag = nullptr)
 
{
208     return generateSink(State, Pred,
209                        (Tag ? Tag : Location.getTag()));
210   }

由于传给generateSink()函数的参数ExplodedNode *Pred的值为Pred。所以,上述代码的逻辑为:以当前节点作为前继节点创建新节点,并且新节点作为该执行路径上的最后一个节点,意味着符号执行引擎不会继续探索该执行路径。

因此,类clang::ento::CheckerContext中的成员函数generateErrorNode()的作用为:以当前节点作为前继节点扩展可达程序图。如果成功,则返回新创建的节点,并且符号执行引擎不会继续探索该执行路径;否则,返回当前节点。

返回上一级


研究结论

clang::ento::CheckerContext的作用为: 封装路径敏感检查器的上下文信息。

其数据成员及用途如下:

数据成员 数据类型 用途
Eng clang::ento::ExprEngine &
Pred clang::ento::ExplodedNode * 表示可达程序图中该执行路径上的当前节点(后面无其他节点)
Changed bool 表示该检查器是否更改了执行状态。如果值为true,表示该检查器成功创建了新节点或者报告了程序错误
Location const clang::ProgramPoint
NB clang::ento::NodeBuilder &
wasInlined const bool

其成员函数及用途如下:

成员函数 函数原型 用途
getSVal() SVal getSVal(const Stmt *S) const 获取语句S在当前节点中对应的符号
getState() const ProgramStateRef &getState() const 获取当前节点的程序状态
addTransition(ProgramStateRef, const ProgramPointTag *) ExplodedNode *addTransition(ProgramStateRef State = nullptr, const ProgramPointTag *Tag = nullptr) 以当前节点作为前继节点扩展可达程序图。如果成功,则返回新创建的节点,并且符号执行引擎可以继续探索该执行路径;否则,返回当前节点
addTransition(ProgramStateRef, ExplodedNode *, const ProgramPointTag *) ExplodedNode *addTransition(ProgramStateRef State, ExplodedNode *Pred, const ProgramPointTag *Tag = nullptr) 以指定节点作为前继节点扩展可达程序图。如果成功,则返回新创建的节点,并且符号执行引擎可以继续探索该执行路径;否则,返回当前节点
generateSink() ExplodedNode *generateSink(ProgramStateRef State, ExplodedNode *Pred, const ProgramPointTag *Tag = nullptr) 以指定节点作为前继节点扩展可达程序图。如果成功,则返回新创建的节点,并且符号执行引擎不会继续探索该执行路径;否则,返回当前节点
generateErrorNode() ExplodedNode *generateErrorNode(ProgramStateRef State = nullptr, const ProgramPointTag *Tag = nullptr) 以当前节点作为前继节点扩展可达程序图。如果成功,则返回新创建的节点,并且符号执行引擎不会继续探索该执行路径;否则,返回当前节点

References


下一篇:LLVM 之 Clang 源码分析篇(7):clang::ento::RangedConstraintManager 类

上一篇:LLVM 之 Clang 源码分析篇(5):clang::ento::Checker< CHECK1, CHECKs > 类模板

首页