6

I am working on an AST transformer using Clang. It is supposed to take a file-name as input, perform some transformations to the code in that file, and return the transformed code. It is based on a helpful example from Eli Bendersky.

Here is the code:

std::string transform(std::string fileName) {

  // CompilerInstance will hold the instance of the Clang compiler for us,
  // managing the various objects needed to run the compiler.
  CompilerInstance compilerInstance;

  compilerInstance.createDiagnostics();

  auto& langOpts = compilerInstance.getLangOpts();
  langOpts.CPlusPlus = true;

  // Initialize target info with the default triple for our platform.
  auto TO = std::make_shared<TargetOptions>();
  TO->Triple = llvm::sys::getDefaultTargetTriple();
  TargetInfo* targetInfo =
      TargetInfo::CreateTargetInfo(compilerInstance.getDiagnostics(), TO);
  compilerInstance.setTarget(targetInfo);

  compilerInstance.createFileManager();
  auto& fileManager = compilerInstance.getFileManager();

  compilerInstance.createSourceManager(fileManager);
  auto& sourceManager = compilerInstance.getSourceManager();

  compilerInstance.createPreprocessor(TU_Module);
  compilerInstance.createASTContext();

  // A Rewriter helps us manage the code rewriting task.
  auto rewriter = clang::Rewriter(sourceManager, compilerInstance.getLangOpts());

  // Set the main file handled by the source manager to the input file.
  const FileEntry* inputFile = fileManager.getFile(fileName);
  sourceManager.setMainFileID(
      sourceManager.createFileID(inputFile, SourceLocation(), SrcMgr::C_User));
  compilerInstance.getDiagnosticClient().BeginSourceFile(
      compilerInstance.getLangOpts(), &compilerInstance.getPreprocessor());

  // Create an AST consumer instance which is going to get called by
  // ParseAST.
  MyASTConsumer consumer(rewriter);

  // Parse the file to AST, registering our consumer as the AST consumer.
  clang::ParseAST(
    compilerInstance.getPreprocessor(), 
    &consumer, 
    compilerInstance.getASTContext());

  // At this point the rewriter's buffer should be full with the rewritten
  // file contents.
  const RewriteBuffer* buffer = rewriter.getRewriteBufferFor(sourceManager.getMainFileID());

  return std::string(buffer->begin(), buffer->end());
}

Here is my input program, negate.cpp:

bool negate(bool b) {
    if (b) {
        return false;
    } else {
        return true;
    }
}

When I run transform on this code I get the following error:

negate.cpp:1:1: error: unknown type name 'bool'
bool negate(bool b) {
^

This suggests to me that it is working in C mode, not C++ mode. To confirm, I replaced bool with int, true with 1 and false with 0:

int negate(int b) {
    if (b) {
        return 0;
    } else {
        return 1;
    }
}

This worked, so my question is:

How do I put a Clang CompilerInstance into C++ mode?


Update:

I tried changing the invocation, but no luck:

  auto& langOpts = compilerInstance.getLangOpts();
  langOpts.CPlusPlus = true;
  langOpts.CPlusPlus11 = true;

  auto* compilerInvocation = new CompilerInvocation();

  compilerInvocation->setLangDefaults(
    langOpts, 
    clang::InputKind::IK_CXX, 
    LangStandard::lang_gnu11);
sdgfsdh
  • 33,689
  • 26
  • 132
  • 245
  • 1
    I'm not too sure as I am just experimenting with clang myself but you could try: `auto& lang_opts = ci.getLangOpts(); lang_opts.CPlusPlus14 = 1;`. – Galik Jul 13 '16 at 16:07
  • `clang++ filename.cpp` maybe? Or with `clang++ -std=c++11 ...` maybe? Or name your source file to end with `.cpp` maybe? – Mark Setchell Jul 13 '16 at 16:18
  • @Galik Already tried that unfortuantely. – sdgfsdh Jul 13 '16 at 16:25
  • @MarkSetchell I am not using `clang` or `clang++` the command-line tools. – sdgfsdh Jul 13 '16 at 16:25
  • FrontEndFile.InputKind looks promising, distinguishes between IK_C, IK_CXX, IK_ObjC, etcetera. – Hans Passant Jul 13 '16 at 16:37
  • 1
    what `-std=` is being targeted? Is `-ansi` turned on? Is this approach the same as using `clang -cc1` option [What does the clang -cc1 option do?](http://stackoverflow.com/questions/8991662/what-does-the-clang-cc1-option-do)? – Richard Chambers Jul 13 '16 at 16:45
  • 3
    take a look at [Unable to force a clang CompilerInstance object to parse a header as C++ file](http://stackoverflow.com/questions/25580877/unable-to-force-a-clang-compilerinstance-object-to-parse-a-header-as-c-file) which seems to be a similar problem. – Richard Chambers Jul 13 '16 at 18:58

1 Answers1

6

Before executing the compiler, you have to check/set the CompilerInvocation structure with the getInvocation() / setInvocation() methods. This structure defines the language options (calling convention, garbage collection, ...), the language kind (C, CXX, Asm, ObjC, ...), the target triple, the preprocessor options and the language standard. It is set with the method setLangDefaults().

This is the InputKind class that holds which language you want to parse.

As written in the documentation, the setLangDefaults() API

sets some properties which depend solely on the input kind

by inspecting the source code, you can see the following constructors call hierarchy:

  • CompilerInstance()
  • CompilerInvocation()
  • CompilerInvocationBase()
  • LangOptions()

In the LangOptions() constructor, you can see that no specific/default language is set (LangOptions.def). Hence the need to go through the setLangDefaults() specific API.

you should thus go like:

std::string transform(std::string fileName) {

  CompilerInstance compilerInstance;
  compilerInstance.createDiagnostics();

  CompilerInvocation & invocation = compilerInstance.getInvocation();

  // Initialize target info with the default triple for our platform.
  auto TO = std::make_shared<TargetOptions>();
  TO->Triple = llvm::sys::getDefaultTargetTriple();
  TargetInfo* targetInfo =
      TargetInfo::CreateTargetInfo(compilerInstance.getDiagnostics(), TO);
  compilerInstance.setTarget(targetInfo);

  compilerInstance.createFileManager();
  auto& fileManager = compilerInstance.getFileManager();

  compilerInstance.createSourceManager(fileManager);
  auto& sourceManager = compilerInstance.getSourceManager();

  LangOptions langOpts; 
  langOpts.GNUMode = 1;  
  langOpts.CXXExceptions = 1;  
  langOpts.RTTI = 1;  
  langOpts.Bool = 1;   // <-- Note the Bool option here !
  langOpts.CPlusPlus = 1;  
  PreprocessorOptions &PPOpts = compilerInstance.getPreprocessorOpts();

  invocation.setLangDefaults(langOpts, 
                             clang::IK_CXX,
                             TO->Triple, 
                             PPOpts,
                             clang::LangStandard::lang_cxx0x); 


  compilerInstance.createPreprocessor(TU_Module);
  compilerInstance.createASTContext();

  // A Rewriter helps us manage the code rewriting task.
  auto rewriter = clang::Rewriter(sourceManager, compilerInstance.getLangOpts());

  // Set the main file handled by the source manager to the input file.
  const FileEntry* inputFile = fileManager.getFile(fileName);
  sourceManager.setMainFileID(
      sourceManager.createFileID(inputFile, SourceLocation(), SrcMgr::C_User));
  compilerInstance.getDiagnosticClient().BeginSourceFile(
      compilerInstance.getLangOpts(), &compilerInstance.getPreprocessor());

  // Create an AST consumer instance which is going to get called by
  // ParseAST.
  MyASTConsumer consumer(rewriter);

  // Parse the file to AST, registering our consumer as the AST consumer.
  clang::ParseAST(
    compilerInstance.getPreprocessor(), 
    &consumer, 
    compilerInstance.getASTContext());

  // At this point the rewriter's buffer should be full with the rewritten
  // file contents.
  const RewriteBuffer* buffer = rewriter.getRewriteBufferFor(sourceManager.getMainFileID());

  return std::string(buffer->begin(), buffer->end());
}

Reference taken in the clang tutorial CIRewriter.cpp, and which seems also a bit outdated.

Heyji
  • 1,113
  • 8
  • 26