Open
Description
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