從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用於風格檢查,能夠發現和修改的更可能是一種格式上的約定和某些明顯的不允許或無效邏輯,雖可解決很多問題,可是也有其侷限性。實際工做中,一方面可限制使用某些固定的風格,更重要的是保持團隊風格的統一和規範,提升其可讀性。

##參考文檔

相關文章
相關標籤/搜索