从Oclint开始接触Clang编译

##前置工做html

  • Oclint(静态代码分析工具)
  • Xcpretty(格式化输出工具)
    • 安装: sudo gem install -n /usr/local/bin xcpretty
  • Cmake(编译工具,这里用来构建LLVM和Xcode工程)
    • 来源:cmake.org/download/
    • 安装:运行cmake图形界面程序,在左上角的选项栏中选择Tools,点击How to install for Command Line Use,官方给出了三种安装cmake command line tool的方法,即终端可以识别cmake命令的方法。我选择了官方给出的第二种方法,即复制sudo"/Applications/CMake.app/Contents/bin/cmake-gui" --install 命令到终端,而后运行
  • Ninja(比cmake更小的编译工具,这里用来构建LLVM)
    • 来源:github.com/ninja-build…
    • 安装:将下好的Ninja解压, 拷贝到一个系统目录中 /usr/bin 来完成安装(此处有坑,该文件夹权限获取:重启Mac,按住command+R,进入recovery模式。选择打开Utilities下的终端,输入:csrutil disable并回车,而后正常重启Mac便可).

##前言 Clang是llvm的编译器前端,很是适合进行源码分析.目前开源的oclint就是基于clang进行的代码静态检查.工做中遇到了一些问题须要进行代码分析,因此学习了一下相关知识.咱们平常用的clang有这两种: 1.Xcode内部自带的Clang Static Analyzer(简称CSA) 2.OCLint 这二者都是基于Clang的前端编译,CSA因为被内置,因此使用起来比较方便,可是可用的检查规则比较少,只有16条,大部分是核心向的功能例如空指针检测,类型转换检测,空判断检测,内存泄漏检测,没法检测代码风格,可扩展性比较差。 OCLint可用的检查规则有70+条,比OSA支持的规则多不少,还支持自定义规则,因此选择了OCLint。前端

##准备开发环境python

  • 安装Oclint,获得的目录结构以下:
.
├── README.md
├── oclint-core
├── oclint-driver
├── oclint-metrics
├── oclint-reporters
├── oclint-rules
└── oclint-scripts

复制代码
  • cd进入oclint-scripts文件加,执行./make。大约30分钟后编译完成,大概过程是下载LLVM、clang的源代码,编译LLVM、clang与OCLint的默认规则。git

  • 编译成功后就能够写规则而且编译执行了。为了方便,OCLint提供了一个叫scaffoldRule的脚本程序,它在oclint-rules目录下。咱们经过他传入要生成的规则名,级别,类型,脚本就会在目录oclint-rules/rules/custom/自动帮咱们生成一个模板代码,而且加入编译路径中。举个例子:github

# 生成一个名为BGTestRule类型是ASTVisitor的规则模板
python scaffoldRule BGTestRule -t ASTVisitor

复制代码

生成两个文件:json

# CMakeLists.txt 是对规则BGTestRule的编译描述,由make程序在编译时使用。

├── custom
│   ├── CMakeLists.txt
│   └── BGTestRule.cpp

复制代码
  • 接着就能够对新添加的内容进行编译了,不过相比于从新执行./make来讲有一个更加优雅的办法,就是将规则相关的内容整合成一个Xcode工程,而且咱们的每一个规则都是一个scheme,编译时能够只选择编译那个选择的规则生成对应的dylib。很简单,OCLint工程使用CMakeLists的方式维护各个文件的依赖关系,咱们可使用CMake自带的功能将这些CMakeLists生成一个xcodeproj工程文件。
# 在OCLint源码目录下创建一个文件夹,我这里命名为oclint-xcoderules

mkdir oclint-xcoderules
cd oclint-xcoderules

# 建立一个脚本(代码以下段),并执行它(我写的方便修改参数,其实里面就一句命令,直接执行也行)(PS:刚建立的文件是没有执行权限的,不要忘了chmod)

./create-xcode-rules.sh
复制代码

脚本内容:xcode

#! /bin/sh -e

cmake -G Xcode -D CMAKE_CXX_COMPILER=../build/llvm-install/bin/clang++  -D CMAKE_C_COMPILER=../build/llvm-install/bin/clang -D OCLINT_BUILD_DIR=../build/oclint-core -D OCLINT_SOURCE_DIR=../oclint-core -D OCLINT_METRICS_SOURCE_DIR=../oclint-metrics -D OCLINT_METRICS_BUILD_DIR=../build/oclint-metrics -D LLVM_ROOT=../build/llvm-install/ ../oclint-rules
复制代码

执行bash

sh ./create-xcode-rules.sh
复制代码

因而咱们就获得了Xcode工程:闭包

├── CMakeCache.txt
├── CMakeFiles
├── CMakeScripts
├── OCLINT_RULES.build
├── OCLINT_RULES.xcodeproj
├── cmake_install.cmake
├── create-xcode-rules.sh
├── lib
├── rules
└── rules.dl
复制代码

打开OCLINT_RULES.xcodeproj:app

  • 选择本身的规则,编译,成功后就能够在Products下看到生成的dylib了.

  • Jenkins/Xcode 中写入oclint的命令:

myworkspace=XXXXXX.xcworkspace # 替换workspace的名字
myscheme=XXXXX # 替换scheme的名字
xcodebuild -workspace $myworkspace -scheme $myscheme clean&&
xcodebuild -workspace $myworkspace -scheme $myscheme \
-configuration Debug \
COMPILER_INDEX_STORE_ENABLE=NO | xcpretty -r json-compilation-database -o compile_commands.json&&
oclint-json-compilation-database -e Pods -- \
-report-type pmd -o pmd.xml  \
-R 这里是你编译生成的dylib目录 (ps :/oclint/oclint-xcoderules/rules.dl/Debug) \
-max-priority-1=9999 \
-max-priority-2=9999 \
-max-priority-3=9999; \
rm compile_commands.json;
if [ -f ./pmd.xml ]; then echo '-----分析完毕-----'
else echo "-----分析失败-----"; fi

复制代码

##如何自定义规则 经过查看官方文档,咱们发现OCLint的原理是调用clang的API把一个个源文件生成一个一个AST语法树,而后遍历树中的每一个节点传入各个规则的整个过程,在Xcode8以前,Xcode各类代码分析插件也是这样的原理操做的,详情看参考文档4,5。

###分析默认规则 下面的例子是默认规则中较为简单的一个:判断行长度是否超过了限制长度

class LongLineRule : public AbstractSourceCodeReaderRule
{
public:
	//重载方法 输出规则名称
    virtual const string name() const override {
        return "long line";
    }
	//重载方法 输出规则优先级
    virtual int priority() const override {
        return 3;
    }
	//重载方法 输出规则分类
    virtual const string category() const override {
        return "size";
    }

	#ifdef DOCGEN
	//重载方法 输出规则使用版本
    virtual const std::string since() const override {
        return "0.6";
    }
	//重载方法 输出规则描述
    virtual const std::string description() const override {
        return "当某行代码长度过长, "
            "它很大程度上损害了可读性。建议将长行代码分割成多行.";
    }
	//重载方法 输出规则示例
    virtual const std::string example() const override {
        return R"rst( .. code-block:: cpp void example() { int a012345678901234567890123456789...1234567890123456789012345678901234567890123456789; } )rst";
    }
	//重载方法 输出规则可配置参数
    virtual const std::map<std::string, std::string> thresholds() const override
    {
        std::map<std::string, std::string> thresholdMapping;
        thresholdMapping["LONG_LINE"] = "每行代码长度限制, 默认长度 200 .";
        return thresholdMapping;
    }
	#endif
	//核心回调 这个回调返回源文件中的每一行 规则方法就写这里
    virtual void eachLine(int lineNumber, string line) override {
        int threshold = RuleConfiguration::intForKey("LONG_LINE", 200);
        int currentLineSize = line.size();
        if (currentLineSize > threshold)
        {
            string description = "该行长度: " + toString<int>(currentLineSize) +
            " 字符,默认规则限制: " + toString<int>(threshold);
            //返回闭包 此时产生一个警告
            addViolation(lineNumber, 1, lineNumber, currentLineSize, this, description);
        }
    }
};

复制代码

###简单的自定义规则: 到这里咱们看新建立的规则模板就会发现,模板中这些以Visit开头的百十个函数都是OCLint提供给开发者的回调函数.只要在这写回调方法中写规则的核心代码就OK啦. 示例:

//重载方法 源文件中全部的变量都会到这里 传入的参数是AST树节点的类型
 	bool VisitVarDecl(VarDecl *declaration)
    {
        checkVarName(declaration);
        return true;
    }
    //检测变量是否合法  
    void checkVarName(VarDecl *decl)
    {
        StringRef className = decl -> getName();
        
        //必须以小写字母开头
        char c = className[0];
        if (isUppercase(c))
        {
            //修正提示
            std::string tempName = className;
            tempName[0] = toUppercase(c);
            StringRef replacement(tempName);
            
            string description = tempName + "首字母应该为小写";
            //抛出警告
            addViolation(decl, this, description);
        }
    }

复制代码

分析AST语法树

如无必要,勿增实体, 分析AST的文章好多,详情看参考文档2,6.

###难点 规则代码需用C++来写,复杂规则须要查文档->dump看AST语法树->生成dylib->复制dylib到OCLint规则目录下->执行检测->查看报告->发现问题->修改代码从新生成dylib,暂时还未找到解决方法.

##总结 如上所述,基于Clang的Oclint用于风格检查,能够发现和修改的更可能是一种格式上的约定和某些明显的不允许或无效逻辑,虽可解决很多问题,可是也有其局限性。实际工做中,一方面可限制使用某些固定的风格,更重要的是保持团队风格的统一和规范,提升其可读性。

##参考文档

相关文章
相关标签/搜索