clang static analyzer源码分析(三)

引子

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类,这个类前面我们也提到过,含有多个和静态分析的核心数据成员,例如CheckerManagerDiagnosticsEngine以及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

该文件定义了两个类,ExplodedNodeExplodedGraph,我们在前一篇文章中介绍到过exploded graphclang static analyzerCFG上进行符号执行的“结果”。

This file defines the template classes ExplodedNode and ExplodedGraph, which represents a path-sensitive, intra-procedural “exploded graph.”

后面我们会详细介绍ExplodedNodeExplodedGraph这两个类。

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

该文件定义了对内存位置进行抽象的一些类,例如MemRegionSubRegionAllocaRegionMemSpaceRegion以及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是当前状态下所存储的SValGenericDataMap存储用户定义的数据。

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.

SValBuilderclang 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

该类定义了SValLoc以及NonLoc等类,这些类用于表达静态分析过程中的得到的值,例如变量值,内存的地址值或者未知的符号值。这些类可以说是静态分析过程中的血肉,后面我们会详细介绍这些类。

SymbolManager.h

该文件定义了一些符号类,以及符号值管理器SymbolManager和无用符号清除器SymbolReaper。在分析过程中会得到很多符号类,比如:SymbolCastBinarySymExpr以及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)分析可达性并扩展ExplodedGraphWorkList.h定义了WorkListUnit类以及WorkList类,其中WorkList类是一个虚基类,被DFSBFS以及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的核心类以及对一些常见情况的分析。

### 回答1: Clang静态分析器是一个用于检测C、C++和Objective-C代码中潜在问题的工具。它可以在编译时对代码进行静态分析,以检测内存泄漏、空指针引用、未初始化变量等问题,并生成相应的警告或错误信息。Clang静态分析器是Clang编译器的一部分,可以通过命令行或集成开发环境(IDE)使用。它是一个非常有用的工具,可以帮助开发人员提高代码质量和可靠性。 ### 回答2: Clang Static Analyzer(以下简称CSA)是一款由LLVM项目开发的静态分析工具。它采用基于路径进行符号执行的分析方式,在程序编写阶段对源代码进行扫描,自动检测代码中的潜在问题,如内存泄漏、空指针引用、数组越界等,并给出相应的警报和建议,帮助程序员尽早发现和修复代码中的缺陷。 CSA具有以下几个特点: 1.准确性高:采用符号执行技术,可以深入分析程序的每一个执行路径,准确地定位问题点。 2.兼容性强:支持C、C++和Objective-C等多种语言的静态分析,并支持多种平台,可在Windows、Linux、Mac OSX等多个操作系统中使用。 3.使用简单:作为Clang编译器的一部分,CSA可与其他Clang工具集成使用,无需额外设置与安装,用户只需在编译时加入相应的编译选项即可。 4.可扩展性好:CSA提供良好的插件机制,方便开发者扩展自定义的分析插件和规则库,满足不同应用场景的需求。 总之,CSA是一款十分实用的静态分析工具,可以有效地改善程序的质量,提高开发效率。使用CSA进行代码分析,不仅能够保证程序的可靠性与稳定性,同时也会让程序员更深入地了解程序设计的细节,从而提升自己的编程技能与水平。 ### 回答3: Clang是一种基于LLVM基础架构的C/C++编译器。他是Apple公司推出的,始终致力于提高代码的质量。Clang像其他编译器一样,有一个静态代码分析的工具,它叫做Clang Static Analyzer。它是一个非常有用的工具,可以检测代码中的一些潜在问题,并给出一些自动化的建议和修复建议,从而帮助开发人员开发出更好的代码。在这篇文章中,我们将讨论Clang Static Analyzer的优点和使用方法。 Clang静态分析器的优点有: 1. 更好的准确性:Clang静态分析器使用LLVM框架,这使得它在识别潜在的问题方面更准确。 2. 杜绝缺陷:Clang静态分析器可以帮助开发人员解决所有类型的缺陷,如内存泄漏、空指针解引用、死锁等等。 3. 改进效率:由于静态分析器可以自动地检查代码,它可以提高开发人员的工作效率,并且可以减少手动调试时间和成本。 Clang静态分析器的使用方法: 1. 安装Clang编译器:在使用Clang静态分析器之前,需要先安装Clang编译器。可以在https://clang.llvm.org/网站上下载Clang编译器。 2. 安装分析器:Clang分析器与Clang编译器一起安装。安装完成之后,可以在命令行上使用分析器。 3. 运行分析器:使用命令行运行Clang分析器的示例命令: $ clang --analyze [options] input-file。 4. 分析结果:在运行分析器后,它会生成分析结果文件。分析结果文件包含了所有的问题和建议。开发人员可以查看分析结果文件并根据分析结果来修改代码和修复bug。 总之,Clang Static Analyzer是一个非常有用的工具,它可以帮助开发人员提高代码质量,并且减少代码中的潜在缺陷。如果您是C/C++开发人员,那么Clang静态分析器一定会大大提高您的开发效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值