在代碼覆蓋率工具開發中,需解析 C++ 源文件提取所有函數定義(用於插桩)。初期採用「括號匹配 + 關鍵字識別」的手寫解析器,隨項目深入暴露嚴重問題:
掃描字符,維護棧匹配 `{}` `()`,遇到 `)` 後跟 `{` 判定為函數定義。
**失敗案例**:宏導致括號數錯誤、函數指釖、lambda、類內定義等。
發現其使用正則表達式配置文件定義語言解析規則。嘗試移植其 C++ 正則規則。
**優點**:快速獲得基礎支援。
**缺點**:正則本質上無法處理遞歸結構(模板嵌套、括號匹配),異常代碼仍會漏報/誤報;且規則難以擴展。
**正解**:使用 Clang 前端庫直接解析 AST,提取 `FunctionDecl` 節點。
**優勢**:
// 基於 RecursiveASTVisitor
class FunctionFinder : public RecursiveASTVisitor {
public:
explicit FunctionFinder(ASTContext* ctx) : ctx_(ctx) {}
bool VisitFunctionDecl(FunctionDecl* f) {
if (f->isThisDeclarationADefinition()) {
SourceLocation loc = f->getLocation();
auto spelling = f->getQualifiedNameAsString();
auto file = ctx_->getSourceManager().getFilename(loc);
unsigned line = ctx_->getSourceManager().getSpellingLineNumber(loc);
// 輸出或存儲:文件、行號、函數全名
printf("%s:%d: %s\n", file.str().c_str(), line, spelling.c_str());
}
return true;
}
private:
ASTContext* ctx_;
};
// FrontendAction 創建 ASTConsumer 運行訪問者
// 需配合 CommonOptionsParser 讀取 compile_commands.json
若僅需**運行時**在函數內獲取自身名稱(如日誌、插桩),C99 標準宏 `__func__`(C++11 `__FUNCTION__`)極其簡單:
#define LOG() printf("[%s:%d] %s\n", __FILE__, __LINE__, __func__)
void foo() { LOG(); ... }
**局限**:無法靜態獲取全項目函數列表,只能在函數體內使用。
| 需求 | 推薦方案 | 複雜度 |
|---|---|---|
| 靜態分析全項目函數列表(含簽名、位置) | Clang LibTooling (LibAST) | 中(需編譯環境) |
| 輕量級語法高亮/大綱視圖(IDE 插件) | Tree-sitter / Language Server Protocol | 低 |
| 運行時函數名獲取(日誌/插桩) | `__func__` / `__FUNCTION__` 宏 | 極低 |
| 簡單腳本提取(不追求完美) | Tree-sitter 查詢 / 正則 + 簡單狀態機 | 低 |
核心教訓:C++ 語法過於複雜,**不要嘗試手寫解析器**。利用成熟的編譯器前端庫是工業級解決方案的唯一正確道路。