如何在大型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
相关产品推荐
相关产品推荐

