聊聊PreStmt<CallExpr>,PreCall,PostCall,PostStmt<CallExpr>之間的關係
Clang Static Analyzer在遍歷CFG進行檢測的過程中,碰到一個函數調用時,執行三步重要的分析,即
1、函數調用前~
2、函數調用中~
3、函數調用後~
從名字就可以看出PreStmt<CallExpr>和PreCall執行的是步驟1,PostCall和PostStmt<CallExpr>執行的是步驟3。在Clang中對應的方法為ExprEngine::VisitCallExpr,該方法簡化後的代碼邏輯如下所示:
void ExprEngine::VisitCallExpr() {n runCheckersForPreStmt();n evalCall();n runCheckersForPostStmt();n}nnvoid evalCall() {n runCheckersForPreCall();n runCheckersForEvalCall();n runCheckersForPostCall();n}n
從上面的簡化代碼中可以看出,在checker中四個與函數調用相關的回調介面執行順序如下所示:
PreStmt<CallExpr> : void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
PreCall : void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
PostCall: void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
PostStmt<CallExpr> : void checkPostStmt(const CallExpr *CE, CheckerContext &C) const;
在PreCall和PostCall之間,夾雜了一個evalCall的方法,模擬函數執行,對該函數進行建模。建模分為三種情況:
1、某個checker繼承Checker<eval::Call>,在evalCall()方法中對該函數進行建模,並返回true,則CSA採用checker提供的建模方式。
2、如果採用inline模式分析,則嘗試找到函數的函數體,進行inline分析。
3、如果無法inline分析,則進行conservative analyze。
CSA的檢測框架在每一個遍歷到的ExplodedNode點,都會輪詢一遍所有的checkers,對實現了該介面的checkers都會觸發回調,如果某個checkers介面修改了ProgramState,則會影響後續的檢測分析。這就導致了對於看起來功能相同的介面(例如PreStmt<CallExpr> 和 PreCall),如果使用不恰當,可能會導致檢測不到想要的結果。
編碼測試:
checker部分代碼:
static void dumpSValForExpr(CheckerContext &C, const Expr *arg) {n SVal SVal1 = C.getSVal(arg);n SVal1.dump();n llvm::errs() << "n";nn if (SVal1.getAs<Loc>()) {ntconst MemRegion *MR = SVal1.getAsRegion();ntif (!MR) {nt return;nt}nntSVal sval = C.getState()->getSVal(MR);ntsval.dump();ntllvm::errs() << "n";n }n}nnvoid TestingChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const {n llvm::errs() << "Call function: " << CE->getDirectCallee()->getNameAsString() << "n";n llvm::errs() << "PreStmt<CallExpr>:n";n const Expr *arg1 = CE->getArg(0);nn if (!arg1) {n return;n }nn dumpSValForExpr(C, arg1);n}nnvoid TestingChecker::checkPreCall(const CallEvent &Call,n CheckerContext &C) const {n llvm::errs() << "PreCall:n";n const Expr *arg1 = Call.getArgExpr(0);nn if (!arg1) {n return;n }nn dumpSValForExpr(C, arg1);n}nnvoid TestingChecker::checkPostCall(const CallEvent &Call,n CheckerContext &C) const {n llvm::errs() << "PostCall:n";n const Expr *arg1 = Call.getArgExpr(0);n if (!arg1) {n return;n }nn dumpSValForExpr(C, arg1);n}nnvoid TestingChecker::checkPostStmt(const CallExpr *CE,n CheckerContext &C) const {n llvm::errs() << "PostStmt<CallExpr>:n";nn const Expr *arg1 = CE->getArg(0);nn if (!arg1) {n return;n }nn dumpSValForExpr(C, arg1);n}n
測試樣例:
int noChangeValue(int a){ntreturn a;n}nnvoid changeValue(int &a){nta++;n}nnvoid test(){ntint a = 10;ntint b = 20;nt++a;ntnoChangeValue(a);tntchangeValue(a);n}n
結果:
Call function: noChangeValuenPreStmt<CallExpr>:n11 S32bnPreCall:n11 S32bnPostCall:n11 S32bnPostStmt<CallExpr>:n11 S32bnnnCall function: changeValuenPreStmt<CallExpr>:n&an11 S32bnPreCall:n&an11 S32bnPostCall:n&an12 S32bnPostStmt<CallExpr>:n&an12 S32bn
首先,從結果中可以看到分析的順序依次是PreStmt<CallExpr>, PreCall, PostCall,PostStmt<CallExpr>;
其次,對於noChangeValue(a),由於函數內部並沒有改變a的值,所以四個介面中a的SVal都是一樣的;對於changeValue(a),在函數內部對a的值做了修改,所以在PreStmt<CallExpr>和PreCall中,獲取到a的值依然是11,而在PostCall和PostStmt<CallExpr>中,a的值變為了12,因為在這中間,對changeValue()進行了建模操作。
未完待續~
推薦閱讀:
※LLVM國內的開發者需要Social一下嗎?
※編譯時能否關閉clang的所有優化?我試過-O0,但是編譯成彙編之後還是自動進行了一些優化?
※LLVM 相比與其他 Compiler Infrastructure 有什麼優勢?