How to use Clang Static Analyzer

Introduction

Clang

宏观上讲,Clang是一个项目名称。微观上,类似于GCC,Clang是一个C语言、C++、Objective C语言的轻量级编译器,它是Clang项目的一部分。

相比较于GCC,Clang的编译速度更快,占用的内存更少。Clang的错误提示与警告信息也比GCC更加准确清晰。此外,Clang基于库的模块化设计,易于IDE的集成并且遵循LLVM BSD协议。

Clang Static Analyzer

Clang Static Analyzer是一个能查找C语言、C++、Objective-C(C语言家族)漏洞的源码分析工具。

目前,Clang Static Analyzer可以作为一个独立的工具,也可以在Xcode开发环境(Mac os)中运行。Clang Static Analyzer作为一个独立的工具可以从命令行(如ubuntu的终端)中启动,并且它运行在构建一个代码库过程中。

Clang Static Analyzer作为Clang项目的一部分,是一个百分之百开源的软件。就像Clang编译器一样,Clang Static Analyzer可以像一个C++库一样集成到其他应用程序中。

scan-build & scan-view

scan-build是一个命令行工具,它能够帮助使用者运行静态分析器(static analyzer)检查源代码,使其能正常的构建。静态分析器与代码的编译是互不影响并且同步执行的,即:当一个项目在构建中,源码会被静态分析器分析并查找源码的漏洞。如:

$ scan-build make

当构建完成时,结果将会以一个web网页的形式呈现给使用者。类似于scan-build,scan-view也是一个命令行工具。它能打开由scan-build生成的web网页,显示bug报告。


How to install

不同平台Clang的安装方法有所不同:Mac OS X环境下安装方法:MAC OS CLANG,本文介绍Ubuntu系统下的安装方法,除Mac OS X的其他平台都与ubuntu类似。

apt-get 安装

用apt-get直接安装,只需要在终端中输入:

$ sudo apt-get install clang

这种方法适用于对版本要求不高的用户,优点是简单快速。

源码安装

如果对Clang版本要求很高,就需要手动对Clang源代码编译安装,这也是官网建议的安装方法。这种方法优点是能得到最新版本,缺点是耗时麻烦。

  1. 安装必要工具:

    $ sudo apt-get install subversion
    $ sudo apt-get install g++
    
  2. 下载LLVM系统和Clang最新源码:
    • 用svn下载LLVM源码

      • 将目录切换到想要放LLVM源码的目录
      • 输入如下代码:
        $ sudo svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm
        
    • 下载Clang源码
      $ cd llvm/tools/
      $ sudo svn co http://llvm.org/svn/llvm-project/cfe/trunk clang
      $ cd ../..
      
    • 下载额外Clang工具(可选)
      $ cd llvm/tools/clang/tools
      $ sudo svn co http://llvm.org/svn/llvm-project/clang-tools-extra/trunk extra
      $ cd ../../../..
      
    • 下载Compiler-RT
      $ cd llvm/projects
      $ sudo svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt
      $ cd ../..
      
  1. 编译Clang

    • 创建一个build目录用于存放执行configure后生成的文件

      $ mkdir build
      $ cd build
      $ ../llvm/configure --prefix=/usr/clang --enable-optimized --enable-targets=host
      $ sudo make -j2
      
    • 运行configure的时候,可以添加一些选项,选项的详细说明可见:CLANG CONFIGURATION 这里,
      1. --prefix=/usr/clang 选项用于在生成Makefile文件后,执行make install时指定将Clang安装到/usr/clang目录中。
      2. --enable-optimized 选项用于打开优化。默认不打开,生成的是Debug版本,非常占用内存并且时间是optimized版本的10倍。这里没有必要生成Debug版本。
      3. --enable-target=host 选项用于选择目标平台。默认是生成所有平台,这里只需要适合本机的情况就可以了。
    • make的选项-j2指定2个线程同时执行。(如果双核就-j2,四核就-j4)
    • 编译会花费一段时间,编译之后就可以make install了:
      $ make install
      

      由于--prefix选项指定了安装路径,故Clang就被安装在/usr/clang目录下。

    • 让bash能搜索到可执行程序clang。在/usr/bin下创建软链接到目标目录,ln -s ../clang/bin/clang (也可以修改PATH环境变量,但在这里不推荐,因为这样不容易让scan-build工具找到clang)
  2. 调整scan-build和scan-view目录

    官网给的源码make install并不会安装scan-build和scan-view工具。编译完成后,编译出的scan-build和scan-view分别在$(SRCDIR)/tools/clang/tools/scan-build和$(SRCDIR)/tools/clang/tools/scan-view中($(SRCDIR)是下载的llvm路径)。为防止误删除这两个目录,通常情况下,会把这两个目录放到根目录下(比如放到/usr/share/clang下),如果当前目录在llvm目录下。

    $ sudo mkdir /usr/share/clang/scan-build -p
    $ sudo cp ./tools/clang/tools/scan-build/* /usr/share/clang/scan-build/
    $ sudo mkdir /usr/share/clang/scan-view -p
    $ sudo cp -r ./tools/clang/tools/scan-view/* /usr/share/clang/scan-view/
    $ cd /usr/bin
    $ sudo ln -s ../share/clang/scan-build/scan-build
    $ sudo ln -s ../share/clang/scan-view/scan-view
    

    至此,scan-build和scan-view工具就可以使用了,但是由于源码脚本并没有指定clang的路径,所以每次使用scan-build工具时必须写出clang的路径。这样非常麻烦,解决这个问题,可以修改scan-build脚本。

    sudo vi /usr/bin/scan-build
    # Find ‘clang‘
    if (!defined $AnalyzerDiscoveryMethod) {
    - -  $Clang = Cwd::realpath("$RealBin/bin/clang");
    +  $Clang = Cwd::realpath("/usr/bin/clang");
       if (!defined $Clang || ! -x $Clang) {
         $Clang = Cwd::realpath("$RealBin/clang");
       }
    

    即:将第一个$RealBin改为/usr。这样就向scan-build指定了Clang的位置。Clang Static Analyzer就完全安装完成了。


Standard Operation Procedure

Clang编译器

与GCC相类似,Clang也集成预编译器、汇编器和链接器于一体,使用的语法格式也为:

clang [options] <inputs>

其大多数编译选项也与GCC类似,如-E只执行预编译、-S生成汇编语言文件(以.s结尾)、-c选项只编译不链接等。详细的选项说明可以man clang或者clang -help查看。Clang编译器的用户手册详见:CLANG COMPILER USER‘S MANUAL

与GCC不同的一点是使用--analyze选项可以启动静态分析器,如

$ clang --analyze test.c

在在运行这条指令时,Clang Static Analyzer会被启动,分析test.c中的bug。

scan-build和scan-view

  • scan-build

    1. 开始

      scan-build本质上是一个唤起Clang Static Analyzer的Perl脚本。scan-build命令会介入到整个工程的构建过程中去分析这个工程。这就意味着,没有被gcc或是clang编译的文件是不会被分析的。

    2. 基本使用

      scan-build的基本使用方式很简单,只需要在命令行开头输入scan-build即可。

      $ scan-build make
      

      在make整个工程的同时,scan-build启动静态分析器分析正在构建的的工程代码。下面是scan-build命令的通用格式:

      $ scan-build [scan-build options] <command> [command options]
      

      scan-build会逐个运行这些命令,其参数也是按顺序执行。例如,在make命令中传入一个 -j4参数,结果就是用4核并行编译:

      $ scan-build make -j4
      

      scan-build也可以用来分析具体的文件:

      $ scan-build gcc -c test1.c  test2.c
      

      这个命令实现对test1.c和test2.c文件的分析。

    3. 其他选项(scan-build options)

      scan-build支持很多选项,下面是一些常用选项:

      1. -o (output)html报告文件存放目录。可以按照需要创建一些子目录,来区分每个运行的分析器。如果没有指定这个参数,默认将报告文件保存在/tmp目录下。
      2. -h (help)显示scan-build的所有参数。
      3. -k (keep on going)增加一个连续运行的参数到具体的命令中。这个选项目前只支持make和xcodebuild。
      4. -v (verbose)冗余输出,用于输出更详细的分析结果。两个或三个-v选项可以增加冗余度,关于bug的分析报告也就更多。
      5. -V (view)运行指令后立即打开浏览器显示报告结果。
      6. -plist 输出的结果保存成.plist文件。
      7. -plist-html 输出的结果保存成html和.plist两种文件格式。
      8. --use-c++[=compiler_path] 使用默认的C++和Objective-C++编译器,或指定路径使用指定的编译器。
      9. --use-cc[=compiler_path] 使用默认的C或Objective-C编译器,或指定路径使用指定的编译器。
    4. 更多scan-build选项可以man scan-build或者scan-build -h查看。
    5. scan-build的输出结果

      scan-build的输出结果是一个HTML文件集合,每个html文件代表一个独立的缺陷报告。index.html文件是用来查询所有的缺陷。可以用浏览器打开index.html文件查看所有缺陷报告。html报告文件的存放路径是由-o选项指定的,默认是保存在/tmp目录下。scan-build运行后会打印出报告所在路径。如果想在命令执行完之后立即去查看报告,那么可以传入一个-V参数。

    6. 注意事项

      如果要分析的工程用configure脚本自动生成makefile文件,那么就需要通过scan-build来执行configure配置文件。

      $ scan-build ./configure
      $ scan-build make
      

      configure文件需要通过scan-build运行的原因是静态分析器是通过介入编译器来分析源码的。这种介入是通过暂时地设置环境变量CC成ccc-analyzer。ccc-analyzer作为一个伪编译器,转发命令行参数给gcc或是clang来执行静态分析。

  • scan-viewscan-view工具用来查看由scan-build生成的html报告文件。其具体的用法为:
    scan-view [options] <results directory>
    

    scan-view的选项可以通过scan-view -h或者man scan-view查看。<results directory>是由scan-build生成的存放html文件的路径。scan-build执行完成后会提示出如何用scan-view查看html报告。

checkers

Clang Static Analyzer就是利用不同的checker来检测源码不同类型的bug的。静态分析器会默认使用6类checkers(default checker):

  • Core Checkers:提供一些一般性的检查,比如是否被0除、是否使用空指针和使用未初始化参数等。
  • C++ Checkers:提供C++检查。
  • Dead Code Checkers:检查没有使用的代码。
  • OS X Checkers:检查Objective-C和Apple‘s SDKs的使用情况。
  • Security Checkers:检查不安全API的使用和基于CERT Secure Coding Standards的检查。
  • Unix Checkers:检查Unix和POSIX API的使用情况。

其中,每一类的checkers中包含有不同的checker负责检查不同细分类型的bug。比如core checkers类中的一个checker: core.CallAndMessage 该checker主要负责检查函数调用的逻辑错误或者信息表达的错误,比如未初始化的参数,空的函数指针等。例如:

// C
struct S {
  int x;
};

void f(struct S s);

void test() {
  struct S s;
  f(s); // warn: passed-by-value arg contain uninitialized data
}

可以使用选项--help-checkers来查看默认checkers列表。-enable-checker和-disable-checker用来使用和禁用checker,例如

$ scan-build --help-checkers
$ scan-build -enable-checker core.CallAndMessage gcc test.c
$ scan-build -disable-checker core.CallAndMessage gcc test.c

如果不禁止使用某个checker,scan-build会自动使用默认的checkers。详细的checkers功能介绍请看后面的章节“The functions of default checkers”或AVAILABLE CHECKERS


The flow chart of scan-build & ccc-analyzer

用来启动静态分析器的scan-build是一个perl脚本。ccc-analyzer与scan-build一样,也是一个perl脚本。ccc-analyzer脚本是一个中间文件,用户不直接接触,但是会被scan-build调用。

scan-build flow chart

以上为scan-build脚本执行的前半部分,用来处理scan-build option。

第二部分的scan-build主要处理command和command option。

scan-build总结:

  1. 不管是编译单独文件还是整个工程,scan-build 都不直接启动clang,而是启动ccc-analyzer或c++-analyzer
  2. [scan-build option]经过scan-build处理,以环境变量的形式传递给ccc-analyzer
  3. [command option]会全部传递给ccc-analyzer
  4. scan-build在make工程时,通过修改(并只会修改)makefile中CC或CXX变量,将变量中指定的编译器全部换成ccc-analyzer或c++-analyzer。即:makefile中CC或CXX变量中指定的编译器更本就是没有任何作用的!
  5. 所以当用scan-build管理工程时,必须用--use-cc或--use-c++指定编译器

ccc-analyzer flow chart

ccc-analyzer脚本流程总结:

  1. 环境变量CCC_CCC做为最终的编译器
  2. 如果环境变量CCC_CC没有指定,会用默认的gcc或g++编译
  3. system()函数是阻塞型函数。即先编译,后启动静态分析器

How to use scan-build to find bugs

分析一个使用未初始化的函数指针的代码

  1. vi test.c输入如下代码:

    int main(){
      void (*foo)(void);
      foo();
    }
    
  2. 保存退出后,通过scan-build编译test.c
    $ scan-build gcc test.c
    
  3. scan-build会产生如下结果:
    scan-build: Using ‘/usr/bin/clang‘ for static analysis
    test.c:3:2: warning: Called function pointer is an uninitalized pointer value
            foo();
            ^~~~~
    1 warning generated.
    scan-build: 1 bugs found.
    scan-build: Run ‘scan-view /tmp/scan-build-2014-06-27-164418-6424-1‘ to examine bug reports.
    
  4. 说明分析出了一个bug。按照提示用scan-view查看这个bug报告:
    $ scan-view /tmp/scan-build-2014-06-27-164418-6424-1
    

    scan-view会自动打开浏览器,显示报告信息,如下:

分析MIPS架构linux分位中的private apps

  1. 下载并编译linux分位源码

    步骤参见wiki:mDNS的章节avahi与netatalk。建议编译通过后再执行scan-build指令,防止系统环境问题干扰scan-build。一些常见的编译错误以及解决方法在wiki中也有解释。

  2. 修改maple_linux/userspace中makefile,使其使用scan-build启动clang静态分析器分析生成private apps的源码
     private-apps:
    -       $(MAKE) -C private/apps
    +       #$(MAKE) -C private/apps
    +       scan-build --use-cc /opt/toolchains/uclibc-crosstools-gcc-4.2.3-4/usr/bin/mips-linux-gcc $(MAKE) -C private/apps > ../1.txt
    

    将scan-build运行的结果输出重定向到1.txt是防止make的打印信息太多而找不到scan-build的输出结果。保存退出后在maple_linux目录下再次运行./build.sh

  3. 编译完成后在maple_linux目录下打开1.txt,根据最后一句提示用scan-view启动浏览器查看bug报告。结果如下:


The functions of default checkers

Clang Static Analyzer中default checkers共有6类,分别为:Core Checkers, C++ Checkers, Dead Code Checkers, OS X Checkers, Security Checkers和Unix Checkers。以下为除OS X Checkers的其他5类中各个checker的功能描述。

注:更多关于各个功能所能修改的代码示例详见:AVAILABLE CHECKERS


A example of a custom checker

Clang Static Analyzer之所以能够查找源代码中的bug就是因为checker的存在,所以,checker可以说是Analyzer的灵魂。尽管Analyzer有数十个自带的default checkers和experimental (alpha) Checkers,并且还在不停添加,但是这些checker永远不能包含所有可能出现bug的情况。幸运的是,用户可以根据自身的需求添加适合自己代码的checker,这很大的提高了Analyzer的灵活性。以下内容就来讨论checker的原理,以及如何写一个自定义checker。

1.明确checker需要解决的问题

当为防止数据同时被多个线程修改的时候,锁机制会被经常使用。在C/C++中,程序员必须保证上锁与解锁成对出现,但当函数功能的增多或是出现很多分支的时候,很容易造成double lock, double unlock, unreleased lock这些情况。由于Analyzer可以追踪程序的状态(Program State),所以它很适合检查这种bug。另一方面,上锁和解锁通常不会相隔很大距离(通常在一个函数内),而目前为止,Analyzer还并不支持跨越多文件检查。在这个例子中,假设上锁的函数名叫‘lock‘,解锁的函数名叫‘unlock‘。为了简化这个例子,这两个函数都不带参数,即它们都代表一个锁。以下是三个代码中必须遵守的原则:

  • 所有调用lock()的函数,在返回之前必须调用unlock()来解锁----否则产生unreleased lock错误
  • 不允许有函数在已经调用lock()函数之后,紧接着又调用lock()----否则产生double lock错误
  • 不允许有函数在已经调用unlock()之后,紧接着又调用unlock()----否则产生double unlock错误

2.checker的结构

所有的checker都要继承自Checker模板类,这个模板类的参数描述了会调用这个checker的事件类型。事件类型以及对应事件的触发举例:

  • [check::PreStmt<xxx>] - 在statement xxx发生之前调用这个checker
  • [check::PostStmt<xxx>] - 在statement xxx发生之后调用这个checker
  • [check::PreCall] - 在函数调用之前调用这个checker
  • [check::EndFunction] - 在函数结束时调用这个checker
  • [check::BranchCondition] - 在分支出现时调用这个checker
  • [check::DeadSymbols] - 当参数超出生命周期时调用这个checker

更多详细的事件及事件功能可以参考:CheckerDocumentation.cpp在这个例子中,需要check::PreCall事件,用来判断调用的是lock()还是unlock()函数;check::EndFunction事件,用来查看函数结束时有没有unreleased
lock的情况。对于每一种事件类型,当事件触发时,Analyzer都会回调checker中的回调函数。CheckerDocumentation.cpp中声明了这些回调函数。参照这些声明格式,例子中类的框架模型为:

class LockUnlockChecker : public Checker<check::PreCall,check::EndFunction > {
  ...
  public:
  LockUnlockChecker(void) {
  ...
  }
  void checkPreCall(const CallEvent &call, CheckerContext &C) const {
  ...
  }
  void checkEndFunction(CheckerContext &) const {
  ...
  }
};

3.程序状态(Program State)

Clang Static Analyzer就像其他静态分析工具一样,并不会执行源代码,而是象征性的执行代码(symbolic excution),并且会执行代码中的每一个分支(Path Sensitive)。在“执行”过程中,Analyzer会实时的根据运行情况追踪和改变程序状态(Program State)。举一个简单的例子,有如下代码:

void example_function(int a) {
  if(a)
    lock();
  ...
  if(a)
    unlock();
}

从这个例子中,代码关于上锁与解锁的使用是正确的。不管变量a为何值,代码的运行都满足之前指出的三条原则。Analyzer是如何运行这段代码的呢?在函数的开始,Analyzer会决定程序状态:

  • Value of ‘a‘: any possible value
  • State of lock: unlocked

注:程序状态其他值并没有被列出,因为它们与本例无关。当“执行”到第一个if语句时,Analyzer并不能确定执行哪个分支,因为a的值无法被确定。所以,程序的状态被分成两个(针对两个分支):

  • Value of ‘a‘: non-zero; State of lock: unlocked
  • Value of ‘a‘: zero; State of lock: unlocked

在a非零的分支中,当执行到lock()函数时,程序的状态被改为:

  • Value of ‘a‘: non-zero; State of lock: locked
  • Value of ‘a‘: zero; State of lock: unlocked

这个状态将会保持到第二个if语句。当执行到第二个if语句时,因为这两个状态都定义了变量‘a‘,所以状态不会再一次一分为二。a为非零的状态将被选择执行。最后,状态将变成:

  • Value of ‘a‘: non-zero; State of lock: unlocked
  • Value of ‘a‘: zero; State of lock: unlocked

因为上面的例子并没有bug出现,所以Analyzer不会报告bug。从这个例子可以总结,当PreCall与EndFunction事件被触发,Checker中的回调函数将被调用,checker的回调函数要么修改程序状态,要么报告bug。

4.Locked/Unlocked程序状态

知道什么样的信息应该存在程序状态中,那如何定义适合本例的程序状态?类ProgramState中定义了程序状态。程序状态主要包含以下三类信息:

  • 变量和表达式可能的值(在类Environment中)
  • 内存位置的值(在类Store中)
  • 与checker相关的值(checker-specific)

只有最后一类状态信息是需要所关心的,用来存储Locked/Unlocked状态。可以使用宏定义的方法来添加程序状态。对于这个例子,可以使用宏REGISTER_TRAIT_WITH_PROGRAMSTATE。这个宏有两个参数:给这类信息取的名称和信息的数据类型。此例中,信息的名称可以叫‘LockState‘,信息的存储类型用‘bool‘。即:

REGISTER_TRAIT_WITH_PROGRAMSTATE(LockState, bool)

除了宏REGISTER_TRAIT_WITH_PROGRAMSTATE,还有REGISTER_MAP_WITH_PROGRAMSTATEREGISTER_SET_WITH_PROGRAMSTATEREGISTER_LIST_WITH_PROGRAMSTATE宏也可以用来定义checker相关的信息。状态信息添加好后,就可以用函数get()set()分别来获得和设置状态信息。比如:

ProgramStateRef state;
...
bool currentlyLocked = state->get<LockState>();
...
state = state->set<LockState>(true);

5.代码实现

根据checker的结构和程序状态的定义,部分代码实现如下:

void checkPreCall(const CallEvent & call, CheckerContext &C) const {
  const IdentifierInfo * identInfo = call.getCalleeIdentifier();
  if(!identInfo) {
    return;
  }
  std::string funcName = std::string(identInfo->getName());
  ProgramStateRef state = C.getState();
  if(funcName.compare("lock") == 0) {
    bool currentlyLocked = state->get<LockState>();
      if(currentlyLocked) {
        ... (emit warning about double lock) ...
      }
    state = state->set<LockState>(true);
    C.addTransition(state);
  } else if(funcName.compare("unlock") == 0) {
    bool currentlyLocked = state->get<LockState>();
    if(!currentlyLocked) {
      ... (emit warning about double unlock) ...
    }
    state = state->set<LockState>(false);
    C.addTransition(state);
  }
}
void checkEndFunction(CheckerContext &C) const {
  ProgramStateRef state = C.getState();
  bool currentlyLocked = state->get<LockState>();
  if(currentlyLocked) {
    ... (emit warning about returning without unlocking) ...
  }
}

6.报告bug

报告bug设计两个类:BugTypeBugReport。BugType代表bug的类型,因为有三个bug(double
lock, double unlock, unreleased lock),所以在checker中需要定义三个BugType。将这三个BugType定义在LockUnlockChecker类中,并在构造函数中初始化。BugReport类代表一个特定发生的bug。BugReport的三个参数为:

  • Bug的类型
  • 当bug发生是出现的描述提示字符串
  • bug出现的位置

当报告bug时,一般情况需要转变当前程序状态成为Sink Node。Sink Node可以阻止Analyzer沿着这条路径继续分析,从而防止额外的bug沿着这条路径出现。由CheckerContext类中的函数generateSink()产生Sink
Node,并且返回的指针代表着Sink node的位置(即可以作为bug的位置参数)。

ExplodedNode * bugloc = C.generateSink();
if(bugloc) {
  BugReport * bug = new BugReport(*DoubleLockBugType,
    "Call to lock when already locked", bugloc);
  C.EmitReport(bug);
}

Building a Checker in 24 Hours是一个官方提供的另一个checker例子。clang提供的所有API函数见:Clang
API


Register and test the custom checker

有两种方法注册自定义checker,一种是将自定义checker作为experimental checker编译到clang可执行程序中,另一种是将自定义checker单独编译成.so共享库文件,在使用时动态加载。第一种方式适合已经测试好的checker,而第二种方式更适合调试阶段的checker。

Register and test as a experimental checker

对于从官网下载源码编译的情况,所有的checker文件都放在clang/lib/StaticAnalyzer/Checkers目录下(apt-get的安装方法不能将自定义checker编译到clang可执行程序中)。以上面的例子为例,以下说明如何注册一个用户自己写的checker:LockUnlockChecker?

  1. 创建一个新的checker,LockUnlockChecker.cpp。将这个文件放在lib/StaticAnalyzer/Checkers目录下。
  2. 将如下代码放在LockUnlockChecker.cpp最后:
    void ento::registerLockUnlockChecker(CheckerManager &mgr) {
      mgr.registerChecker<LockUnlockChecker>();
    }
    
  3. 这个Checker还需要在clang/lib/StaticAnalyzer/Checkers/Checkers.td文件中定义。因为这个checker是刚创建的,所以必须属于‘alpha’(测试)版本。并且,根据这个checker的功能判断属于上文说明6类中的哪一类。在此例中,假设用LockUnlockChecker测试Unix系统的上锁情况。所以,可以把这个checker定义在‘alpha.unix‘包中。将如下代码放入Checkers.td文件中定义‘alpha.unix‘包中checker的位置。
    let ParentPackage = UnixAlpha in {
    ...
    def LockUnlockChecker : Checker<"LockUnlock">,
      HelpText<"Checker for use of lock()/unlock()">,
      DescFile<"LockUnlockChecker.cpp">;
    ...
    } // end "alpha.unix"
    

注意:

  • 加在Checkers.td文件中的代码Checker<"LockUnlock">中,"LockUnlock"将与所在的包名组成新添加checker的名称。例如此例中,checker的名称为alpha.unix.LockUnlock。该名称也可以自定义成别的。
  • Checkers.td的def LockUnlockChecker意为定义ento::registerLockUnlockChecker(CheckerManager &)这个函数。所以,def后面的部分必须与LockUnlockChecker.cpp文件中函数ento::registerXXX(CheckerManager?
    &)保持一致。
  • LockUnlockChecker.cpp中mgr.registerChecker<LockUnlockChecker>的LockUnlockChecker为自定义的类名,所以要与上面代码class LockUnlockChecker保持一致。
  • HelpText<"">中为对alpha.unix.LockUnlock功能的说明。
  • DescFile<"">中为alpha.unix.LockUnlock的源文件名。
  1. 完成两个文件代码的添加后,按照上文安装方法中步骤重新编译安装。两个文件可见附件中的[LockUnlockChecker.cpp]和[Checkers.td]
  2. 测试
    1. 完成代码的注册并重新编译安装后,输入以下指令查看clang支持的checker中是否有刚添加进去的checker:

      $ clang -cc1 -analyzer-checker-help
      
    2. 写一段测试代码,可以使用附件中的lock_test.c
    3. 运行注:新添加的checker不是default checker,所以在使用新添加的checker查bug时需要用scan-build的选项-enable-checker将新checker添加进来:
      $ scan-build -enable-checker alpha.unix.LockUnlock gcc -c lock_test.c
      

Register and test as a plugin

用apt-get安装和用源码编译安装的方法都支持链接共享库的方式加载自定义checker。

  1. 为了能让clang准确加载插件,插件必须暴露一些符号给clang。符号CLANG_ANALYZER_API_VERSION_STRING用来指明插件所用API的版本。在clang源文件中,API版本由预编译变量CLANG_ANALYZER_API_VERSION_STRING提供。所以:

    extern "C" const char clang_analyzerAPIVersionString[] = CLANG_ANALYZER_API_VERSION_STRING;
    

    extern "C" 确保符号名不会被破坏。第二个插件必须输出的符号是clang_registerCheckers(CheckerRegistry &registry)。这个函数在加载插件时将会被调用。

    extern "C" void clang_registerCheckers(CheckerRegistry &registry) {
      registry.addChecker<LockUnlockChecker>("example.LockUnlockChecker", "Checker for use of lock()/unlock()");
    }
    

    当注册插件时,CheckerRegistry类中的成员函数addChecker会被调用,它包含有三个参数:

    • checker中的类名
    • checker名
    • 对这个checker功能的描述
  2. 将源文件编译成库文件

    需要用到可执行程序llvm-config。如果用apt-get安装方法,一般在目录/usr/lib/llvm-X.X/bin/llvm-config下(X.X为llvm版本号)。源码安装一般在目录build/Release+Asserts/bin/llvm-config。以下以源码安装为例:

    g++ -shared -fPIC `~/llvm/build/Release+Asserts/bin/llvm-config --cxxflags` -I`~/llvm/build/Release+Asserts/bin/llvm-config --src-root`/tools/clang/include -I`~/llvm/build/Release+Asserts/bin/llvm-config --obj-root`/tools/clang/include -o LockUnlockChecker.so LockUnlockChecker.cpp
    
  3. 测试可用附件中代码lock_test.c测试。
    scan-build -load-plugin ./LockUnlockChecker.so -enable-checker example.LockUnlockChecker gcc -c lock_test.c
    

Notice

Clang options

以上的命令中涉及到很多选项,但需要区别clangclang -cc1clang -cc1是专门给开发者使用的,包含clang项目的所有功能。而clang相当于一个driver(驱动),是专门给用户使用,他兼容GCC的使用方法,以方便熟悉GCC的用户使用。例如,与gcc类似,clang命令也可以驱动预处理器、汇编器、编译器、连接器甚至静态分析器。 当使用命令行命令clang时,clang会自动产生一系列适合当前系统的选项并传递给clang
-cc1
。所以clangclang -cc1选项不能混用。

Compatibility

由于Clang项目目前仍处于开发阶段,不同版本之间有不少区别。(亲测在3.2版本的代码不能直接在3.5版本中运行)

时间: 2024-08-03 18:25:05

How to use Clang Static Analyzer的相关文章

使用static analyzer发现更多潜在问题。

from: iOS Good Practices The Clang compiler (which Xcode uses) has a static analyzer that performs control and data flow analysis on your code and checks for lots of errors that the compiler cannot. You can manually run the analyzer from the Product

Xcode开发调试技巧-.Static Analyzer

1.Static Analyzer(静态分析) Static Analyzer是一个非常好的工具, 它可以帮助我们发现编译器警告不会提示的问题. Static Analysis 优点: 1.使用操作容易. 2.能够在编码阶段,开发自行进行代码检查.早期发现代码隐患. 3.直接分析源代码来发现程序中的错误,而不需要实际运行. 4.自动检测objective-C程序中的BUG,发现内存泄露和其它问题. 5.内存问题发现越早,解决的代价就越小. Static Analysis  可以对以下一些情况进行

clang static analyze

C++静态检查一般使用cppcheck直接一条cppcheck ./*.{h,cpp,hpp}命令搞定整个项目,最近发现用clang进行代码补全和代码分析更加强大,借助scan-build工具更好的完成整个过程 直接使用clang扫描 --analyze选项可以直接静态扫描源码 --analyzer-check设置检查的内容 -analyzer-checker-help可以列出可以检测的内容 -c将会只运行预处理.编译和汇编的步骤 首先使用scan-build扫描一下构建 使用格式为:scan-

Top 40 Static Code Analysis Tools

https://www.softwaretestinghelp.com/tools/top-40-static-code-analysis-tools/ In this article, I have summarised some of the top static code analysis tools. Can we ever imagine sitting back and manually reading each line of codes to find flaws? To eas

iOS安装包瘦身的那些事儿

在我们提交安装包到App Store的时候,如果安装包过大,有可能会收到类似如下内容的一封邮件: 收到这封邮件的时候,意味着安装包在App Store上下载的时候,有的设备下载的安装包大小会超过100M.对于超过100M的安装包,只能在WIFI环境下下载,不能直接通过4G网络进行下载. 在这里,我们提交App Store的安装包大小为67.6MB,在App Store上显示的下载大小和实际下载下来的大小,我们通过下表做一个对比: iPhone型号 系统 AppStore 显示大小 下载到设备大小

C/C++框架和库 (真的很强大) 转

http://blog.csdn.net/xiaoxiaoyeyaya/article/details/42541419 值得学习的C语言开源项目 - 1. Webbench Webbench是一个在Linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能,最多可以模拟3万个并发连接去测试网站的负载能力.Webbench使用C语言编写, 代码实在太简洁,源码加起来不到600行. 下载链接:http://home.tiscali

值得推荐的C/C++框架和库(深度好文)

[本文系外部转贴,原文地址:http://www.cppblog.com/merlinfang/archive/2014/12/26/209311.html http://coolshell.info/c/c++/2014/12/13/c-open-project.htm]留作存档 公交车上看到的好文,忍不住转发!下次造轮子前先看看现有的轮子吧-- 值得学习的C语言开源项目 - 1. Webbench Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客

C++开源库大全(转)

程序员要站在巨人的肩膀上,C++拥有丰富的开源库,这里包括:标准库.Web应用框架.人工智能.数据库.图片处理.机器学习.日志.代码分析等. 标准库 C++ Standard Library:是一系列类和函数的集合,使用核心语言编写,也是C++ISO自身标准的一部分. Standard Template Library:标准模板库 C POSIX library : POSIX系统的C标准库规范 ISO C++ Standards Committee :C++标准委员会 框架 C++通用框架和库

值得推荐的C/C++框架和库

值得学习的C语言开源项目 - 1. Webbench Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能,最多可以模拟3万个并发连接去测试网站的负载能力.Webbench使用C语言编写, 代码实在太简洁,源码加起来不到600行. 下载链接:http://home.tiscali.cz/~cz210552/webbench.html - 2. Tinyhttpd tinyhttpd是一个超轻量型Ht