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

如何在大型C++代码库中查找继承或包含QWidget的类?LLVM方案咨询

用LLVM/LibTooling检测C++代码中QWidget相关类的实现方案

要扫描大型C++代码库中继承QWidget或包含QWidget类型成员的类,最可靠的方式是基于Clang的AST(抽象语法树)遍历实现——LLVM生态中的Clang-Tidy或LibTooling都能满足需求,以下是具体落地步骤:

方案一:基于Clang-Tidy实现可集成的检查器

Clang-Tidy的优势是能直接集成到现有构建流程(比如CMake、Ninja),适合长期维护的代码库。

1. 编写自定义Clang-Tidy Check

新建一个C++文件(比如QWidgetUsageCheck.cpp),实现继承自ClangTidyCheck的检查逻辑:

#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Tooling/Tooling.h"
#include "clang/Tidy/ClangTidy.h"
#include "clang/Tidy/ClangTidyCheck.h"
#include "clang/Tidy/ClangTidyModule.h"
#include "clang/Tidy/ClangTidyModuleRegistry.h"

using namespace clang;
using namespace clang::ast_matchers;
using namespace clang::tidy;

class QWidgetUsageCheck : public ClangTidyCheck {
public:
  QWidgetUsageCheck(StringRef Name, ClangTidyContext *Context)
      : ClangTidyCheck(Name, Context) {}

  void registerMatchers(MatchFinder *Finder) override {
    // 匹配所有直接/间接继承QWidget的类(注意调整命名空间为代码中实际使用的)
    Finder->addMatcher(cxxRecordDecl(isDerivedFrom("::QtWidgets::QWidget")).bind("derived_class"), this);
    
    // 匹配包含QWidget或其派生类成员的类
    Finder->addMatcher(
        cxxRecordDecl(has(fieldDecl(hasType(qualType(hasCanonicalType(
            recordType(hasDeclaration(cxxRecordDecl(isDerivedFrom("::QtWidgets::QWidget"))))))))).bind("class_with_member"),
        this);
  }

  void check(const MatchFinder::MatchResult &Result) override {
    if (const auto *DerivedClass = Result.Nodes.getNodeAs<CXXRecordDecl>("derived_class")) {
      diag(DerivedClass->getLocation(), "类 %0 继承自 QWidget")
          << DerivedClass->getName();
    }

    if (const auto *ClassWithMember = Result.Nodes.getNodeAs<CXXRecordDecl>("class_with_member")) {
      diag(ClassWithMember->getLocation(), "类 %0 包含 QWidget 类型成员")
          << ClassWithMember->getName();
    }
  }
};

class QWidgetUsageModule : public ClangTidyModule {
public:
  void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
    CheckFactories.registerCheck<QWidgetUsageCheck>("misc-qwidget-usage-check");
  }
};

static ClangTidyModuleRegistry::Add<QWidgetUsageModule>
    X("qwidget-usage-module", "检测继承QWidget或包含QWidget成员的类");

extern "C" LLVM_ATTRIBUTE_WEAK ::clang::tidy::ClangTidyModule *
clangTidyModuleFactory() {
  return new QWidgetUsageModule();
}

extern "C" LLVM_ATTRIBUTE_WEAK void clangTidyModuleRegistryInitialize() {}

2. 编译自定义Check

用CMake配置编译(需要提前安装LLVM/Clang开发库):

cmake_minimum_required(VERSION 3.13)
project(QWidgetUsageCheck)

find_package(LLVM REQUIRED CONFIG)
find_package(Clang REQUIRED CONFIG)

include_directories(${LLVM_INCLUDE_DIRS} ${CLANG_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})

add_library(QWidgetUsageCheck MODULE QWidgetUsageCheck.cpp)

target_link_libraries(QWidgetUsageCheck
  PRIVATE
  clangAST
  clangASTMatchers
  clangTidy
  clangTooling
  LLVMSupport
)

set_target_properties(QWidgetUsageCheck PROPERTIES
  CXX_STANDARD 17
  PREFIX ""
)

编译后会生成一个动态库(比如QWidgetUsageCheck.so)。

3. 运行检查

指定Clang-Tidy加载自定义Check,并扫描代码:

clang-tidy -checks='misc-qwidget-usage-check' src/**/*.cpp -- -I/path/to/qt/includes -std=c++17

如果代码库有compile_commands.json(可通过CMake的CMAKE_EXPORT_COMPILE_COMMANDS=ON生成),可以省略手动指定的编译参数:

clang-tidy -checks='misc-qwidget-usage-check' -p build src/**/*.cpp

方案二:基于LibTooling实现独立扫描工具

如果只是一次性扫描代码库,不需要集成到构建流程,用LibTooling写独立工具更轻便。

1. 编写LibTooling工具代码

新建QWidgetScanner.cpp:

#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.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;

static cl::OptionCategory ToolCategory("QWidget Scanner Options");

class QWidgetDerivedHandler : public MatchFinder::MatchCallback {
public:
  void run(const MatchFinder::MatchResult &Result) override {
    if (const auto *Class = Result.Nodes.getNodeAs<CXXRecordDecl>("derived_class")) {
      llvm::outs() << "继承QWidget的类: " << Class->getNameAsString() 
                   << " (位于 " << Class->getBeginLoc().printToString(Result.SourceManager) << ")\n";
    }
  }
};

class QWidgetMemberHandler : public MatchFinder::MatchCallback {
public:
  void run(const MatchFinder::MatchResult &Result) override {
    if (const auto *Class = Result.Nodes.getNodeAs<CXXRecordDecl>("class_with_member")) {
      llvm::outs() << "包含QWidget成员的类: " << Class->getNameAsString() 
                   << " (位于 " << Class->getBeginLoc().printToString(Result.SourceManager) << ")\n";
    }
  }
};

int main(int argc, const char **argv) {
  CommonOptionsParser OptionsParser(argc, argv, ToolCategory);
  ClangTool Tool(OptionsParser.getCompilations(), OptionsParser.getSourcePathList());

  MatchFinder Finder;
  QWidgetDerivedHandler DerivedHandler;
  QWidgetMemberHandler MemberHandler;

  Finder.addMatcher(cxxRecordDecl(isDerivedFrom("::QtWidgets::QWidget")).bind("derived_class"), &DerivedHandler);
  Finder.addMatcher(
      cxxRecordDecl(has(fieldDecl(hasType(qualType(hasCanonicalType(
          recordType(hasDeclaration(cxxRecordDecl(isDerivedFrom("::QtWidgets::QWidget"))))))))).bind("class_with_member"),
      &MemberHandler);

  return Tool.run(newFrontendActionFactory(&Finder).get());
}

2. 编译工具

CMake配置如下:

cmake_minimum_required(VERSION 3.13)
project(QWidgetScanner)

find_package(LLVM REQUIRED CONFIG)
find_package(Clang REQUIRED CONFIG)

include_directories(${LLVM_INCLUDE_DIRS} ${CLANG_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})

add_executable(QWidgetScanner QWidgetScanner.cpp)

target_link_libraries(QWidgetScanner
  PRIVATE
  clangAST
  clangASTMatchers
  clangTooling
  LLVMOption
  LLVMSupport
)

set_target_properties(QWidgetScanner PROPERTIES
  CXX_STANDARD 17
)

3. 运行扫描

./QWidgetScanner src/**/*.cpp -- -I/path/to/qt/includes -std=c++17

同样,有compile_commands.json时可以简化为:

./QWidgetScanner -p build src/**/*.cpp

关键注意事项

  • 命名空间适配:如果代码中QWidget的使用没有带QtWidgets命名空间(比如用了using namespace QtWidgets;),需要调整匹配器中的字符串为"QWidget",但优先推荐使用完全限定名避免误匹配。
  • 间接继承与别名处理:isDerivedFrom会自动处理间接继承;hasCanonicalType能识别typedef、using定义的别名类型,确保不会漏掉用别名声明的QWidget成员。
  • 大型代码库优化:利用compile_commands.json自动获取编译参数,避免手动指定大量头文件路径;可以结合并行扫描(Clang-Tooling支持-j参数)提升速度。

内容的提问来源于stack exchange,提问作者JDGross

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 11:30:52