What's the right way to match #includes (or #defines) using Clang's libtooling?
Asked Answered
L

2

9

I'm writing a libtooling refactoring tool. I have a class, let's say Foo, defined in a header called foo.h. I want to see if foo.h is included in a file. Currently, to check if bar.cc includes foo.h, I'm just matching using recordDecl(hasName("Foo")). This works because class Foo { ... }; will exist inside of bar.cc's AST after preprocessing, if bar.cc includes foo.h.

But this doesn't work if, for example, bar.cc includes cat.h which includes foo.h. I want bar.cc to EXPLICITLY include foo.h.

Further, I'd like to be able to match #define macros.

The way I've been writing my tool has made these two goals impossible because the AST I'm matching on has already been preprocessed. Is what I'm trying to do even possible? I dug around the Preprocessor class reference on Clang's Doxygen pages, but I haven't quite found what I'm looking for.

Langur answered 19/11, 2014 at 23:53 Comment(0)
L
14

I figured this out after digging around in Clang's Doxygen and code. I needed to be using the PPCallbacks class along with the Preprocessor class. An example is below. Note, this isn't guaranteed to be a functional code snippet, but it illustrates general usage. For more info, see Clang's PPCallbacks documentation and also the documentation for addPPCallbacks and getPPCallbacks in clang::Preprocessor.

class Find_Includes : public PPCallbacks
{
public:
  bool has_include;

  void InclusionDirective(
    SourceLocation hash_loc,
    const Token &include_token,
    StringRef file_name,
    bool is_angled,
    CharSourceRange filename_range,
    const FileEntry *file,
    StringRef search_path,
    StringRef relative_path,
    const Module *imported)
  {
    // do something with the include
    has_include = true;
  }
};

class Include_Matching_Action : public ASTFrontendAction
{
  bool BeginSourceFileAction(CompilerInstance &ci, StringRef)
  {
    std::unique_ptr<Find_Includes> find_includes_callback(new Find_Includes());

    Preprocessor &pp = ci.getPreprocessor();
    pp.addPPCallbacks(std::move(find_includes_callback));

    return true;
  }

  void EndSourceFileAction()
  {
    CompilerInstance &ci = getCompilerInstance();
    Preprocessor &pp = ci.getPreprocessor();
    Find_Includes *find_includes_callback = static_cast<Find_Includes>(pp.getPPCallbacks());

    // do whatever you want with the callback now
    if (find_includes_callback->has_include)
      std::cout << "Found at least one include" << std::endl;
  }
};
Langur answered 22/11, 2014 at 8:23 Comment(2)
This did not work for me, Find_Includes::InclusionDirective() was never executed. But if I inherit from clang::PreprocessOnlyAction instead of ASTFrontendAction then it works. See xaizek.github.io/2015-04-23/detecting-wrong-first-includePlacet
@GerardoHernandez: I just tried this ASTFrontendAction (clang 11) and it works as expected. Maybe a version thing.Deledda
T
1

I successfully implemented it. I do not think EndSourceFileAction() method should contain anything related to Find_Includes.

void InclusionDirective( SourceLocation hash_loc, const Token &include_token, StringRef file_name, bool is_angled, CharSourceRange filename_range,
                            const FileEntry *file, StringRef search_path, StringRef relative_path, const Module *imported)
              {
                  // Add your code here.
              }
bool BeginSourceFileAction(CompilerInstance &CI) 
{     std::unique_ptr<Find_Includes> find_includes_callback(new Find_Includes(TheRewriter));
      Preprocessor &pp = CI.getPreprocessor();
      pp.addPPCallbacks(std::move(find_includes_callback));
      return true;
}
Tovatovar answered 6/1, 2018 at 3:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.