5

使用 Clang LibTooling 的 Minimul 源代码,这是一种非常常见的方式:

#include "pch.h"

#include "clang/Frontend/FrontendActions.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Support/CommandLine.h"
#include "clang/Driver/Options.h"
#include "clang/AST/AST.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/ASTConsumers.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include <iostream>

using namespace std;
using namespace clang;
using namespace clang::driver;
using namespace clang::tooling;
using namespace llvm;

class ExampleVisitor : public RecursiveASTVisitor<ExampleVisitor> {
public:
    explicit ExampleVisitor(CompilerInstance *CI) {}
};

class ExampleASTConsumer : public ASTConsumer {
private:
    CompilerInstance *CI;
public: 
    explicit ExampleASTConsumer(CompilerInstance *CI) : CI(CI) {}   
    virtual void HandleTranslationUnit(ASTContext &Context) {
        ExampleVisitor(CI).TraverseDecl(Context.getTranslationUnitDecl());
    }
};

class ExampleFrontendAction : public ASTFrontendAction {
public:
    virtual std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef file) {
        return  std::unique_ptr<ASTConsumer>(new ExampleASTConsumer(&CI)); 
    }
};

void run(int argc, const char **argv, llvm::cl::OptionCategory& tc) {
    CommonOptionsParser op(argc, argv, tc);
    ClangTool Tool(op.getCompilations(), op.getSourcePathList());
    std::cout <<"getSourcePathList.size="<< op.getSourcePathList().size()<<"\n";
    int result = Tool.run(newFrontendActionFactory<ExampleFrontendAction>().get()); 
}

int main(int argc, const char **argv) {
    llvm::cl::OptionCategory tc1("c1");
    llvm::cl::OptionCategory tc2("c2");
    llvm::cl::OptionCategory tc3("c3");
    run(argc, argv,tc1);
    run(argc, argv,tc2);
    run(argc, argv,tc3);
    std::cin.get();
    return 0;
}

调试应用程序的参数是:

"the_only_source_file_to_scan.cpp" --

这很好。

输出是(来自 main() 上面的方法“run”):

getSourcePathList.size=1
getSourcePathList.size=2
getSourcePathList.size=3

问题是 main() 使用相同的上述参数调用 run() 3 次,其中仅包含要扫描的 1 个源文件,但每次存储在 CommonOptionsParser 中的源扫描列表的大小都会增加一(每个项目在该列表是来自 argv 的相同文件输入),它似乎只是每次都将要扫描的源文件附加到列表中。

以上所有内容都保存在每次运行中新创建的临时变量中,那么 LibTooling 如何以及为什么保持上次运行的状态以及如何“重置”这些状态?

4

3 回答 3

1

使用FixedCompilationDatabase可以规避这个问题,它可以在一个进程中运行多个clangTool

于 2021-09-01T02:54:13.937 回答
0

以上代码使用了CommonOptionsParser,其代码在

clang\lib\Tooling\CommonOptionsParser.cpp

在 CommonOptionsParser::init 方法中有:

  static cl::list<std::string> SourcePaths(...);

每次调用都会将其源添加到此静态变量中。因此,正是这个局部静态变量导致了先前调用的内存。在每次调用中,这个局部静态变量都被 cl::ParseCommandLineOptions 以某种未知的方式修改,因为它根本没有传递给 cl::ParseCommandLineOptions。在 SourcePaths 被修改(即当前调用的源被添加到可能已经包含先前调用的源的 SourcePaths)之后,它最终被复制到一个实例变量中。

顺便说一句,@AbaoZhang 的线索帮助我找到了位置,确实 CommonOptionsParser::init 内部使用 FixedCompilationDatabase 但不适用于上述情况。

于 2021-09-04T08:35:20.227 回答
0
static cl::list<std::string> SourcePaths(...);

@jw_ 是的,这段代码有问题。

你可以解决这个问题。

for (auto iter = llvm::cl::AllSubCommands->OptionsMap.begin(); iter != llvm::cl::AllSubCommands->OptionsMap.end(); iter++)
{
    iter->getValue()->setDefault();
}
for (auto iter = llvm::cl::AllSubCommands->PositionalOpts.begin(); iter != llvm::cl::AllSubCommands->PositionalOpts.end(); iter++)
{
    (*iter)->setDefault();
}
for (auto iter = llvm::cl::AllSubCommands->SinkOpts.begin(); iter != llvm::cl::AllSubCommands->SinkOpts.end(); iter++)
{
    (*iter)->setDefault();
}
于 2021-11-13T14:16:01.130 回答