程序分析
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有两点建议:
- 需要追踪什么事件?
- 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:
-
Bug类型。BugType的实例。
-
简短描述字符串。Bug描述,用于显示。
-
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
- 除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