You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Clang AST Matchers能否基于调用次数过滤?构造函数多fopen检测

如何实现检测多次调用fopen()的构造函数的clang-tidy检查器

好问题!clang-tidy的AST匹配器确实没有直接提供基于调用次数过滤的内置功能,但我们可以通过先匹配范围,再在回调中统计调用次数的方式实现你想要的检查逻辑——找出那些调用fopen()超过一次的构造函数,避免后续调用抛出异常时的资源泄漏问题。

核心思路

  1. 先用AST匹配器定位所有包含至少一次fopen()调用的构造函数;
  2. 在检查器的回调函数中,手动统计该构造函数内(包括函数体和初始化列表)的fopen()调用次数;
  3. 当次数≥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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 19:23:09