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 > 类模板