Skip to content

libtooling: behaviour of __COUNT__ diverges from calling clang manually #87588

Open
@giulianobelinassi

Description

@giulianobelinassi

With the following files:

header.h:

#define __DEFINE_FUNCTION(i) \
   int function_ ## i (void) { return 0; }
#define _DEFINE_FUNCTION(i) __DEFINE_FUNCTION(i)
#define DEFINE_FUNCTION      _DEFINE_FUNCTION(__COUNTER__)
DEFINE_FUNCTION;

test.c:

#include "header.h"
DEFINE_FUNCTION;
int main()
{
  function_0();
  return 0;
}

compiling with:

$ clang++ -O2 -c test.c

Results in the input being accepted.


Now with libtooling, the following reproducer ( $ clang++ -g llvm-repro.cpp -lclang-cpp -lLLVM):

#include "clang/Frontend/ASTUnit.h"
#include "clang/Frontend/CompilerInstance.h"

using namespace clang;
using namespace llvm;

static const char *const header =
"#define __DEFINE_FUNCTION(i) \\\n"
"   int function_ ## i (void) { return 0; }\n"
"#define _DEFINE_FUNCTION(i) __DEFINE_FUNCTION(i)\n"
"#define DEFINE_FUNCTION      _DEFINE_FUNCTION(__COUNTER__)\n"
"DEFINE_FUNCTION;\n";

static const char *const test_file =
"#include \"header.h\"\n"
"DEFINE_FUNCTION;\n"
"int main()\n"
"{\n"
"  function_0();\n"
"  return 0;\n"
"}\n";

static const std::vector<const char *> clang_args =  {"-O2", "-c", "test.c"};

int main(void)
{
  IntrusiveRefCntPtr<DiagnosticsEngine> Diags;
  std::shared_ptr<CompilerInvocation> CInvok;
  std::shared_ptr<PCHContainerOperations> PCHContainerOps;

  IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> MFS;

  MFS = IntrusiveRefCntPtr<vfs::InMemoryFileSystem>(new vfs::InMemoryFileSystem);
  MFS->addFile("test.c", 0, MemoryBuffer::getMemBufferCopy(test_file));
  MFS->addFile("header.h", 0, MemoryBuffer::getMemBufferCopy(header));

  DiagnosticOptions *diagopts = new DiagnosticOptions();

  Diags = CompilerInstance::createDiagnostics(diagopts);

  clang::CreateInvocationOptions CIOpts;
  CIOpts.Diags = Diags;
  CInvok = clang::createInvocation(clang_args, std::move(CIOpts));

  FileManager *FileMgr = new FileManager(FileSystemOptions(), MFS);
  PCHContainerOps = std::make_shared<PCHContainerOperations>();

  auto AST = ASTUnit::LoadFromCompilerInvocation(
      CInvok, PCHContainerOps, Diags, FileMgr, false, CaptureDiagsKind::None, 1,
      TU_Complete, false, false, false);

  const DiagnosticsEngine &de = AST->getDiagnostics();

  if (AST == nullptr || de.hasErrorOccurred()) {
    llvm::outs() << "Rejected.\n";
    return 1;
  } else {
    llvm::outs() << "Accepted.\n";
    return 0;
  }
}

Results in the output always being rejected with the following output:

test.c:2:1: error: redefinition of 'function_0'
    2 | DEFINE_FUNCTION;
      | ^
header.h:4:30: note: expanded from macro 'DEFINE_FUNCTION'
    4 | #define DEFINE_FUNCTION      _DEFINE_FUNCTION(__COUNTER__)
      |                              ^
header.h:3:29: note: expanded from macro '_DEFINE_FUNCTION'
    3 | #define _DEFINE_FUNCTION(i) __DEFINE_FUNCTION(i)
      |                             ^
header.h:2:8: note: expanded from macro '__DEFINE_FUNCTION'
    2 |    int function_ ## i (void) { return 0; }
      |        ^
<scratch space>:3:1: note: expanded from here
    3 | function_0
      | ^
header.h:5:1: note: previous definition is here
    5 | DEFINE_FUNCTION;
      | ^
header.h:4:30: note: expanded from macro 'DEFINE_FUNCTION'
    4 | #define DEFINE_FUNCTION      _DEFINE_FUNCTION(__COUNTER__)
      |                              ^
header.h:3:29: note: expanded from macro '_DEFINE_FUNCTION'
    3 | #define _DEFINE_FUNCTION(i) __DEFINE_FUNCTION(i)
      |                             ^
header.h:2:8: note: expanded from macro '__DEFINE_FUNCTION'
    2 |    int function_ ## i (void) { return 0; }
      |        ^
<scratch space>:3:1: note: expanded from here
    3 | function_0
      | ^
Rejected.

Notice how it is exactly the same code, but one is being compiled through clang, whereas the other is being compiled through libtooling.

I'd say this is undesired behavior and a bug.

clang version 18.1.2
Target: x86_64-suse-linux
Thread model: posix
InstalledDir: /usr/bin

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions