一文說清 OCLint 源碼解析及工作流分析

轉(zhuǎn)自

完成閱讀的收獲

  1. 了解靜態(tài)代碼審核技術(shù)的原理
  2. 了解靜態(tài)代碼審核技術(shù)工作流

不得不提的 Clang

由于 OCLint 是一個(gè)基于 Clang tool 的靜態(tài)代碼分析工具,所以不得不提一下 Clang。
Clang 作為 LLVM 的子項(xiàng)目, 是一個(gè)用來編譯 c,c++,以及 oc 的編譯器。

OCLint 本身是基于 Clang tool 的,換句話說相當(dāng)于做了一層封裝。
它的核心能力是對(duì) Clang AST 進(jìn)行分析,最后輸出違反規(guī)則的代碼信息,并且導(dǎo)出指定格式的報(bào)告。

接下來就讓我們看看作為輸入信息的 Clang AST 是什么樣子的。

Clang AST

Clang AST 是在編譯器編譯時(shí)的一個(gè)中間產(chǎn)物,從詞法分析,語法分析(生成 AST),到語義分析,生成中間代碼。

image.png

抽象語法樹示例

這里先對(duì)抽象語法樹有一個(gè)初步的印象。

//Example.c
#include <stdio.h>
int global;
void myPrint(int param) {
    if (param == 1)
        printf("param is 1");
    for (int i = 0 ; i < 10 ; i++ ) {
        global += i;
    }
}
int main(int argc, char *argv[]) {
    int param = 1;
    myPrint(param);
    return 0;
}
image.png

這里可以清晰的看到,這一段代碼的每一個(gè)元素與其子節(jié)點(diǎn)的關(guān)系。其中的節(jié)點(diǎn)有兩大類型,一個(gè)是 Stmt 類,包括 Expr 表達(dá)式類也是繼承于 Stmt,它是語句,有一定操作;另一大類元素是 Decl 類,即定義。所有的類,方法,函數(shù)變量均是一個(gè) Decl 類 (這兩個(gè)類互不兼容,需要特殊容器節(jié)點(diǎn)來轉(zhuǎn)換,比如 DeclStmt 節(jié)點(diǎn)) 。另外從數(shù)據(jù)結(jié)構(gòu)中可以看到,這個(gè)樹是單向的,只有從某一個(gè)頂層元素向下訪問。

在終端中可以用如下指令查看語法樹:

clang -Xclang -ast-dump -fsyntax-only Example.c

訪問抽象語法樹

無論是 Stmt 還是 Decl 都自帶迭代器,可以方便的遍歷所有節(jié)點(diǎn)元素,再判斷其類型進(jìn)行操作。不過在 Clang 中還有更方便的方法:繼承 RecursiveASTVisitor 類。
它是一個(gè) AST 樹遞歸器,可以遞歸的訪問一個(gè) AST 樹的所有節(jié)點(diǎn)。最常用的方法是 TraverseStmt 和 TraverseDecl。

例如我要訪問這么一段代碼中所有的函數(shù),即 FunctionDecl,并且輸出這些函數(shù)的名字,我就要重寫 (通過自定義 checker) 這么一個(gè)方法:

bool VisitFunctionDecl(FunctionDecl *decl){
    string name = decl->getNameAsString();
    printf(name);
    return true;
}

這樣,我們就能夠訪問到這棵 AST 樹中所有的 FunctionDecl 節(jié)點(diǎn),并且把其中函數(shù)名字給輸出出來了。

接下來我們看看 OCLint 的源碼,看看 OCLint 到底是如何工作的!

OCLint 源碼解析

首先看一下核心類關(guān)系圖,有一點(diǎn)初步的印象后,我們開始看代碼 ??

image.png

1 首先找到入口文件 oclint/driver/main.cpp,及入口函數(shù) main()

該文件的精簡后的代碼框架如下所示:

int main(int argc, const char **argv)
{
    llvm::cl::SetVersionPrinter(oclintVersionPrinter);
    // 構(gòu)造 parser 分析程序
    CommonOptionsParser optionsParser(argc, argv, OCLintOptionCategory);
    // 配置
    oclint::option::process(argv[0]);

    ...

// 構(gòu)造 analyzer
    oclint::RulesetBasedAnalyzer analyzer(oclint::option::rulesetFilter().filteredRules());
// 構(gòu)造 driver
    oclint::Driver driver;

    // 執(zhí)行分析
    driver.run(optionsParser.getCompilations(), optionsParser.getSourcePathList(), analyzer);

    std::unique_ptr<oclint::Results> results(std::move(getResults()));

    ostream *out = outStream();
    // 輸出報(bào)告
    reporter()->report(results.get(), *out);
    disposeOutStream(out);

    return handleExit(results.get());
}

2 接著查看核心的 Driver 類的關(guān)鍵代碼片段,有三個(gè)比較核心的方法 constructCompilers(),invoke(),run()

// 構(gòu)建編譯器
static void constructCompilers(std::vector<oclint::CompilerInstance *> &compilers,
    CompileCommandPairs &compileCommands,
    std::string &mainExecutable)
{
    for (auto &compileCommand : compileCommands) // 遍歷編譯命令集
    {
        std::vector<std::string> adjustedCmdLine =
            adjustArguments(compileCommand.second.CommandLine, compileCommand.first);

#ifndef NDEBUG
        printCompileCommandDebugInfo(compileCommand, adjustedCmdLine);
#endif

        LOG_VERBOSE("Compiling ");
        LOG_VERBOSE(compileCommand.first.c_str());
    std::string targetDir = stringReplace(compileCommand.second.Directory, "\\ ", " ");

        if(chdir(targetDir.c_str()))
        {
            throw oclint::GenericException("Cannot change dictionary into \"" +
                targetDir + "\", "
                "please make sure the directory exists and you have permission to access!");
        }
        clang::CompilerInvocation *compilerInvocation =
            newCompilerInvocation(mainExecutable, adjustedCmdLine);// 創(chuàng)建 CompilerInvocation 對(duì)象
        oclint::CompilerInstance *compiler = newCompilerInstance(compilerInvocation);
// 使用 clang 的 CompilerInvocation 對(duì)象 創(chuàng)建 oclint 的 CompilerInstance 對(duì)象,oclint 做了封裝
        compiler->start(); // clang::FrontendAction 核心是獲取到 action 并執(zhí)行
        if (!compiler->getDiagnostics().hasErrorOccurred() && compiler->hasASTContext())
        {
            LOG_VERBOSE(" - Success");
            compilers.push_back(compiler); // oclint 封裝的 CompilerInstance 對(duì)象放入集合中
        }
        else
        {
            LOG_VERBOSE(" - Failed");
        }
        LOG_VERBOSE_LINE("");
    }
}

// 實(shí)際的進(jìn)行分析的喚起方法
static void invoke(CompileCommandPairs &compileCommands,
    std::string &mainExecutable, oclint::Analyzer &analyzer)
{
    std::vector<oclint::CompilerInstance *> compilers; // 編譯器容器
    constructCompilers(compilers, compileCommands, mainExecutable);  // 構(gòu)建編譯器

    // collect a collection of AST contexts
    std::vector<clang::ASTContext *> localContexts;
    for (auto compiler : compilers) // 遍歷編譯器集合
    {
        localContexts.push_back(&compiler->getASTContext()); // 將 AST 上下文放入 上下文集合
    }

    // use the analyzer to do the actual analysis
    analyzer.preprocess(localContexts); // 將上下文集合送入分析器 預(yù)處理
    analyzer.analyze(localContexts); // 分析
    analyzer.postprocess(localContexts); // 發(fā)送處理

    // send out the signals to release or simply leak resources
    for (size_t compilerIndex = 0; compilerIndex != compilers.size(); ++compilerIndex)
    {
        compilers.at(compilerIndex)->end();
        delete compilers.at(compilerIndex);
    }
}
// main.cpp 調(diào)用的核心方法,執(zhí)行分析
void Driver::run(const clang::tooling::CompilationDatabase &compilationDatabase,
    llvm::ArrayRef<std::string> sourcePaths, oclint::Analyzer &analyzer)
{
    CompileCommandPairs compileCommands; // 生成編譯指令對(duì)容器
    constructCompileCommands(compileCommands, compilationDatabase, sourcePaths); // 構(gòu)造編譯指令對(duì)

    static int staticSymbol; // 靜態(tài)符號(hào)
    std::string mainExecutable = llvm::sys::fs::getMainExecutable("oclint", &staticSymbol);// 獲取 oclint 可執(zhí)行程序的路徑

    if (option::enableGlobalAnalysis()) // 啟用全局分析的情況
    {
        invoke(compileCommands, mainExecutable, analyzer);// 調(diào)用 invoke 方法,注意 analyzer 也一并入?yún)?    }
    else 
    { // 非全局分析的情況 逐個(gè) compileCommand 進(jìn)行分析
        for (auto &compileCommand : compileCommands)
        {
            CompileCommandPairs oneCompileCommand { compileCommand };
            invoke(oneCompileCommand, mainExecutable, analyzer);
        }
    }

    if (option::enableClangChecker()) // 啟用 clang checker
    {
        invokeClangStaticAnalyzer(compileCommands, mainExecutable); // 調(diào)用 clang 的靜態(tài)分析器
    }
}

3 最后一個(gè)就是 RulesetBasedAnalyzer 類,這個(gè)類的代碼量非常少,如下所示

void RulesetBasedAnalyzer::analyze(std::vector<clang::ASTContext *> &contexts)
{
    for (const auto& context : contexts)
    {
        LOG_VERBOSE("Analyzing ");
        auto violationSet = new ViolationSet();
        auto carrier = new RuleCarrier(context, violationSet); // 規(guī)則運(yùn)載者,context 是傳遞給規(guī)則來分析的數(shù)據(jù),violationSet 是用于存放處理好的結(jié)果集
        LOG_VERBOSE(carrier->getMainFilePath().c_str());
        for (RuleBase *rule : _filteredRules) // 遍歷已經(jīng)過濾的規(guī)則集合
        {
            rule->takeoff(carrier); // 調(diào)用規(guī)則的 takeoff
        }
        ResultCollector *results = ResultCollector::getInstance(); // 取得結(jié)果收集器實(shí)例
        results->add(violationSet); // 將規(guī)則處理好的數(shù)據(jù)加入收集器
        LOG_VERBOSE_LINE(" - Done");
    }
}

從上面的代碼可以看出 analyzer 會(huì)遍歷規(guī)則集合,來調(diào)用 rule 的 takeoff 方法。rule 的基類是 RuleBase,這個(gè)基類含有一個(gè) RuleCarrier 的示例作為成員,RuleCarrier包含了每個(gè)文件對(duì)應(yīng)的 ASTContext 和 violationSet,violationSet 用來存放違例的相關(guān)信息。
rule 的職責(zé)就是,檢查其成員變量 ruleCarrier 的 ASTContext,有違例的情況,就將結(jié)果寫入 ruleCarrier 的 violationSet 中。

高級(jí):自定義規(guī)則

到目前為止,我們已經(jīng)了解到 oclint 的基本用法,以及工作流程。

接下來更靈活也是有更高的使用難度的部分--自定義規(guī)則。

規(guī)則必須實(shí)現(xiàn) RuleBase 類或其派生的抽象類。不同的規(guī)則專注于不同的抽象級(jí)別,例如,某些規(guī)則可能必須非常深入地研究代碼的控制流,相反,某些規(guī)則僅通過讀取源代碼的字符串來檢測(cè)缺陷。

oclint 提供了三個(gè)抽象類,以便我們來編寫自定義規(guī)則。
AbstractSourceCodeReaderRule(源代碼讀取器規(guī)則),AbstractASTVisitorRule(AST 訪問者規(guī)則),以及 AbstractASTMatcherRule(AST 匹配器規(guī)則)。

按照官方文檔的說法,由于 AST 匹配器規(guī)則 具有良好的可讀性,除非性能是個(gè)大問題,我們可能大多數(shù)時(shí)候都會(huì)選擇編寫AST匹配器規(guī)則。

AST 訪問者規(guī)則是基于訪問者模式,你只需要重載某些方法(該抽象類提供了一系列節(jié)點(diǎn)被訪問的接口),即可處理相應(yīng)節(jié)點(diǎn)內(nèi)的校驗(yàn)邏輯。(由于 OCLint 使用的是 Clang 生成的抽象語法樹,因此了解 Clang AST 的 API 在編寫規(guī)則時(shí)非常有幫助相關(guān)鏈接)。

AST 匹配器規(guī)則是基于匹配模式,你需要構(gòu)造一些匹配器并加載。只要找到匹配項(xiàng),callback 就以該 AST 節(jié)點(diǎn)作為參數(shù)調(diào)用 method,你就可以在 callback 中收集違例信息。(關(guān)于匹配器的更多信息看這里

這里簡單就說這么多,我們只需要知道 oclint 提供了抽象類,用于實(shí)現(xiàn)自定義規(guī)則。關(guān)于如何編寫一個(gè)規(guī)則的部分會(huì)在下一節(jié)展開。

創(chuàng)建規(guī)則——scaffoldRule 腳本

這是由 oclint 提供的一個(gè)腳手架。相關(guān)介紹如下使用腳手架創(chuàng)建規(guī)則
可以使用該腳本可以方便的創(chuàng)建自定義規(guī)則。

編寫規(guī)則

通過閱讀 oclint 的官方文檔,以及閱讀 Clang AST 的介紹?,F(xiàn)在我們已經(jīng)知道了,oclint 的大致工作方式。首先通過調(diào)用 Clang 的 api 把源文件一個(gè)個(gè)的生成對(duì)應(yīng)的 AST;其次遍歷 AST 中的每個(gè)節(jié)點(diǎn),并根據(jù)相應(yīng)的規(guī)則將違例情況寫入違例結(jié)果集;最后根據(jù)配置的報(bào)告類型,將違例結(jié)果輸出成指定的報(bào)告格式。

先上一個(gè) oclint 規(guī)則編寫思路的腦圖,有個(gè)初步的印象即可。

image.png

按照上文,我們現(xiàn)在已經(jīng)得到了一個(gè) xcodeproj 工程。現(xiàn)在可以打開我們創(chuàng)建的規(guī)則的 cpp 源文件。

首先我們可以看到,使用腳手架生成的規(guī)則,模板代碼有近 2000 行,是不是有點(diǎn)慌? 不用擔(dān)心。這些模板里,大多都是 Visit 開頭的方法,這是 oclint 提供給我們的回調(diào)方法, 也就是說在訪問到 AST 上相應(yīng)的節(jié)點(diǎn)時(shí)就會(huì)觸發(fā)的方法。


下面我們來看一個(gè)實(shí)際的案例,已經(jīng)用在 iOS 組的代碼檢查中的一個(gè)規(guī)則。
這個(gè)規(guī)則所做的工作大致如下,按照 cocoa 的規(guī)范要求來檢查 if else 條件分支的格式。
具體的格式要求是這樣的,if else 和后面跟著的括號(hào)以及花括號(hào)要分割開,可以使用空格和換行符。
示例代碼如下:

void example()
{
    int a = 1;
    if(a > 0) { // (左側(cè)無空格或換行不合規(guī)
        a = 10;
    }

    if (a > 0){ // )右側(cè)無空格或換行不合規(guī)
        a = 10;
    }

    if (a > 0)
    {
        a = 10;
    }else { // }右側(cè)無空格或換行不合規(guī)
        a = -1;
    }

    if (a > 0)
    {
        a = 10;
    } else{ // {左側(cè)無空格或換行不合規(guī)
        a = -1;
    }
}

1 首先在終端中使用 dump 查看 AST(上文已經(jīng)介紹了如何查看 AST,如果沒看過建議先看看)。

屏幕上一連串花花綠綠的字符閃過,最后停在了這里!
沒錯(cuò),這正是我們需要找的。

image.png

可以很清楚的看到,最上方的變量聲明 VarDecl,以及下方的條件語句 IfStmt。

2 需要檢驗(yàn)的節(jié)點(diǎn)名稱已經(jīng)確定,就是 IfStmt。
3 接下來,在已經(jīng)生成的規(guī)則模板中找對(duì)應(yīng)的回調(diào)方法。
我推測(cè),應(yīng)該叫做 VisitXXIfStmt 之類的。
果然不出所料,我們找到了!VisitIfStmt 這個(gè)方法,看起來正是我們所需要的。
4 緊接著,我們需要獲取節(jié)點(diǎn)名稱和節(jié)點(diǎn)描述。(詳細(xì)的代碼可以參看下方提供的完整規(guī)則文件)
5 最后是判斷這里的方法名是否符合規(guī)則。(可以使用 llvm,Clang,以及 std 提供的各種函數(shù),如果有你需要的)
6 如果檢測(cè)出來的方法名是不符合規(guī)范的,將節(jié)點(diǎn)及描述信息加入 violationSet。

到這里,整體的編寫流程已經(jīng)完成了。相信你看完下方的實(shí)例代碼,以及再多讀幾個(gè)官方提供的規(guī)則代碼之后,很快就可以舉一反三的寫出自己的規(guī)則了。

這里直接給出上文規(guī)則的完整實(shí)現(xiàn):

#include "oclint/AbstractASTVisitorRule.h"
#include "oclint/RuleSet.h"

using namespace std;
using namespace clang;
using namespace oclint;

class KirinzerTestRule : public AbstractASTVisitorRule<KirinzerTestRule>
{
public:
    virtual const string name() const override
    {
        return "if else format";
    }

    virtual int priority() const override
    {
        return 2;
    }

    virtual const string category() const override
    {
        return "controversial";
    }

#ifdef DOCGEN
    virtual const std::string since() const override
    {
        return "20.11";
    }

    virtual const std::string description() const override
    {
        return "用于檢查 if else 條件分支中的括號(hào)是否符合編碼規(guī)范";
    }

    virtual const std::string example() const override
    {
        return R"rst(
.. code-block:: cpp

        void example()
        {
        int a = 1;
        if(a > 0) { // (左側(cè)無空格或換行不合規(guī)
        a = 10;
        }

        if (a > 0){ // )右側(cè)無空格或換行不合規(guī)
        a = 10;
        }

        if (a > 0)
        {
        a = 10;
        }else { // }右側(cè)無空格或換行不合規(guī)
        a = -1;
        }

        if (a > 0)
        {
        a = 10;
        } else{ // {左側(cè)無空格或換行不合規(guī)
        a = -1;
        }
        }
        )rst";
    }

#endif

    bool VisitIfStmt(IfStmt *node)
    {
        clang::SourceManager *sourceManager = &_carrier->getSourceManager();

        SourceLocation begin = node->getIfLoc();
        SourceLocation elseLoc = node->getElseLoc();
        SourceLocation end = node->getEndLoc();

        int length = sourceManager->getFileOffset(end) - sourceManager->getFileOffset(begin) + 1; // 計(jì)算該節(jié)點(diǎn)源碼的長度
        string sourceCode = StringRef(sourceManager->getCharacterData(begin), length).str(); // 從起始位置按指定長度讀取字符數(shù)據(jù)
//        printf("%s\n", sourceCode.c_str());

        // 檢查 if 左括號(hào)
        std::size_t found = sourceCode.find("if (");
        if (found==std::string::npos) {
//            printf("if ( 格式不正確\n");
            AppendToViolationSet(node, Description());
        }

        // 檢查 if 右括號(hào)
        found = sourceCode.find(") {");
        if (found==std::string::npos) {
            found = sourceCode.find(")\n");
            if (found ==std::string::npos) {
//                printf("if 右括號(hào) 格式不正確\n");
                AppendToViolationSet(node, Description());
            }
        }

        // 沒有 else 分支就不再進(jìn)行檢查
        if (!elseLoc.isValid()) {
            return true;
        }

        // 檢查 else 左括號(hào)
        found = sourceCode.find("} else");
        if (found==std::string::npos) {
            found = sourceCode.find("}\n");
            if (found==std::string::npos) {
//                printf("} else 格式不正確\n");
                AppendToViolationSet(node, Description());
            }
        }

        // 檢查 else 右括號(hào)
        found = sourceCode.find("else {");
        if (found==std::string::npos) {
            found = sourceCode.find("else\n");
            if (found==std::string::npos) {
//                printf("else { 格式不正確\n");
                AppendToViolationSet(node, Description());
            }
        }

        return true;
    }

    // 將違例信息追加進(jìn)結(jié)果集
    bool AppendToViolationSet(IfStmt *node, string description) {
        addViolation(node, this, description);
    }

    string Description() {
        return "格式不正確";
    }
};

static RuleSet rules(new KirinzerTestRule());

調(diào)試規(guī)則

根據(jù)前面的所學(xué)到的內(nèi)容,我們知道了規(guī)則的實(shí)際體現(xiàn)形式為 dylib 文件。那么如果編寫 cpp 的時(shí)候沒辦法調(diào)試,那真的是噩夢(mèng)一般的體驗(yàn)。將我們現(xiàn)在遇到的問題,如何調(diào)試 oclint 規(guī)則?

1 首先需要一個(gè) Xcode 工程。

oclint 工程使用 CMakeLists 來維護(hù)依賴關(guān)系。我們也可利用 CMake 來將 CMakeLists 生成 xcodeproj。你可以對(duì)每個(gè)文件夾生成一個(gè) Xcode 工程,在這里我們對(duì) oclint-rules 生成對(duì)應(yīng)的 Xcode 工程。

// 在OCLint源碼目錄下建立一個(gè)文件夾,我這里命名為oclint-xcoderules
mkdir oclint-xcoderules
cd oclint-xcoderules
// 執(zhí)行如下命令
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

2 Xcode 工程創(chuàng)建好之后,我們需要對(duì)指定的 Scheme 添加啟動(dòng)參數(shù)。并且在 Scheme 的 Info 一欄選擇 Executable ,選擇上文中編譯完成的 oclint 可執(zhí)行文件。

Tip: 編譯生成的oclint可執(zhí)行文件在根目錄下 build/oclint-release/bin 目錄下,以最新版的 oclint 20.11 為例,生成的文件名為 oclint-20.11,會(huì)被 Finder 識(shí)別為 Document 類型。(.11被識(shí)別為了后綴),雖然并不影響在終端的直接調(diào)用,但是我們后續(xù)的調(diào)試中會(huì)需要在 Xcode 中通過 Finder 來選取這個(gè)可執(zhí)行文件,但是由于類型被識(shí)別錯(cuò)誤,會(huì)導(dǎo)致無法點(diǎn)擊選中。所以在這里我們就刪除小數(shù)點(diǎn),修改可執(zhí)行文件名為 oclint-2011 并且沒有任何后綴即可。(注意修改的時(shí)候,右鍵getInfo,在文件名和擴(kuò)展名那一欄來修改,還有注意是否隱藏了拓展名)。

啟動(dòng)參數(shù)如下:
(第一個(gè)參數(shù)是規(guī)則加載路徑,第二個(gè)是測(cè)試規(guī)則用文件)

>-R=/Users/developer/TempData/oclint/oclint-xcoderules/rules.dl/Debug /Users/developer/TempData/oclint/oclint-xcoderules/test2.m -- -x objective-c -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk

準(zhǔn)備完成后即可運(yùn)行規(guī)則,在控制臺(tái)中可以輸出你的規(guī)則運(yùn)行的結(jié)果以及調(diào)試信息。

image.png

使用規(guī)則

使用 Xcode 編寫的規(guī)則完成編譯后,可以在 Xcode 的 Products group 中找到相應(yīng)的 dylib 文件。

默認(rèn)情況下,規(guī)則將從$(/path/to/bin/oclint)/../lib/oclint/rules目錄中加載,我們將其命名為“ 規(guī)則搜索路徑”或“ 規(guī)則加載路徑”。規(guī)則搜索路徑由一組動(dòng)態(tài)庫組成,這些庫在Linux,macOS和 Windows中具有擴(kuò)展名 so, dylib 以及 dll。

通過將新規(guī)則拖放到規(guī)則加載路徑中,可以立即使用它們。 因此,只需要將我們自定義規(guī)則生成的 dylib 放入默認(rèn)的規(guī)則加載目錄即可。當(dāng)然這里的規(guī)則目錄也是可以配置的。一個(gè)項(xiàng)目可以使用多個(gè)規(guī)則搜索路徑,可以為不同的項(xiàng)目指定不同的規(guī)則加載路徑。

更多詳細(xì)的配置參考這里的官方文檔:

選擇OCLint檢查規(guī)則

總結(jié)

使用靜態(tài)代碼檢查工具,可以高效的檢查出代碼中的潛在問題,在做持續(xù)的業(yè)務(wù)交付過程中,提高開發(fā)同學(xué)們對(duì)于編碼規(guī)范的重視,防止代碼的劣化,減少一些由于粗心導(dǎo)致的錯(cuò)誤。希望本文提及的靜態(tài)檢查工具,以及自定義規(guī)則的編寫的說明,能幫助大家寫出更高質(zhì)量,更優(yōu)雅,更美觀的代碼。

參考資料

簡述 LLVM 與 Clang 及其關(guān)系
Clang Tutorial
Clang Users Manual
oclint-docs v20.11

</article>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容