Clang AST Matchers能否基于调用次数过滤?构造函数多fopen检测
如何实现检测多次调用fopen()的构造函数的clang-tidy检查器
好问题!clang-tidy的AST匹配器确实没有直接提供基于调用次数过滤的内置功能,但我们可以通过先匹配范围,再在回调中统计调用次数的方式实现你想要的检查逻辑——找出那些调用fopen()超过一次的构造函数,避免后续调用抛出异常时的资源泄漏问题。
核心思路
- 先用AST匹配器定位所有包含至少一次
fopen()调用的构造函数; - 在检查器的回调函数中,手动统计该构造函数内(包括函数体和初始化列表)的
fopen()调用次数; - 当次数≥2时,触发警告并标注所有
fopen()的位置。
完整实现代码示例
#include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Tooling.h" #include "llvm/Support/CommandLine.h" using namespace clang; using namespace clang::ast_matchers; using namespace clang::tooling; using namespace llvm; namespace { class MultipleFopenCtorChecker : public MatchFinder::MatchCallback { public: void check(const MatchFinder::MatchResult &Result) override { if (const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor")) { size_t fopenCallCount = 0; std::vector<const CallExpr*> fopenCalls; // 1. 统计初始化列表中的fopen调用 for (const auto *Init : Ctor->inits()) { if (const auto *Call = dyn_cast<CallExpr>(Init->getInit())) { if (const auto *FD = dyn_cast<FunctionDecl>(Call->getCalleeDecl())) { if (FD->getName() == "fopen" && FD->isExternC()) { fopenCallCount++; fopenCalls.push_back(Call); } } } } // 2. 统计函数体中的fopen调用 auto bodyMatches = match( callExpr(callee(functionDecl(hasName("fopen"), isExternC()))), *Ctor->getBody(), *Result.Context ); fopenCallCount += bodyMatches.size(); for (const auto &match : bodyMatches) { if (const auto *Call = match.getNodeAs<CallExpr>()) { fopenCalls.push_back(Call); } } // 3. 调用次数≥2时触发警告 if (fopenCallCount >= 2) { diag(Ctor->getLocation(), "constructor contains multiple fopen() calls; if a later call throws, earlier opened files may leak") << DiagnosticIDs::Warning; // 标注每一处fopen的位置 for (const auto *Call : fopenCalls) { diag(Call->getBeginLoc(), "fopen() call here", DiagnosticIDs::Note); } } } } }; class MultipleFopenCtorAction : public ASTFrontendAction { public: MultipleFopenCtorAction() : Finder() { // 匹配包含fopen调用的构造函数(覆盖函数体和初始化列表) Finder.addMatcher( cxxConstructorDecl( anyOf( hasDescendant(callExpr(callee(functionDecl(hasName("fopen"), isExternC())))), hasAnyConstructorInitializer( cxxCtorInitializer( hasInitializer(callExpr(callee(functionDecl(hasName("fopen"), isExternC())))) ) ) ) ).bind("ctor"), &Checker ); } std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef File) override { return Finder.newASTConsumer(); } private: MatchFinder Finder; MultipleFopenCtorChecker Checker; }; } // namespace static llvm::cl::OptionCategory MyToolCategory("multiple-fopen-checker options"); int main(int argc, const char **argv) { auto ExpectedParser = CommonOptionsParser::create(argc, argv, MyToolCategory); if (!ExpectedParser) { llvm::errs() << ExpectedParser.takeError(); return 1; } CommonOptionsParser &OptionsParser = *ExpectedParser; ClangTool Tool(OptionsParser.getCompilations(), OptionsParser.getSourcePathList()); return Tool.run(newFrontendActionFactory<MultipleFopenCtorAction>().get()); }
关键细节说明
- 限定标准库fopen:使用
isExternC()匹配,避免误报用户自定义的同名函数; - 覆盖初始化列表:构造函数的初始化列表中也可能存在
fopen调用,这部分同样需要统计,否则会漏检; - 精准标注位置:不仅报告构造函数,还会标注每一处
fopen的调用位置,方便开发者定位修复。
编译与使用
用clang++编译检查器(需要链接clang相关库):
clang++ -std=c++17 multiple_fopen_checker.cpp -o multiple_fopen_checker \ `llvm-config --cxxflags --ldflags --system-libs --libs core` \ `clang -print-resource-dir`/../lib/libclangASTMatchers.a \ `clang -print-resource-dir`/../lib/libclangTooling.a \ `clang -print-resource-dir`/../lib/libclangAST.a \ `clang -print-resource-dir`/../lib/libclangFrontend.a
运行检查器分析目标代码:
./multiple_fopen_checker your_source.cpp
内容的提问来源于stack exchange,提问作者vrnithinkumar
相关产品推荐
相关产品推荐

