添加编译警告的另一种方式:开发 clang
添加編譯警告,除了使用上文 開發(fā) clang 插件:0 基礎(chǔ)感受底層組 提到的 clang 插件 ,
也可以直接開發(fā) clang
1, 開發(fā) clang,使用 ninja ,才能保證正常的開發(fā)速度
使用 Xcode 編譯 clang, 就慢了
可以使用 ninja + Xcode 配合開發(fā)
使用 Xcode 的代碼自動(dòng)補(bǔ)全、代碼提示、編譯檢查、函數(shù)跳轉(zhuǎn),方便
1.1 安裝 ninja
采用 ninja 構(gòu)建
- 檢查安裝了沒有
brew list | grep ^ninja
- 去安裝
brew install ninja
1.2 下載工程
下載 llvm-project
git clone https://github.com/llvm/llvm-project
1.3 代碼生成與編譯
這一步,會(huì)在后面反復(fù)使用,簡(jiǎn)稱為 job_O
- 使用 Ninja 生成 llvm 項(xiàng)目
進(jìn)入
cd /Users/jzd/Movies/A_B/llvm-projectX
等價(jià)于
cd /yourPath/llvm-project
代碼生成
cmake -S llvm -B build -G Ninja -DLLVM_ENABLE_PROJECTS="clang;libcxx;libcxxabi"
- 項(xiàng)目編譯
cd /Users/jzd/Movies/A_B/llvm-projectX/build
ninja clang
效果:
? build git:(main) ninja clang
[3345/3345] Creating executable symlink bin/clang
補(bǔ)充: Xcode 相關(guān),見上文
1.4 本文的例子是,檢查 if 語(yǔ)句的過度嵌套
看下效果,簡(jiǎn)單的 if 判斷,不報(bào)錯(cuò)
復(fù)雜的,才報(bào)錯(cuò)
要解決的問題
怎樣算復(fù)雜? 至少 3 層不同操作符,計(jì)算的嵌套
最后的效果
2. clang 開發(fā),階段一,識(shí)別 if 語(yǔ)句的 AST, 和簡(jiǎn)單的 warning 處理
添加 if 語(yǔ)句過度嵌套的編譯警告
來一小段,編譯原理
編譯啊,預(yù)處理,語(yǔ)法分析,詞法分析,語(yǔ)義分析,拿到 AST
拿到完整的抽象語(yǔ)法樹,分析 if 的節(jié)點(diǎn),是不是過于復(fù)雜
-
先對(duì)代碼進(jìn)行解析,parse
-
再語(yǔ)義分析,semantic analysis
2.1 定位到 clang 源代碼,對(duì) AST 中的 if 節(jié)點(diǎn),寫日志
定位到語(yǔ)義分析文件
/Users/jzd/Movies/A_B/llvm-projectX/clang/lib/Sema/SemaStmt.cpp
里面的這個(gè)方法,IF statement
添加兩行日志代碼,代碼生成與編譯,就是 job_O
補(bǔ)充: 怎么定位到的,可以看我在 CSDN 的筆記 clang 學(xué)習(xí)輔助
StmtResult Sema::ActOnIfStmt(SourceLocation IfLoc, bool IsConstexpr,SourceLocation LParenLoc, Stmt *InitStmt,ConditionResult Cond, SourceLocation RParenLoc,Stmt *thenStmt, SourceLocation ElseLoc,Stmt *elseStmt) {//...// 添加下面兩句 llvm:: dbgs() << "處于 ActOnIfStmt, 發(fā)現(xiàn)了 if 條件判斷 \n";CondExpr->dump();return BuildIfStmt(IfLoc, IsConstexpr, LParenLoc, InitStmt, Cond, RParenLoc,thenStmt, ElseLoc, elseStmt); }2.1.1, 看簡(jiǎn)單效果
- 上例子
? build git:(main) ? cat /Users/jzd/Movies/A_a/Clang/proj/collect/999998/999998/test.cpp
代碼很簡(jiǎn)單
void test(int a, int b){if (a > 0 && b > 0){} }- 命令, ( 這一步,調(diào)試頻繁,下文簡(jiǎn)稱為 job_debug )
? build git:(main) ? /Users/jzd/Movies/A_B/llvm-projectX/build/bin/clang -c /Users/jzd/Movies/A_a/Clang/proj/collect/999998/999998/test.cpp
dump 到的 AST
處于 ActOnIfStmt, 發(fā)現(xiàn)了 if 條件判斷 BinaryOperator 0x7fe8e9075ea0 '_Bool' '&&' |-BinaryOperator 0x7fe8e9075e08 '_Bool' '>' | |-ImplicitCastExpr 0x7fe8e9075df0 'int' <LValueToRValue> | | `-DeclRefExpr 0x7fe8e9075db0 'int' lvalue ParmVar 0x7fe8e9075b68 'a' 'int' | `-IntegerLiteral 0x7fe8e9075dd0 'int' 0 `-BinaryOperator 0x7fe8e9075e80 '_Bool' '>'|-ImplicitCastExpr 0x7fe8e9075e68 'int' <LValueToRValue>| `-DeclRefExpr 0x7fe8e9075e28 'int' lvalue ParmVar 0x7fe8e9075be8 'b' 'int'`-IntegerLiteral 0x7fe8e9075e48 'int' 02.2 從 dump 日志,到報(bào)錯(cuò) warning
2.2.1 warning 源文件修改
進(jìn)入到語(yǔ)義分析的警告表格
/Users/jzd/Movies/A_B/llvm-projectX/clang/include/clang/Basic/DiagnosticSemaKinds.td
添加一行警告,于文件結(jié)尾
// 添加這一行 def warn_if_condition_too_complex: Warning<"if 語(yǔ)句,太過復(fù)雜,修下吧 ...">;} // end of sema component.2.2.2 繼續(xù)修改語(yǔ)義分析文件
/Users/jzd/Movies/A_B/llvm-projectX/clang/lib/Sema/SemaStmt.cpp
輔助命令
open /Users/jzd/Movies/A_B/llvm-projectX/clang/lib/Sema/SemaStmt.cpp
- 添加方法
- 調(diào)用方法
還是上面提到的,語(yǔ)義分析方法
StmtResult Sema::ActOnIfStmt(SourceLocation IfLoc, bool IsConstexpr,SourceLocation LParenLoc, Stmt *InitStmt,ConditionResult Cond, SourceLocation RParenLoc,Stmt *thenStmt, SourceLocation ElseLoc,Stmt *elseStmt) {//...// 添加調(diào)用DiagnoseIf(Cond.Condition.get(), *this);return BuildIfStmt(IfLoc, IsConstexpr, LParenLoc, InitStmt, Cond, RParenLoc,thenStmt, ElseLoc, elseStmt); }2.2.3 看效果
還是上面的例子, 簡(jiǎn)單的 cpp 代碼
生成代碼,再編譯, job_O 一下
使用編譯出的 clang 調(diào)試, job_debug 下
建好的 warning,投入使用
? build git:(main) ? /Users/jzd/Movies/A_B/llvm-projectX/build/bin/clang -c /Users/jzd/Movies/A_a/Clang/proj/collect/999998/999998/test.cpp /Users/jzd/Movies/A_a/Clang/proj/collect/999998/999998/test.cpp:13:15: warning: if 語(yǔ)句,太過復(fù)雜,修下吧 ...if (a > 0 && b > 0){~~~~~~^~~~~~~~ 1 warning generated.警告的控制,通過診斷組 diagnostic group
體現(xiàn)在,通過命令行的 flag , 激活該編譯警告,或使該編譯警告失效
這次換了一個(gè)文件路徑
/Users/jzd/Movies/A_B/llvm-projectX/clang/include/clang/Basic/DiagnosticGroups.td
在文尾添加
def ComplexIf: DiagGroup<"complex-condition">;接著改,上面的語(yǔ)義分析的警告表格
/Users/jzd/Movies/A_B/llvm-projectX/clang/include/clang/Basic/DiagnosticSemaKinds.td
修改警告的定義
DefaultIgnore , 這個(gè)的意思是,讓該警告默認(rèn)失效
def warn_if_condition_too_complex: Warning<"if 語(yǔ)句,太過復(fù)雜,修下吧 ...">, InGroup<ComplexIf>, DefaultIgnore;修改調(diào)用處的代碼,代碼位置見上面
// 精確使用, 編譯警告if(!Diags.isIgnored(diag:: warn_if_condition_too_complex, Cond.Condition.get()->getExprLoc())){// 這個(gè) if 判斷,算性能優(yōu)化DiagnoseIf(Cond.Condition.get(), *this);}效果看下
job_O 下后,
job_debug 等價(jià)于
? build git:(main) ? /Users/jzd/Movies/A_B/llvm-projectX/build/bin/clang -Wno-complex-condition -c /Users/jzd/Movies/A_a/Clang/proj/collect/999998/999998/test.cpp
默認(rèn)的選項(xiàng)是,使失效,
因?yàn)?CPU 計(jì)算編譯警告,需要時(shí)間
-Wno-complex-condition, 默認(rèn)不需要 warning no , 這個(gè)編譯組 complex-condition
激活 if 檢查的編譯警告,使用選項(xiàng) -Wcomplex-condition
( 這一步,新的調(diào)試,下文簡(jiǎn)稱為 job_debug1 )
/Users/jzd/Movies/A_B/llvm-projectX/build/bin/clang -Wcomplex-condition -c /Users/jzd/Movies/A_a/Clang/proj/collect/999998/999998/test.cpp
3. clang 開發(fā),階段 2,復(fù)雜 if 語(yǔ)句的 warning 處理
3.1 ,計(jì)算出 if 嵌套語(yǔ)句的復(fù)雜度
回到了我們的 DSA
3.1.1 感性認(rèn)識(shí),簡(jiǎn)單代碼語(yǔ)句的 AST 展開
-
函數(shù)的聲明 f1 ,對(duì)應(yīng)聲明表達(dá)式,declaration reference expresssion
-
對(duì)函數(shù) f1,做了一次隱式轉(zhuǎn)換,implicit cast expression
拿到了 f1 的函數(shù)指針
-
拿到函數(shù)指針調(diào)用的結(jié)果,f1(),call expression
-
然后是各種簡(jiǎn)單的運(yùn)算
3.1.2 算 if 嵌套語(yǔ)句復(fù)雜度
關(guān)注的是,二元操作符節(jié)點(diǎn)
遇到了 3 層,就要報(bào)錯(cuò)了
3.1.3 修改代碼
- 計(jì)算層級(jí),對(duì)樹深度優(yōu)先遍歷
- 調(diào)用部分
3.1.5 驗(yàn)證下
編譯 job_O + 調(diào)試 job_debug1
- 新的用例
- 結(jié)果
例子 0~4 , 正常
例子 5,warning 出現(xiàn)重復(fù),
重復(fù)的 warning,算噪音 noisy
簡(jiǎn)單的算法錯(cuò)誤
把對(duì)應(yīng)的樹,畫一下,就明白了
? build git:(main) ? /Users/jzd/Movies/A_B/llvm-projectX/build/bin/clang -Wcomplex-condition -c /Users/jzd/Movies/A_a/Clang/proj/collect/999998/999998/test.cpp /Users/jzd/Movies/A_a/Clang/proj/collect/999998/999998/test.cpp:18:37: warning: if 語(yǔ)句,太過復(fù)雜,修下吧 ... [-Wcomplex-condition]if (((a > 0 || b > 0) && c > 0) || d > 0){ }~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~ /Users/jzd/Movies/A_a/Clang/proj/collect/999998/999998/test.cpp:20:48: warning: if 語(yǔ)句,太過復(fù)雜,修下吧 ... [-Wcomplex-condition]if ((((a > 0 || b > 0) && c > 0) || d > 0) && e > 0){ }~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~ /Users/jzd/Movies/A_a/Clang/proj/collect/999998/999998/test.cpp:22:48: warning: if 語(yǔ)句,太過復(fù)雜,修下吧 ... [-Wcomplex-condition]if (((a > 0 || b > 0) && (c > 0 || d > 0)) || e > 0){ }~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~ /Users/jzd/Movies/A_a/Clang/proj/collect/999998/999998/test.cpp:22:48: warning: if 語(yǔ)句,太過復(fù)雜,修下吧 ... [-Wcomplex-condition]if (((a > 0 || b > 0) && (c > 0 || d > 0)) || e > 0){ }~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~ 4 warnings generated.3.2, 解決 bug 1, warning 重復(fù)
起因,如圖
解決,遇到一個(gè)就報(bào)錯(cuò),結(jié)束
代碼修改
bool DiagnoseIf(const Expr * IfRoot, Sema &S, const Expr * CurrentExpr, int CurrentNestingLevel){// 忽略函數(shù)的 Imp 指針轉(zhuǎn)化CurrentExpr = CurrentExpr->IgnoreParenImpCasts();// dyn_cast 動(dòng)態(tài)轉(zhuǎn)化下,看是不是二元操作符if (const auto * BinaryOp = dyn_cast<BinaryOperator>(CurrentExpr)){// 看這個(gè)二元操作符,是不是 && 或 ||if (BinaryOp->getOpcode() == BO_LAnd || BinaryOp->getOpcode() == BO_LOr){if (CurrentNestingLevel >= 2){S.Diag(IfRoot->getExprLoc(), diag:: warn_if_condition_too_complex) << IfRoot->getSourceRange();return false;}else{// 對(duì)樹,深度優(yōu)先遍歷,有一個(gè)簡(jiǎn)單的遞歸if (DiagnoseIf(IfRoot, S, BinaryOp->getLHS(), CurrentNestingLevel + 1)){if (DiagnoseIf(IfRoot, S, BinaryOp->getRHS(), CurrentNestingLevel + 1)){return true;}else{return false;}}else{return false;}}}}return true; }改完,看效果
流程走一走,
兩個(gè) job 走一遍
正常 ( 還是剛才的用例 )
? build git:(main) ? /Users/jzd/Movies/A_B/llvm-projectX/build/bin/clang -Wcomplex-condition -c /Users/jzd/Movies/A_a/Clang/proj/collect/999998/999998/test.cpp /Users/jzd/Movies/A_a/Clang/proj/collect/999998/999998/test.cpp:18:37: warning: if 語(yǔ)句,太過復(fù)雜,修下吧 ... [-Wcomplex-condition]if (((a > 0 || b > 0) && c > 0) || d > 0){ }~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~ /Users/jzd/Movies/A_a/Clang/proj/collect/999998/999998/test.cpp:20:48: warning: if 語(yǔ)句,太過復(fù)雜,修下吧 ... [-Wcomplex-condition]if ((((a > 0 || b > 0) && c > 0) || d > 0) && e > 0){ }~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~ /Users/jzd/Movies/A_a/Clang/proj/collect/999998/999998/test.cpp:22:48: warning: if 語(yǔ)句,太過復(fù)雜,修下吧 ... [-Wcomplex-condition]if (((a > 0 || b > 0) && (c > 0 || d > 0)) || e > 0){ }~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~ 3 warnings generated.4. clang 開發(fā),階段 3,不斷 debug, 遇到新的情況
4.1 , 要考慮 AST 的構(gòu)造方式
4.1.1 新的用例
簡(jiǎn)單的重復(fù),看起來,沒有嵌套
if (a > 0 || b > 0 || c > 0 || d > 0 || e > 0){ }4.1.2 跑一下 ( 流程同上 )
/Users/jzd/Movies/A_a/Clang/proj/collect/999998/999998/test.cpp:24:42: warning: if 語(yǔ)句,太過復(fù)雜,修下吧 ... [-Wcomplex-condition]if (a > 0 || b > 0 || c > 0 || d > 0 || e > 0){ }~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~4.1.3 分析問題
C++ 允許寫連續(xù)的判斷,不帶括號(hào)
實(shí)際上該用例,對(duì)于語(yǔ)義分析來說,有一個(gè)等價(jià)
這樣 AST 的二元操作符判斷,就嵌套了三層
需要回避這種情況
4.1.4 解決
輔助方法
// 約定 // 啥也不是, 0 // && , 1 // || , 2int valOfExpresion(const Expr * BOp){BOp = BOp->IgnoreParenImpCasts();if (const auto * BinaryOp = dyn_cast<BinaryOperator>(BOp)){if (BinaryOp->getOpcode() == BO_LAnd){return 1;}else if (BinaryOp->getOpcode() == BO_LOr){return 2;}}return 0; }修改方法,
增加判斷,如果父二元操作符節(jié)點(diǎn)和子二元操作符節(jié)點(diǎn),相等
本層計(jì)數(shù),忽略
bool DiagnoseIf(const Expr * IfRoot, Sema &S, const Expr * CurrentExpr, int CurrentNestingLevel){// 忽略函數(shù)的 Imp 指針轉(zhuǎn)化CurrentExpr = CurrentExpr->IgnoreParenImpCasts();// dyn_cast 動(dòng)態(tài)轉(zhuǎn)化下,看是不是二元操作符if (const auto * BinaryOp = dyn_cast<BinaryOperator>(CurrentExpr)){// 看這個(gè)二元操作符,是不是 && 或 ||if (BinaryOp->getOpcode() == BO_LAnd || BinaryOp->getOpcode() == BO_LOr){// 對(duì)樹,深度優(yōu)先遍歷,有一個(gè)簡(jiǎn)單的遞歸int val = valOfExpresion(CurrentExpr);int valLhs = valOfExpresion(BinaryOp->getLHS());// levelLhs , 考慮的是,本層的訪問,是要納入計(jì)算,還是要忽略int levelLhs = 1;if (val == valLhs){levelLhs = 0;}int valRhs = valOfExpresion(BinaryOp->getRHS());int levelRhs = 1;if (val == valRhs){levelRhs = 0;}llvm:: dbgs() << "levelRhs: " << levelRhs << " \n" << "levelLhs: " << levelLhs<< " \n\n\n";if (CurrentNestingLevel >= 2){S.Diag(IfRoot->getExprLoc(), diag:: warn_if_condition_too_complex) << IfRoot->getSourceRange();return false;}else if (DiagnoseIf(IfRoot, S, BinaryOp->getLHS(), CurrentNestingLevel + levelLhs)){if (DiagnoseIf(IfRoot, S, BinaryOp->getRHS(), CurrentNestingLevel + levelRhs)){return true;}else{return false;}}else{return false;}}}return true; }4.1.5 錯(cuò)誤示范
把返回前置
這樣嵌套 2 層,這邊就報(bào)錯(cuò)了
判斷為 || 、&&, 再?zèng)Q定返回,
考慮了本層,+ 1 層,3 層嵌套,才報(bào)錯(cuò)
levelLhs 的值, 考慮的是,本層的訪問,是要納入計(jì)算,還是要忽略
levelRhs 的值, 也一樣
bool DiagnoseIf(const Expr * IfRoot, Sema &S, const Expr * CurrentExpr, int CurrentNestingLevel){if (CurrentNestingLevel >= 2){S.Diag(IfRoot->getExprLoc(), diag:: warn_if_condition_too_complex) << IfRoot->getSourceRange();return false;}// 忽略函數(shù)的 Imp 指針轉(zhuǎn)化CurrentExpr = CurrentExpr->IgnoreParenImpCasts();// dyn_cast 動(dòng)態(tài)轉(zhuǎn)化下,看是不是二元操作符if (const auto * BinaryOp = dyn_cast<BinaryOperator>(CurrentExpr)){// 看這個(gè)二元操作符,是不是 && 或 ||if (BinaryOp->getOpcode() == BO_LAnd || BinaryOp->getOpcode() == BO_LOr){// 對(duì)樹,深度優(yōu)先遍歷,有一個(gè)簡(jiǎn)單的遞歸int val = valOfExpresion(CurrentExpr);int valLhs = valOfExpresion(BinaryOp->getLHS());// levelLhs , 考慮的是,本層的訪問,是要納入計(jì)算,還是要忽略int levelLhs = 1;if (val == valLhs){levelLhs = 0;}int valRhs = valOfExpresion(BinaryOp->getRHS());int levelRhs = 1;if (val == valRhs){levelRhs = 0;}if (DiagnoseIf(IfRoot, S, BinaryOp->getLHS(), CurrentNestingLevel + levelLhs)){if (DiagnoseIf(IfRoot, S, BinaryOp->getRHS(), CurrentNestingLevel + levelRhs)){return true;}else{return false;}}else{return false;}}}return true; }錯(cuò)誤示范效果
/Users/jzd/Movies/A_a/Clang/proj/collect/999998/999998/test.cpp:16:26: warning: if 語(yǔ)句,太過復(fù)雜,修下吧 ... [-Wcomplex-condition]if ((a > 0 || b > 0) && c > 0){ }~~~~~~~~~~~~~~~~~^~~~~~~~4.2 , 其他情況
4.2.1, if 語(yǔ)句里面,存在宏的展開
宏的展開,是在預(yù)處理的時(shí)候,
我們處理 AST , 是在語(yǔ)義分析階段,需要規(guī)避
解決,略
4.2.2, C++ 的模版函數(shù),重復(fù)報(bào)錯(cuò)
解決,略
將 clang 添加 Xcode
操作比較簡(jiǎn)單
- 自定義兩個(gè)用戶設(shè)置
CC 和 CXX
- 填入內(nèi)容
CC 的路徑是
/Users/jzd/Movies/A_B/llvm-projectX/build/bin/clang
CXX 的路徑是
/Users/jzd/Movies/A_B/llvm-projectX/build/bin/clang++
- 設(shè)置文件的編譯選項(xiàng)
要這個(gè)警告
-Wcomplex-condition
github repo
總結(jié)
以上是生活随笔為你收集整理的添加编译警告的另一种方式:开发 clang的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【教程3】疯壳·ARM功能手机-整板资源
- 下一篇: 台达ES2与台达温控器ASCII通讯程序