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的源码实现目录如下:
头文件为clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h
源文件为clang/lib/StaticAnalyzer/Core/CheckerContext.cpp
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
生成当前的可达程序图:
(gdb) p 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(), false, nullptr, 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 MarkAsSink和ExplodedNode *P的值分别为false、nullptr。所以,成员函数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 MarkAsSink和ExplodedNode *P的值分别为false、Pred。所以,上述代码的逻辑为:以指定节点作为前继节点创建新节点,并且新节点不作为该执行路径上的最后一个节点,意味着符号执行引擎可以继续探索该执行路径。
因此,类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 MarkAsSink和ExplodedNode *P的值分别为true、Pred。所以,上述代码的逻辑为:以指定节点作为前继节点创建新节点,并且新节点作为该执行路径上的最后一个节点,意味着符号执行引擎不会继续探索该执行路径。
因此,类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) | 以当前节点作为前继节点扩展可达程序图。如果成功,则返回新创建的节点,并且符号执行引擎不会继续探索该执行路径;否则,返回当前节点 | 
下一篇:LLVM 之 Clang 源码分析篇(7):clang::ento::RangedConstraintManager 类
上一篇:LLVM 之 Clang 源码分析篇(5):clang::ento::Checker< CHECK1, CHECKs > 类模板