Question

Parse clang AST from `compile_commands.json` ignoring PCH

I'm trying to parse an AST of the whole project using a compilation database (compile_commands.json), but it contains pch files and commands related to generate those, resulting in errors during parsing: error: PCH file uses a newer PCH format that cannot be read 1 error generated.

The project has been configured (via CMake) and compiled using a different compiler, and the whole reason to make a tool instead of a plugin is not to be bound to a particular compiler. I might try an ad hoc solution to strip compile_commands.json from pch-related stuff, but I doubt it will work properly and worth the hustle.

The initialization is basically like this:

  auto options_parser =
      CommonOptionsParser::create(argc, argv, option_category);
  if (!options_parser) {
    llvm::errs() << options_parser.takeError();
    return -1;
  }

  ClangTool tool(options_parser->getCompilations(),
      options_parser->getCompilations().getAllFiles());

Is possible to configure the tool not to handle pch-related stuff?

 2  29  2
1 Jan 1970

Solution

 1

I found the exact issue that has been solved for clangd, the corresponding changes can be found here.
It boils down to setting certain fields for clang::CompilerInvocation - the class that contains all the state that is used to configure a CompilerInstance.

An instance of CompilerInvocation is accessible via implementation of virtual bool clang::tooling::ToolAction::runInvocation. The pch-related options are accessible via CompilerInvocation::getPreprocessorOpts(). This code additionally disables reporting warnings

struct custom_action : public clang::tooling::ToolAction {
  bool
  runInvocation(std::shared_ptr<clang::CompilerInvocation> inv,
                clang::FileManager *files,
                std::shared_ptr<clang::PCHContainerOperations> pch_cont_ops,
                clang::DiagnosticConsumer *diag_cons) override {

    // createInvocationFromCommandLine sets DisableFree.
    inv->getFrontendOpts().DisableFree = false;
    inv->getLangOpts()->CommentOpts.ParseAllComments = true;
    inv->getLangOpts()->RetainCommentsFromSystemHeaders = true;

    [](auto &diag) {
      diag.Warnings.clear();
      diag.UndefPrefixes.clear();
      diag.Remarks.clear();
      diag.VerifyPrefixes.clear();
    }(inv->getDiagnosticOpts());

    [](auto &output) {
      // Disable any dependency outputting, we don't want to generate files or
      // write to stdout/stderr.
      output.ShowIncludesDest = clang::ShowIncludesDestination::None;
      output.OutputFile.clear();
      output.HeaderIncludeOutputFile.clear();
      output.DOTOutputFile.clear();
      output.ModuleDependencyOutputDir.clear();
    }(inv->getDependencyOutputOpts());

    [](auto &preproc) {
      // Disable any pch generation/usage operations. Since serialized preamble
      // format is unstable, using an incompatible one might result in
      // unexpected behaviours, including crashes.
      preproc.ImplicitPCHInclude.clear();
      preproc.PrecompiledPreambleBytes = {0, false};
      preproc.PCHThroughHeader.clear();
      preproc.PCHWithHdrStop = false;
      preproc.PCHWithHdrStopCreate = false;
    }(inv->getPreprocessorOpts());

    // your code here
    
    return true;
  }
};

One can configure all the needed adaptors and than call ClangTool::run(ToolAction*).

2024-07-21
Sergey Kolesnik