引子
在clang static analyzer源码分析(二)中我们简要介绍了ExplodedGraph以及如何调试clang static analyzer。今天这篇文章重点分析一下clang static analyzer对于path-sentitive analysis的代码架构。
path-sensitive analysis的源码架构
clang static analyzer关于path-sensitive analysis的源码结构如下:
note:clang 3.6
- PathSensitive/
APSIntType.h
AnalysisManager.h
BasicValueFactory.h
BlockCounter.h
CallEvent.h
CheckerContext.h
CheckerHelpers.h
ConstraintManager.h
CoreEngine.h
DynamicTypeInfo.h
Environment.h
ExplodedGraph.h
ExprEngine.h
FunctionSymmary.h
MemRegion.h
ProgramState_Fwd.h
ProgramState.h
Store.h
StoreRef.h
SubEngine.h
SummaryManager.h
SValBuilder.h
SVals.h
SymbolManager.h
TaintManager.h
TaintManager.h
WorkList.h
下面我们依次介绍各个文件主要内容。
APSIntType.h
这个文件主要定义了 APSInt 类,这个类用于在静态分析的时候表示各种不同类型的Integer(常量)。
This file implements the APSInt class, which is a simple class that represents an arbitrary sized integer that knows its signedness.
这个类继承自 llvm::APInt,其中APInt的直观解释就是 “arbitrary precision integers“,主要用于unsigned integers,所以APSInt就表示 “arbitrary precision integers singed or unsigned“。
AnalysisManager.h
这个文件声明了AnalysisManager类,这个类前面我们也提到过,含有多个和静态分析的核心数据成员,例如CheckerManager、DiagnosticsEngine以及ASTContext等。然后提供了一些helper方法。
BasicValueFactory.h
这个文件定义了BasicValueFactory.h类,这个类可以说是引擎的整数池,其中存放了在静态分析过程中得到的APSInt。其中还定义了一系列的helper方法,例如根据一个int值通过”getValue(uint64_t X, QualType T)“获取一个APSInt对象,以及根据AST类型QualType通过”getMaxValue(QualType T)“获取该类型的最大值以及最小值等。
在静态分析过程中整形的边界以及类型非常重要,有时会涉及到非常严重的报告,例如”malloc(-1)” 这会分配很大一块内存。或者是”while(s < 300)“中,s是char类型,这回导致死循环。
typdef llvm::FoldingSet<llvm::FoldingSetNodeWrapper<llvm::APSInt>>
APSIntSetTy;
APSIntSetTy APSIntSet;
BlockCounter.h
这个文件定义了BlockCounter类,这个类主要用于记录沿一条路径上,某一基本块总共执行了多少次。
This file defines BlockCounter, an abstract data type used to count the number of times a given block has been visited along a path analyzed by CoreEngine.
CallEvent.h
这个文件定义了静态分析过程中的函数调用,由于在ExplodedGraph中,同一个函数调用在不同的ExplodedNode中算作两种不同的调用,因为调用的Context不相同。
另外,该文件还针对多态定义了一些其他的数据结构。具体的内容等到剖析到clang static analyzer如何符号执行函数调用时在详细介绍。
This file defines CallEvent and its subclasses, which represents path-sensitive instances of different kinds of function and method calls(C, C++ and Objective-C).
CheckerContext.h
该文件定义了CheckerContext类,该类用于给Checker提供上下文信息。随便挑一个Checker,打开源码都可以看到有CheckerContext的影子,例如我们打开DivZeroChecker.cpp,其中有一个回调函数是”checkPreStmt(const BinaryOperator *B, )“,该函数会在分析引擎分析到BinaryOperator之前的时候,调用该回调函数。该函数其中有一个参数是CheckerContext,这个参数基本上可以提供到所有分析要用的信息。例如在获取BinaryOperator右侧符号值的时候就需要用到CheckerContext,另外在下面也需要通过CheckerContext获得ConstrainManager。
// ...
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.getState()->getSVal(B->getRHS(), C.getLocationContext());
Optional<DefinedSVal> DV = Denom.getAs<DefinedSVal>();
// Divied-by-undefined handled in the generic checking for uses of
// undefined values.
if (!DV)
return;
// Check for divide by zero.
ConstraintManager &CM = C.getConstraintManger();
}
// ...
ConstraintManager.h
该文件定义了ConstraintManager类,这个类可以说是clang staic analyzer中最重要的一个类,这个类主要用于管理SymExpr的限制条件。例如在对Branch进行符号执行的时候,需要对未知的符号值进行约束,ConstraintManager提供了很多assume*()方法对符号值进行约束,如果得到了新的约束,则将该约束注册到ProgramState中。
例如ConstraintManager中的assumeSymEQ()方法部分源码如下,如果能够对符号值约束出新的区间,则将这个结果注册到ProgramState中,如最后一行return语句。
ProgramStateRef
RangeConstraintManager::assumeSymEQ(ProgramStateRef, SymbolRef Sym,
const llvm::APSInt &Int,
const llvm::APSInt &Adjustment) {
// Before we do any real work, see if the value can even show up.
APSIntType AdjustmentType(Adjustment);
if (AdjustmentType.testInRange(Int, true) != APSIntType::RTR_Within)
return nullptr;
// [Int-Adjustment, Int-Adjustment]
llvm::APSInt AdjInt = AdjustmentType.convert(Int) - Adjustment;
RangeSet New = GetRange(St, Sym).Intersect(getBasicVals(), F, AdjInt, AdjInt);
return New.isEmpty() ? nullptr : St->set<ConstraintRange>(Sym, New);
}
关于ConstraintManager,我们会在后面进行详细的分析。
CoreEngine.h
该文件定义CoreEngine类,这个类是符号执行的核心类,这个类主要在CFG上进行符号执行,并实时构建出ExplodedGraph。这个类我们会在后面作详细介绍。
This file defines a generic engine for intraprocedural, path-sensitive, dataflow analysis via graph reachability.
Environment.h
Environment负责存储当前程序点所能引用到的表达式到其对应的符号值的映射关系。
if (func(x) + 10 > a) {} else {}
例如在符号执行 “func(x) + 10 > a“的时候,需要首先获取到”func(x)“的值以及变量a的值,此时 { “func(x) -> SVal“, “a -> SVal“} 就是在evaluate “func(x) + 10” > “a” 这条语句时所需要的environment,但是在符号执行下一条语句之前会将上一条语句的environment清除掉。其实这个environment的声明周期只存在于一条语句之内,并且是路径敏感的。详细的我们会在后面继续介绍。
ExplodedGraph.h
该文件定义了两个类,ExplodedNode和ExplodedGraph,我们在前一篇文章中介绍到过exploded graph是clang static analyzer在CFG上进行符号执行的“结果”。
This file defines the template classes ExplodedNode and ExplodedGraph, which represents a path-sensitive, intra-procedural “exploded graph.”
后面我们会详细介绍ExplodedNode和ExplodedGraph这两个类。
ExprEngine.h
该文件定义了ExprEngine类,这个类构建在CoreEngine上,ExprEngine类定义了很多transfer function。
- void ProcessStmt(const CFGStmt S, ExplodedNode *Pred);
- void ProcessInitializer(const CFGInitializer I, ExplodedNode *Pred);
- void ProcessImplicitDtor(const CFGImplicitDtor D, ExplodedNode *Pred);
- void ProcessNewAllocator(const CXXNewExpr *NE, ExplodedNode *Pred);
- …
相应的transfer function有很多,这里就不一一列举了。
FunctionSymmary.h
该文件定义了FunctionSummariesTy类,这个类定义了关于函数的摘要信息,只是这个摘要信息单单用于静态分析中函数信息的记录。
This file defines a summary of a function gathered/used by static analysis.
// ...
/// Marks the IDs of the basic blocks visited during the analyzes.
llvm::SmallBitVector VisitedBasicBlocks;
/// Total number of blocks in the function.
unsigned TotalBasicBlocks : 30;
/// True if this function has been checked against the rules for which
/// functions may be inlined.
unsigned InlineChecked : 1;
unsigned MayInline : 1;
/// The number of times the function has been inlined.
unsigned TimesInlined : 32;
// ...
关于Function Summary信息的记录存放在一个map中,该map存储着当前TU所有的FunctionDecl对应的摘要信息。
class FunctionSummariesTy {
typedef llvm::DenseMap<const Decl*, FunctionSummary> MapTy;
MapTy Map;
};
该摘要信息在静态分析的过程中,特别是在决定是否对该函数进行inline的时候,起着关键性的作用。
MemRegion.h
该文件定义了对内存位置进行抽象的一些类,例如MemRegion、SubRegion、AllocaRegion、MemSpaceRegion以及StackSpaceRegion等。后面我们会分析clang static analyzer的内存模型。
MemRegion - The root abstract class for all memory regions.
MemSpaceRegion - A memory region that represents a "memory space";for example, the set of global variables, the stack frame, etc.
NonStaticGlobalSpaceRegion - The region for all the non-static global variables.
// ...
ProgramState.h
该文件定义了ProgramState类,表示在程序分析过程中的程序状态。
/// \class ProgramState
/// ProgramState - This class encapsulates:
/// 1. A mapping from expressions to values (Environment);
/// 2. A mapping from locations to values(Store);
/// 3. Constraints on symbolic values (GenericDataMap)
/// Together these represent the "abstract state" of a program.
Environment是当前状态所处的环境,store是当前状态下所存储的SVal,GenericDataMap存储用户定义的数据。
Store.h
该文件定义StoreManager类,这个类用于在进行静态分析的时候,对存储进行相应的修改,例如创建新的MemRegion、删除DeadBinding或者是在进入新的StackFrame的时候(也就是analysis CallEvent)都需要调用StoreManager中相应的接口。
/// Return the value bound to specified location in a given state.
/// \param[in] store The current store.
/// \param[in] loc The symbolic memory location.
/// \param[in] T An optional type that provides a hint indicating the
/// expected type of the returned value. This is used if the value is
/// lazily computed.
/// \return The value bound to the location \c loc.
virtual SVal getBinding(Store store, Loc loc, QualType T = QualType()) = 0;
virtual StoreRef Bind(Store store, Loc loc, SVal val) = 0;
virtual StoreRef killBinding(Store ST, Loc L) = 0;
/// ...
StoreRef.h
该文件定义了StoreRef类,该类是内存Store的包装类。Store是通过llvm::ImmutableMap实现的,而llvm::ImmutableMap是通过AVLTree实现的。
class StoreRef {
Store store;
StoreManager &mgr;
// ...
};
SValBuilder.h
该文件定义了SValBuilder类,这个类主要用于符号执行(symbolical evaluators),根据SVals以及相应的运算规则构建新的SVal。注意:SymExpr不是通过SValBuilder构建的。
This file defines SValBuilder, a class that defines the interface for “symbolical evaluators” which construct an SVal from an expression.
SValBuilder是clang static analyzer中为数不多的根据当前值构建新值(其实就是符号执行)的类,SValBuilder提供了丰富的eval()*方法,来根据值以及值的类型创建新的值。由于在构建CFG的过程中,已经将Expression拆分成BinaryOperator了(除了自增、自减以及类型转换运算),所以运算几乎都是二元运算。
SVal evalCast(SVal val, QualType castTy, QualType originType);
// Handles casts of type CK_IntegeralCast
SVal evalIntegralCast(ProgramStateRef state, SVal val, QualType castTy,
QualType originalType);
virtual SVal evalMinus(NonLoc val) = 0;
/// Create a new value which represents a binary expression with two non-location operands.
virtual SVal evalBinOpNN(ProgramStateRef state, BinaryOperator::Opcode op,
NonLoc lhs, NonLoc ths, QualType resultTy) = 0;
/// Create a new value which represents a binary expression with two memory
/// location operands.,
virtual SVal evalBinOpLL(ProgramStateRef state, BinaryOperator::Opcode op,
Loc lhs, Loc rhs, QualType resultTy) = 0;
/// ...
与eval*()类具有同等重要性的方法是assume*()方法,assume*()方法和ConstraintManager紧密相关,主要用于对于一个条件表达式进行求解,判断该条件表达式能否约束到true分支的状态或者false分支的状态。
注:clang static analyzer没有专门的SMT solver,而是在符号执行的过程中,将约束添加到变量上(range set),然后使用一系列的assume()方法来进行求解。只是assume*()很简单,有很大的限制,具体参见canReasonAbout()。但是貌似可以在options上附加自定义的SMT,可惜我没有尝试过(其实是不懂),就不过多介绍了*
Our current constraint solver is fairly simple, keeping sets of possible (scalar) values for symbols and applying a set-union each time a new constraint is added. The implementation uses sets of ranges as a fairly compact way to represent an accumulation of constraints on integers. –Clang-DEV
SVals.h
该类定义了SVal,Loc以及NonLoc等类,这些类用于表达静态分析过程中的得到的值,例如变量值,内存的地址值或者未知的符号值。这些类可以说是静态分析过程中的血肉,后面我们会详细介绍这些类。
SymbolManager.h
该文件定义了一些符号类,以及符号值管理器SymbolManager和无用符号清除器SymbolReaper。在分析过程中会得到很多符号类,比如:SymbolCast、BinarySymExpr以及IntSymExpr等。同样,后面再详细介绍这些类。
class SymbolManager {
// ...
typedef llvm::FoldingSet<SymExpr> DataSetTy;
DataSetTy DataSet;
// ...
};
/// \brief A class responsible for cleaning up unused symbols.
enum SymbolStatus {
NotProcessed,
HaveMarkDependents
};
typedef llvm::DenseSet<SymbolRef> SymbolSetTy;
SymbolMapTy TheLiving;
SymbolMapTy TheDead;
// ...
}
WorkList.h
clang static anlayzer在遍历CFG时,使用工作队列算法(worklist algorithm)分析可达性并扩展ExplodedGraph。WorkList.h定义了WorkListUnit类以及WorkList类,其中WorkList类是一个虚基类,被DFS、BFS以及BFSBlockDFSContents三个类继承,这三个派生类表示了不同CFG遍历规则。
path-sensitive dataflow analysis. How?
该标题是一个技术问答,这是一个对path-sensitive analysis感兴趣的beginner提出来的(我连beginner都算不上),他提到很多的书籍对dataflow analysis都有介绍,但是鲜有书籍对path-sensitive analysis进行详细的介绍。
这里我又要无耻地做个搬运工了。
Making a DFA path-sensitive is pretty easy conceptually, and of a PITA in practice.
注:PITA - pain in the ass…
So, in a path-insensitive DFA, you build a control-flow graph that tells you which basic block can transfer control to which other ones. Then, in your flow functions for the analysis, you take the meet over all the predecessors of a node in order to compute the value for the current node.
To make the data flow analysis path-sensitive, what you need to do is label each edge in the CFA with a test expression under which it will transfer control. So if you have code like
L1: ...
...
if x = 5 then goto L2 else goto L3
then you will have a control flow graph with at least:
L1
|
+----------+----------+
| |
| x = 5 | x != 5
| |
L2 L3
Then you modify your flow function in the DFA. When computing the predecessor/successor of a node to take a meet over, you use symbolic execution on the edge’s test expression to figure out whether the edge is possible or not – if you can show it’s false, then you know that the edge can’t really be taken, and you can leave it out of the set of nodes to do the meet over.
如果修改为下面的样子,我们可以清楚的观察到哪些路径可行,哪些不可行。
extern int global;
int func()
{
if (global > 10)
{
if (global < 10)
return 0;
else
return global - 10;
}
else
{
return 0;
}
}
上面的global 是外部变量,无法推断其值,所以假定其为int类型值区间内的任意值。
Entry
|
|
+------------+------------+
| |
| global > 10 | global <= 10
+-------------------+------------------+ |
| | |
| global < 10 && global > 10 | |
| infeasible | |
| | |
return 0 return global - 10 |
|
return 0
上面的示意图中,global 是外部变量,所以可以对其进行任何假设,然后在true分支对global 约束为 (10, INT_MAX ],在else分支对global 约束为 [INT_MIN, 10]。这些约束在clang static analyzer中就是通过ConstraintManager管理的,而global 的地址和值都是SVal,在分支的地方进行 assume()*,例如假设 “global < 10 && global > 10” 是否成立。
另外在clang static analyzer中,默认是通过 DFS 来对CFG进行扩展的,先对某一路径探索完成或者达到steps 的上限,然后才会遍历另外一条路径。
后面我们会针对性的介绍clang static analyzer的核心类以及对一些常见情况的分析。