dimgel
dimgel

Reputation: 73

How can I get main source file path from MatchCallback::onStartOfTranslationUnit()?

For now I can get it only in MatchCallback::run(const MatchResult& r):

r.SourceManager.getFileEntryForID(r.SourceManager.getMainFileID())->getName().str()

and cache for future use. But I might need it in my MatchCallback subclass even if no matches were found in current file. It's weird that onStartOfTranslationUnit() does not give a clue which translation unit is started.

Upvotes: 0

Views: 246

Answers (1)

Scott McPeak
Scott McPeak

Reputation: 12863

Not easily

Unfortunately, there isn't an easy way to get the main file from the onStartOfTranslationUnit method of MatchCallback. The call site is in clang/lib/ASTMatchers/ASTMatchFinder.cpp:

    for (MatchCallback *MC : Matchers->AllCallbacks) {
      if (EnableCheckProfiling)
        Timer.setBucket(&TimeByBucket[MC->getID()]);
      MC->onStartOfTranslationUnit();        // <-----
    }

The caller has access to an ASTContext in its ActiveASTContext member, from which we could easily get the main source file, but it chooses not to pass it.

Workaround: Intercepting at a higher level

Assuming you're using MatchCallback in a way similar to the tutorials, somewhere you are making a MatchFinder object and using addMatcher to arrange for it to call your callback methods, something like:

  MatchFinder Finder;
  Finder.addMatcher(..., &myCallbackObject);
  return Tool.run(newFrontendActionFactory(&Finder).get());

In this construction, MatchFinder::newASTConsumer will be called, which makes an intermediate object of type MatchASTConsumer (which is private to the implementation), which invokes the MatchCallback methods.

To insert additional processing into this chain, we need to:

  • Define MyMatchFinder whose newASTConsumer creates a MyMatchASTConsumer.

  • Define MyMatchASTConsumer whose HandleTranslationUnit method calls MyMatchFinder::matchAST.

  • Define MyMatchFinder::matchAST, which calls a new onTU callback that accepts an ASTContext. This requires that MyMatchFinder carry its own pointer to the callbacks object.

  • Define and use the onTU callback instead of the old onStartOfTranslationUnit.

  • Create a MyMatchFinder and pass that to Tool instead.

Complete example

The following files provide a complete example demonstrating the approach.

ast-match-finder.cc:

// ast-match-finder.cc
// Demonstrate the MatchFinder::MatchCallback mechanism, and add
// ability to get the main source file upon notification of beginning
// a new translation unit.
//
// Based on:
//   * https://clang.llvm.org/docs/LibASTMatchersTutorial.html
//   * https://clang.llvm.org/doxygen/ASTMatchFinder_8h_source.html
//     (which has many compilation errors that needed fixing)

#include "clang/AST/Decl.h"                      // CXXRecordDecl
#include "clang/ASTMatchers/ASTMatchFinder.h"    // MatchFinder
#include "clang/Frontend/FrontendActions.h"      // newFrontendActionFactory
#include "clang/Tooling/CommonOptionsParser.h"   // CommonOptionsParser
#include "clang/Tooling/CompilationDatabase.h"   // CompilationDatabase
#include "clang/Tooling/Tooling.h"               // ClangTool

#include <memory>                                // unique_ptr
#include <string>                                // string

using clang::ast_matchers::MatchFinder;
using clang::ast_matchers::cxxRecordDecl;
using clang::ast_matchers::hasDefinition;
using clang::ast_matchers::hasName;

using clang::ASTConsumer;
using clang::ASTContext;
using clang::CXXRecordDecl;
using clang::SourceManager;

using clang::tooling::ClangTool;
using clang::tooling::CommonOptionsParser;
using clang::tooling::CompilationDatabase;
using clang::tooling::newFrontendActionFactory;

using std::make_unique;
using std::string;
using std::unique_ptr;


class HandleMatch : public MatchFinder::MatchCallback {
public:
  // This is the ordinary 'run' callback.
  virtual void run(const MatchFinder::MatchResult &Result) override {
    const CXXRecordDecl *Class =
      Result.Nodes.getNodeAs<CXXRecordDecl>("id");
    llvm::outs()
      << "found it: "
      << Class->getLocation().printToString(*(Result.SourceManager))
      << "\n";
  }

  // This is the normal callback for the start of a TU, but there is no
  // way to get the main file name from here, unfortunately, which makes
  // it less useful than it could be.  I'm including it just to show
  // that it is still being called.
  virtual void onStartOfTranslationUnit() override {
    llvm::outs() << "onStartOfTranslationUnit\n";
  }

  // We define a new callback that gets the required info, and then have
  // to call it ourselves.
  void onTU(ASTContext &context) {
    SourceManager &sm = context.getSourceManager();
    llvm::outs()
      << "onTU: "
      << sm.getFileEntryForID(sm.getMainFileID())->getName()
      << "\n";
  }
};


// This class substitutes for clang::ast_matchers::MatchFinder by
// arranging to call 'onTU' at the right time.
class MyMatchFinder : public MatchFinder {
public:      // data
  HandleMatch *m_handleMatch;

public:      // methods
  explicit MyMatchFinder(HandleMatch *handleMatch)
    : m_handleMatch(handleMatch)
  {}

  // This is what 'newFrontendActionFactory' calls.  It is effectively
  // the entry point to the FE action system.
  unique_ptr<ASTConsumer> newASTConsumer();

  // This will be invoked by 'MyMatchASTConsumer'/
  void matchAST(ASTContext &context)
  {
    m_handleMatch->onTU(context);

    // This does the normal MatchFinder stuff, including calling
    // 'onStartOfTranslationUnit'.
    MatchFinder::matchAST(context);
  }
};


// This class is a substitute for the 'MatchASTConsumer' class defined
// in ASTMatchFinder.cpp, inside the implementation of 'MatchFinder'.
class MyMatchASTConsumer : public ASTConsumer {
public:      // data
  MyMatchFinder *m_myFinder;

public:      // methods
  MyMatchASTConsumer(MyMatchFinder *myFinder)
    : m_myFinder(myFinder)
  {}

private:     // methods
  // This is the ASTConsumer callback we need to override.
  void HandleTranslationUnit(ASTContext &Context) override {
    // Call MyMatchFinder::matchAST instead of MatchFinder::matchAST.
    m_myFinder->matchAST(Context);
  }
};


// This has to be defined out of line because it needs the definition
// of 'MyMatchASTConsumer'.
unique_ptr<ASTConsumer> MyMatchFinder::newASTConsumer()
{
  return make_unique<MyMatchASTConsumer>(this);
}


static llvm::cl::OptionCategory MyToolCategory("my-tool options");

int main(int argc, char const **argv)
{
  // This part is boilerplate.
  auto ExpectedParser = CommonOptionsParser::create(argc, argv, MyToolCategory);
  if (!ExpectedParser) {
    llvm::errs() << ExpectedParser.takeError();
    return 2;
  }
  CommonOptionsParser& OptionsParser = ExpectedParser.get();
  ClangTool Tool(OptionsParser.getCompilations(),
                 OptionsParser.getSourcePathList());

  // The handler for matches, augmented to received 'onTU' calls.
  HandleMatch handleMatch;

  // Use the customized 'MyMatchFinder' so we can get the 'onTU'
  // callback.
  MyMatchFinder finder(&handleMatch);

  // The rest of this is just some placeholder match expression, plus
  // more boilerplate for running the Tool.
  finder.addMatcher(cxxRecordDecl(hasName("AClass"),
                                  hasDefinition()).bind("id"),
                    &handleMatch);
  return Tool.run(newFrontendActionFactory(&finder).get());
}


// EOF

Makefile:

# Makefile

# Default target.
all:
.PHONY: all


# ---- Configuration ----
# Installation directory from a binary distribution.
# Has five subdirectories: bin include lib libexec share.
# Downloaded from: https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.0/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz
CLANG_LLVM_INSTALL_DIR = $(HOME)/opt/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04

# ---- llvm-config query results ----
# Program to query the various LLVM configuration options.
LLVM_CONFIG := $(CLANG_LLVM_INSTALL_DIR)/bin/llvm-config

# C++ compiler options to ensure ABI compatibility.
LLVM_CXXFLAGS := $(shell $(LLVM_CONFIG) --cxxflags)

# Directory containing the clang library files, both static and dynamic.
LLVM_LIBDIR := $(shell $(LLVM_CONFIG) --libdir)

# Other flags needed for linking, whether statically or dynamically.
LLVM_LDFLAGS_AND_SYSTEM_LIBS := $(shell $(LLVM_CONFIG) --ldflags --system-libs)


# ---- Compiler options ----
# C++ compiler.
#CXX = g++
CXX = $(CLANG_LLVM_INSTALL_DIR)/bin/clang++

# Compiler options, including preprocessor options.
CXXFLAGS =
CXXFLAGS += -Wall
CXXFLAGS += -Werror

# Silence a warning about a multi-line comment in DeclOpenMP.h.
CXXFLAGS += -Wno-comment

# Get llvm compilation flags.
CXXFLAGS += $(LLVM_CXXFLAGS)

# Tell the source code where the clang installation directory is.
CXXFLAGS += -DCLANG_LLVM_INSTALL_DIR='"$(CLANG_LLVM_INSTALL_DIR)"'

# Linker options.
LDFLAGS =

# Pull in clang+llvm via libclang-cpp.so, which has everything, but is
# only available as a dynamic library.
LDFLAGS += -lclang-cpp

# Needed for llvm::InitLLVM, which is missing from libclang-cpp.so.
LDFLAGS += -lLLVMSupport

# Arrange for the compiled binary to search the libdir for that library.
# Otherwise, one can set the LD_LIBRARY_PATH envvar before running it.
# Note: the -rpath switch does not work on Windows.
LDFLAGS += -Wl,-rpath=$(LLVM_LIBDIR)

# Get the needed -L search path, plus things like -ldl.
LDFLAGS += $(LLVM_LDFLAGS_AND_SYSTEM_LIBS)


# ---- Recipes ----
# Compile a C++ source file.
%.o: %.cpp
    $(CXX) -c -o $@ $(CXXFLAGS) $<

# Executable.
all: ast-match-finder.exe
ast-match-finder.exe: ast-match-finder.o
    $(CXX) -g -Wall -o $@ $^ $(LDFLAGS)

# Quick test.
.PHONY: run
run: ast-match-finder.exe
    ./ast-match-finder.exe test.cc test2.cc --

.PHONY: clean
clean:
    $(RM) *.o *.exe


# EOF

test.cc:

// test.cc
// Test input for ast-match-finder.exe.

class AClass {};

// EOF

test2.cc:

// test2.cc
// Test input for ast-match-finder.exe.



class AClass {};

// EOF

Example run:

$ make run
./ast-match-finder.exe test.cc test2.cc --
onTU: /home/scott/wrk/learn/clang/ast-match-finder/test.cc
onStartOfTranslationUnit
found it: /home/scott/wrk/learn/clang/ast-match-finder/test.cc:4:7
onTU: /home/scott/wrk/learn/clang/ast-match-finder/test2.cc
onStartOfTranslationUnit
found it: /home/scott/wrk/learn/clang/ast-match-finder/test2.cc:6:7

Observe that we see both TUs, each with a match on a distinct line.

Upvotes: 0

Related Questions