From e7146a59af967833554f7b83669c5d3ef2905a5e Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Wed, 8 Apr 2026 17:51:52 +0200 Subject: [PATCH 1/2] upgrade cppinterop to latest --- .../include/CppInterOp/CppInterOp.h | 3 +- .../CppInterOp/lib/CppInterOp/CppInterOp.cpp | 381 ++++++++++++------ .../lib/CppInterOp/CppInterOpInterpreter.h | 21 +- interpreter/CppInterOp/lib/CppInterOp/Sins.h | 28 ++ .../CppInterOp/lib/CppInterOp/exports.ld | 1 + .../unittests/CppInterOp/InterpreterTest.cpp | 54 ++- .../CppInterOp/ScopeReflectionTest.cpp | 115 +++++- 7 files changed, 453 insertions(+), 150 deletions(-) create mode 100644 interpreter/CppInterOp/lib/CppInterOp/Sins.h diff --git a/interpreter/CppInterOp/include/CppInterOp/CppInterOp.h b/interpreter/CppInterOp/include/CppInterOp/CppInterOp.h index 0fdcc609a1172..dcb9ff7b3ebd4 100644 --- a/interpreter/CppInterOp/include/CppInterOp/CppInterOp.h +++ b/interpreter/CppInterOp/include/CppInterOp/CppInterOp.h @@ -38,6 +38,7 @@ using TCppIndex_t = size_t; using TCppScope_t = void*; using TCppConstScope_t = const void*; using TCppType_t = void*; +using TCppConstType_t = const void*; using TCppFunction_t = void*; using TCppConstFunction_t = const void*; using TCppFuncAddr_t = void*; @@ -368,7 +369,7 @@ CPPINTEROP_API bool IsComplete(TCppScope_t scope); CPPINTEROP_API size_t SizeOf(TCppScope_t scope); /// Checks if it is a "built-in" or a "complex" type. -CPPINTEROP_API bool IsBuiltin(TCppType_t type); +CPPINTEROP_API bool IsBuiltin(TCppConstType_t type); /// Checks if it is a templated class. CPPINTEROP_API bool IsTemplate(TCppScope_t handle); diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index 9c6f00432765a..7136aa37ffabb 100755 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -10,6 +10,7 @@ #include "CppInterOp/CppInterOp.h" #include "Compatibility.h" +#include "Sins.h" // for access to private members #include "clang/AST/Attrs.inc" #include "clang/AST/CXXInheritance.h" @@ -64,6 +65,9 @@ #include "llvm/Support/FileSystem.h" #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/Path.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/TargetSelect.h" #include "llvm/Support/raw_ostream.h" #include "llvm/TargetParser/Host.h" #include "llvm/TargetParser/Triple.h" @@ -78,6 +82,7 @@ #include #include #include +#include #include #include #include @@ -128,6 +133,9 @@ using namespace llvm; struct InterpreterInfo { compat::Interpreter* Interpreter = nullptr; bool isOwned = true; + // Store the list of builtin types. + llvm::StringMap BuiltinMap; + InterpreterInfo(compat::Interpreter* I, bool Owned) : Interpreter(I), isOwned(Owned) {} @@ -162,15 +170,128 @@ struct InterpreterInfo { InterpreterInfo& operator=(const InterpreterInfo&) = delete; }; -// std::deque avoids relocations and calling the dtor of InterpreterInfo. -static llvm::ManagedStatic> sInterpreters; +static void DefaultProcessCrashHandler(void*); +// Function-static storage for interpreters +static std::deque& GetInterpreters() { + // static int FakeArgc = 1; + // static const std::string VersionStr = GetVersion(); + // static const char* ArgvBuffer[] = {VersionStr.c_str(), nullptr}; + // static const char** FakeArgv = ArgvBuffer; + // static llvm::InitLLVM X(FakeArgc, FakeArgv); + // Cannot be a llvm::ManagedStatic because X will call shutdown which will + // trigger destruction on llvm::ManagedStatics and the destruction of the + // InterpreterInfos require to have llvm around. + // FIXME: Currently we never call llvm::llvm_shutdown and sInterpreters leaks. + static llvm::ManagedStatic> sInterpreters; + static std::once_flag ProcessInitialized; + std::call_once(ProcessInitialized, []() { + llvm::sys::PrintStackTraceOnErrorSignal("CppInterOp"); + + // Initialize all targets (required for device offloading) + llvm::InitializeAllTargetInfos(); + llvm::InitializeAllTargets(); + llvm::InitializeAllTargetMCs(); + llvm::InitializeAllAsmParsers(); + llvm::InitializeAllAsmPrinters(); + + llvm::sys::AddSignalHandler(DefaultProcessCrashHandler, /*Cookie=*/nullptr); + // std::atexit(llvm::llvm_shutdown); + }); + + return *sInterpreters; +} + +// Global crash handler for the entire process +static void DefaultProcessCrashHandler(void*) { + // Access the static deque via the getter + std::deque& Interps = GetInterpreters(); + + llvm::errs() << "\n**************************************************\n"; + llvm::errs() << " CppInterOp CRASH DETECTED\n"; + + if (!Interps.empty()) { + llvm::errs() << " Active Interpreters:\n"; + for (const auto& Info : Interps) { + if (Info.Interpreter) + llvm::errs() << " - " << Info.Interpreter << "\n"; + } + } + + llvm::errs() << "**************************************************\n"; + llvm::errs().flush(); + + // Print backtrace (includes JIT symbols if registered) + llvm::sys::PrintStackTrace(llvm::errs()); + + llvm::errs() << "**************************************************\n"; + llvm::errs().flush(); + + // The process must actually terminate for EXPECT_DEATH to pass. + // We use _exit to avoid calling atexit() handlers which might be corrupted. + llvm::sys::Process::Exit(/*RetCode=*/1, /*NoCleanup=*/false); +} + +static void RegisterInterpreter(compat::Interpreter* I, bool Owned) { + std::deque& Interps = GetInterpreters(); + Interps.emplace_back(I, Owned); +} static compat::Interpreter& getInterp(TInterp_t I = nullptr) { if (I) return *static_cast(I); - assert(!sInterpreters->empty() && + auto& Interps = GetInterpreters(); + assert(!Interps.empty() && "Interpreter instance must be set before calling this!"); - return *sInterpreters->back().Interpreter; + return *Interps.back().Interpreter; +} + +TInterp_t GetInterpreter() { + std::deque& Interps = GetInterpreters(); + if (Interps.empty()) + return nullptr; + return Interps.back().Interpreter; +} + +void UseExternalInterpreter(TInterp_t I) { + assert(GetInterpreters().empty() && "sInterpreter already in use!"); + RegisterInterpreter(static_cast(I), /*Owned=*/false); +} + +bool ActivateInterpreter(TInterp_t I) { + if (!I) + return false; + + std::deque& Interps = GetInterpreters(); + auto found = + std::find_if(Interps.begin(), Interps.end(), + [&I](const auto& Info) { return Info.Interpreter == I; }); + if (found == Interps.end()) + return false; + + if (std::next(found) != Interps.end()) // if not already last element. + std::rotate(found, found + 1, Interps.end()); + + return true; // success +} + +bool DeleteInterpreter(TInterp_t I /*=nullptr*/) { + std::deque& Interps = GetInterpreters(); + if (Interps.empty()) + return false; + + if (!I) { + Interps.pop_back(); // Triggers ~InterpreterInfo() and potential delete + return true; + } + + auto found = + std::find_if(Interps.begin(), Interps.end(), + [&I](const auto& Info) { return Info.Interpreter == I; }); + if (found == Interps.end()) + return false; // failure + + Interps.erase(found); + return true; } static clang::Sema& getSema() { return getInterp().getCI()->getSema(); } @@ -408,7 +529,7 @@ size_t SizeOf(TCppScope_t scope) { return 0; } -bool IsBuiltin(TCppType_t type) { +bool IsBuiltin(TCppConstType_t type) { QualType Ty = QualType::getFromOpaquePtr(type); if (Ty->isBuiltinType() || Ty->isAnyComplexType()) return true; @@ -1887,83 +2008,133 @@ TCppType_t AddTypeQualifier(TCppType_t type, QualKind qual) { return QT.getAsOpaquePtr(); } -// Internal functions that are not needed outside the library are -// encompassed in an anonymous namespace as follows. This function converts -// from a string to the actual type. It is used in the GetType() function. -namespace { +// Registers all permutations of a word set +static void RegisterPerms(llvm::StringMap& Map, QualType QT, + llvm::SmallVectorImpl& Words) { + std::sort(Words.begin(), Words.end()); + do { + std::string Key; + for (size_t i = 0; i < Words.size(); ++i) { + if (i > 0) + Key += ' '; + Key += Words[i].str(); + } + Map[Key] = QT; + } while (std::next_permutation(Words.begin(), Words.end())); +} +ALLOW_ACCESS(ASTContext, Types, llvm::SmallVector); +static void PopulateBuiltinMap(ASTContext& Context) { + const PrintingPolicy Policy(Context.getLangOpts()); + auto& BuiltinMap = GetInterpreters().back().BuiltinMap; + const auto& Types = ACCESS(Context, Types); + + for (clang::Type* T : Types) { + auto* BT = llvm::dyn_cast(T); + if (!BT || BT->isPlaceholderType()) + continue; + + QualType QT(BT, 0); + std::string Name = QT.getAsString(Policy); + if (Name.empty() || Name[0] == '<') + continue; + + // Initial entry (e.g., "int", "unsigned long") + BuiltinMap[Name] = QT; + + llvm::SmallVector Words; + llvm::StringRef(Name).split(Words, ' ', -1, false); + + bool hasInt = false; + bool hasSigned = false; + bool hasUnsigned = false; + bool hasChar = false; + bool isModifiable = false; + + for (auto W : Words) { + if (W == "int") + hasInt = true; + else if (W == "signed") + hasSigned = true; + else if (W == "unsigned") + hasUnsigned = true; + else if (W == "char") + hasChar = true; + + if (W == "long" || W == "short" || hasInt) + isModifiable = true; + } + + // Skip things like 'float' or 'double' that aren't combined + if (!isModifiable && !hasUnsigned && !hasSigned) + continue; + + // Register base permutations (e.g., "long long" or "unsigned int") + if (Words.size() > 1) + RegisterPerms(BuiltinMap, QT, Words); + + // Expansion: Add "int" suffix where missing (e.g., "short" -> "short int") + if (!hasInt && !hasChar) { + auto WithInt = Words; + WithInt.push_back("int"); + RegisterPerms(BuiltinMap, QT, WithInt); + + // If we are adding 'int', we should also try adding 'signed' + // to cover cases like "short" -> "signed short int" + if (!hasSigned && !hasUnsigned) { + auto WithBoth = WithInt; + WithBoth.push_back("signed"); + RegisterPerms(BuiltinMap, QT, WithBoth); + } + } + + // Expansion: Add "signed" prefix + // (e.g., "int" -> "signed int", "long" -> "signed long") + if (!hasSigned && !hasUnsigned) { + auto WithSigned = Words; + WithSigned.push_back("signed"); + RegisterPerms(BuiltinMap, QT, WithSigned); + } + } + + // Explicit global synonym + BuiltinMap["signed"] = Context.IntTy; + BuiltinMap["unsigned"] = Context.UnsignedIntTy; +} static QualType findBuiltinType(llvm::StringRef typeName, ASTContext& Context) { - bool issigned = false; - bool isunsigned = false; - if (typeName.starts_with("signed ")) { - issigned = true; - typeName = StringRef(typeName.data() + 7, typeName.size() - 7); - } - if (!issigned && typeName.starts_with("unsigned ")) { - isunsigned = true; - typeName = StringRef(typeName.data() + 9, typeName.size() - 9); - } - if (typeName == "char") { - if (isunsigned) - return Context.UnsignedCharTy; - return Context.SignedCharTy; - } - if (typeName == "short") { - if (isunsigned) - return Context.UnsignedShortTy; - return Context.ShortTy; - } - if (typeName == "int") { - if (isunsigned) - return Context.UnsignedIntTy; - return Context.IntTy; - } - if (typeName == "long") { - if (isunsigned) - return Context.UnsignedLongTy; - return Context.LongTy; - } - if (typeName == "long long") { - if (isunsigned) - return Context.UnsignedLongLongTy; - return Context.LongLongTy; - } - if (!issigned && !isunsigned) { - if (typeName == "bool") - return Context.BoolTy; - if (typeName == "float") - return Context.FloatTy; - if (typeName == "double") - return Context.DoubleTy; - if (typeName == "long double") - return Context.LongDoubleTy; - - if (typeName == "wchar_t") - return Context.WCharTy; - if (typeName == "char16_t") - return Context.Char16Ty; - if (typeName == "char32_t") - return Context.Char32Ty; - } - /* Missing - CanQualType WideCharTy; // Same as WCharTy in C++, integer type in C99. - CanQualType WIntTy; // [C99 7.24.1], integer type unchanged by default - promotions. - */ - return QualType(); + llvm::StringMap& BuiltinMap = GetInterpreters().back().BuiltinMap; + if (BuiltinMap.empty()) + PopulateBuiltinMap(Context); + + // Fast Lookup + auto It = BuiltinMap.find(typeName); + if (It != BuiltinMap.end()) + return It->second; + + return QualType(); // Return null if not a builtin +} +static std::optional GetTypeInternal(Decl* D) { + if (!D) + return {}; + // Even though typedefs derive from TypeDecl, their getTypeForDecl() + // returns a nullptr. + if (const auto* TND = llvm::dyn_cast_or_null(D)) + return TND->getUnderlyingType(); + + if (auto* VD = dyn_cast(D)) + return VD->getType(); + + if (const auto* TD = llvm::dyn_cast_or_null(D)) + return QualType(TD->getTypeForDecl(), 0); + + return {}; } -} // namespace TCppType_t GetType(const std::string& name) { QualType builtin = findBuiltinType(name, getASTContext()); if (!builtin.isNull()) return builtin.getAsOpaquePtr(); - auto* D = (Decl*)GetNamed(name, /* Within= */ 0); - if (auto* TD = llvm::dyn_cast_or_null(D)) { - return QualType(TD->getTypeForDecl(), 0).getAsOpaquePtr(); - } - - return (TCppType_t)0; + return GetTypeFromScope((Decl*)GetNamed(name, /*Within=*/nullptr)); } TCppType_t GetComplexType(TCppType_t type) { @@ -1974,17 +2145,12 @@ TCppType_t GetComplexType(TCppType_t type) { TCppType_t GetTypeFromScope(TCppScope_t klass) { if (!klass) - return 0; - - auto* D = (Decl*)klass; - - if (auto* VD = dyn_cast(D)) - return VD->getType().getAsOpaquePtr(); + return nullptr; - if (auto* TD = dyn_cast(D)) - return getASTContext().getTypeDeclType(TD).getAsOpaquePtr(); + if (auto QT = GetTypeInternal((Decl*)klass)) + return QT->getAsOpaquePtr(); - return (TCppType_t) nullptr; + return nullptr; } // Internal functions that are not needed outside the library are @@ -3382,6 +3548,9 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, std::back_inserter(ClingArgv), [&](const std::string& str) { return str.c_str(); }); + // Force global process initialization. + (void)GetInterpreters(); + #ifdef CPPINTEROP_USE_CLING auto I = new compat::Interpreter(ClingArgv.size(), &ClingArgv[0]); #else @@ -3424,7 +3593,7 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, )"); } - sInterpreters->emplace_back(I, /*Owned=*/true); + RegisterInterpreter(I, /*Owned=*/true); // Define runtime symbols in the JIT dylib for clang-repl #if !defined(CPPINTEROP_USE_CLING) && !defined(EMSCRIPTEN) @@ -3456,44 +3625,6 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, return I; } -bool DeleteInterpreter(TInterp_t I /*=nullptr*/) { - if (!I) { - sInterpreters->pop_back(); - return true; - } - - auto found = - std::find_if(sInterpreters->begin(), sInterpreters->end(), - [&I](const auto& Info) { return Info.Interpreter == I; }); - if (found == sInterpreters->end()) - return false; // failure - - sInterpreters->erase(found); - return true; -} - -bool ActivateInterpreter(TInterp_t I) { - if (!I) - return false; - - auto found = - std::find_if(sInterpreters->begin(), sInterpreters->end(), - [&I](const auto& Info) { return Info.Interpreter == I; }); - if (found == sInterpreters->end()) - return false; - - if (std::next(found) != sInterpreters->end()) // if not already last element. - std::rotate(found, found + 1, sInterpreters->end()); - - return true; // success -} - -TInterp_t GetInterpreter() { - if (sInterpreters->empty()) - return nullptr; - return sInterpreters->back().Interpreter; -} - InterpreterLanguage GetLanguage(TInterp_t I /*=nullptr*/) { compat::Interpreter* interp = &getInterp(I); const auto& LO = interp->getCI()->getLangOpts(); @@ -3526,12 +3657,6 @@ InterpreterLanguageStandard GetLanguageStandard(TInterp_t I /*=nullptr*/) { return langStandard; } -void UseExternalInterpreter(TInterp_t I) { - assert(sInterpreters->empty() && "sInterpreter already in use!"); - sInterpreters->emplace_back(static_cast(I), - /*isOwned=*/false); -} - void AddSearchPath(const char* dir, bool isUser, bool prepend) { getInterp().getDynamicLibraryManager()->addSearchPath(dir, isUser, prepend); } diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOpInterpreter.h b/interpreter/CppInterOp/lib/CppInterOp/CppInterOpInterpreter.h index d4cd3d501124b..380e0b6dd69e7 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOpInterpreter.h +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOpInterpreter.h @@ -171,14 +171,14 @@ class Interpreter { private: static std::tuple initAndGetFileDescriptors(std::vector& vargs, - std::unique_ptr& io_ctx) { + IOContext& io_ctx) { int stdin_fd = 0; int stdout_fd = 1; int stderr_fd = 2; // Only initialize temp files if not already initialized - if (!io_ctx->stdin_file || !io_ctx->stdout_file || !io_ctx->stderr_file) { - bool init = io_ctx->initializeTempFiles(); + if (!io_ctx.stdin_file || !io_ctx.stdout_file || !io_ctx.stderr_file) { + bool init = io_ctx.initializeTempFiles(); if (!init) { llvm::errs() << "Can't start out-of-process JIT execution.\n"; stdin_fd = -1; @@ -186,9 +186,9 @@ class Interpreter { stderr_fd = -1; } } - stdin_fd = fileno(io_ctx->stdin_file.get()); - stdout_fd = fileno(io_ctx->stdout_file.get()); - stderr_fd = fileno(io_ctx->stderr_file.get()); + stdin_fd = fileno(io_ctx.stdin_file.get()); + stdout_fd = fileno(io_ctx.stdout_file.get()); + stderr_fd = fileno(io_ctx.stderr_file.get()); return std::make_tuple(stdin_fd, stdout_fd, stderr_fd); } @@ -208,13 +208,6 @@ class Interpreter { const std::vector>& moduleExtensions = {}, void* extraLibHandle = nullptr, bool noRuntime = true) { - // Initialize all targets (required for device offloading) - llvm::InitializeAllTargetInfos(); - llvm::InitializeAllTargets(); - llvm::InitializeAllTargetMCs(); - llvm::InitializeAllAsmParsers(); - llvm::InitializeAllAsmPrinters(); - std::vector vargs(argv + 1, argv + argc); int stdin_fd = 0; @@ -233,7 +226,7 @@ class Interpreter { if (outOfProcess) { std::tie(stdin_fd, stdout_fd, stderr_fd) = - initAndGetFileDescriptors(vargs, io_ctx); + initAndGetFileDescriptors(vargs, *io_ctx); if (stdin_fd == -1 || stdout_fd == -1 || stderr_fd == -1) { llvm::errs() diff --git a/interpreter/CppInterOp/lib/CppInterOp/Sins.h b/interpreter/CppInterOp/lib/CppInterOp/Sins.h new file mode 100644 index 0000000000000..ecd3b5f0e063b --- /dev/null +++ b/interpreter/CppInterOp/lib/CppInterOp/Sins.h @@ -0,0 +1,28 @@ +#ifndef LIB_CPPINTEROP_SINS_H +#define LIB_CPPINTEROP_SINS_H + +/// Standard-protected facility allowing access into private members in C++. +/// Use with caution! +// NOLINTBEGIN(cppcoreguidelines-macro-usage) +#define CONCATE_(X, Y) X##Y +#define CONCATE(X, Y) CONCATE_(X, Y) +#define ALLOW_ACCESS(CLASS, MEMBER, ...) \ + template \ + struct CONCATE(MEMBER, __LINE__) { \ + friend __VA_ARGS__ CLASS::*Access(Only*) { return Member; } \ + }; \ + template struct Only_##MEMBER; \ + template <> struct Only_##MEMBER { \ + friend __VA_ARGS__ CLASS::*Access(Only_##MEMBER*); \ + }; \ + template struct CONCATE(MEMBER, \ + __LINE__), &CLASS::MEMBER> + +#define ACCESS(OBJECT, MEMBER) \ + (OBJECT).* \ + Access( \ + (Only_##MEMBER>*)nullptr) + +// NOLINTEND(cppcoreguidelines-macro-usage) + +#endif // LIB_CPPINTEROP_SINS_H diff --git a/interpreter/CppInterOp/lib/CppInterOp/exports.ld b/interpreter/CppInterOp/lib/CppInterOp/exports.ld index 67b50dae463f9..762a463096e68 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/exports.ld +++ b/interpreter/CppInterOp/lib/CppInterOp/exports.ld @@ -54,3 +54,4 @@ -Wl,--export=_ZN4llvm15SmallVectorBaseIjE8set_sizeEm -Wl,--export=_ZN5clang11Interpreter6createENSt3__210unique_ptrINS_16CompilerInstanceENS1_14default_deleteIS3_EEEENS2_IN4llvm3orc12LLJITBuilderENS4_IS9_EEEE -Wl,--export=_ZNK5clang13CXXRecordDecl19isInjectedClassNameEv +-Wl,--export=_ZNK5clang8QualType11getAsStringERKNS_14PrintingPolicyE diff --git a/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp index 51652a5c6594b..42e1434dca91f 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp @@ -25,6 +25,7 @@ #include "gtest/gtest.h" #include +#include using ::testing::StartsWith; @@ -108,7 +109,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_DeleteInterpreter) { EXPECT_EQ(I3, Cpp::GetInterpreter()) << "I3 is not active"; - EXPECT_TRUE(Cpp::DeleteInterpreter(nullptr)); + EXPECT_TRUE(Cpp::DeleteInterpreter(/*I=*/nullptr)); EXPECT_EQ(I2, Cpp::GetInterpreter()); auto* I4 = reinterpret_cast(static_cast(~0U)); @@ -456,3 +457,54 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_ExternalInterpreter) { delete ExtInterp; #endif } + +// Verify the basic crash banner and Active Interpreter reporting +#ifdef GTEST_HAS_DEATH_TEST +TYPED_TEST(CPPINTEROP_TEST_MODE, SignalHandler_BasicBanner) { + // Ensure a clean registry for each JIT configuration + + // FIXME: Uncomment after resolving compiler-research/CppInterOp#887 + + // while (Cpp::GetInterpreter()) + // Cpp::DeleteInterpreter(/*I=*/nullptr); + + // EXPECT_FALSE(Cpp::GetInterpreter()) << "Failed to delete all interpreters"; + + // // Create an interpreter (this calls RegisterInterpreter internally) + // TInterp_t I = TestFixture::CreateInterpreter(); + // ASSERT_NE(I, nullptr); + + // We expect the banner to appear in stderr when the process dies + std::string ExpectedMsg = "CppInterOp CRASH DETECTED"; +#ifdef _WIN32 + // FIXME: Windows says 'Actual msg:' without maybe capturing the message. + ExpectedMsg = ""; +#endif //_WIN32 + + EXPECT_DEATH( + { + // Trigger a synchronous signal + raise(SIGABRT); + }, + ExpectedMsg); +} +#endif // GTEST_HAS_DEATH_TEST + +// Verify that the handler correctly lists multiple interpreters +#ifdef GTEST_HAS_DEATH_TEST +TYPED_TEST(CPPINTEROP_TEST_MODE, SignalHandler_MultipleInterpreters) { + ASSERT_NE(TestFixture::CreateInterpreter(), nullptr); + ASSERT_NE(TestFixture::CreateInterpreter(), nullptr); + + // The handler iterates through the deque and prints the pointers + + // We check for the "Active Interpreters:" header and the list format + std::string ExpectedMsg = "Active Interpreters:.*- 0x"; +#ifdef _WIN32 + // FIXME: Windows says 'Actual msg:' without maybe capturing the message. + ExpectedMsg = ""; +#endif //_WIN32 + + EXPECT_DEATH({ raise(SIGSEGV); }, ExpectedMsg); +} +#endif // GTEST_HAS_DEATH_TEST diff --git a/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp index 3aece27b73000..988a15e735d32 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp @@ -4,19 +4,17 @@ #include "clang-c/CXCppInterOp.h" #include "clang/AST/ASTContext.h" -#include "clang/Basic/Version.h" -#include "clang/Frontend/CompilerInstance.h" -#include "clang/Sema/Sema.h" - #include "clang/AST/ASTDumper.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/GlobalDecl.h" +#include "clang/AST/Type.h" +#include "clang/Basic/Version.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Sema/Sema.h" #include "llvm/Support/Valgrind.h" -#include "clang-c/CXCppInterOp.h" - #include "gtest/gtest.h" #include @@ -25,6 +23,111 @@ using namespace TestUtils; using namespace llvm; using namespace clang; +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetTypeOfBuiltins) { + // Structural check: Ensure every valid BuiltinType kind can be resolved + TestFixture::CreateInterpreter(); + ASTContext& C = Interp->getCI()->getASTContext(); // for brevity + +#define BUILTIN_TYPE(Id, SingletonId) \ + { \ + QualType QT = C.SingletonId; \ + if (!QT.isNull()) { \ + std::string name = QT.getAsString(C.getPrintingPolicy()); \ + if (name[0] != '<' && !QT->isPlaceholderType()) { \ + const auto* FoundTy = Cpp::GetType(name); \ + EXPECT_TRUE(FoundTy) << "Failed to find builtin: '" << name << "'"; \ + if (FoundTy) { \ + EXPECT_EQ(FoundTy, QT.getAsOpaquePtr()) \ + << "Type mismatch for '" << name << "'"; \ + EXPECT_TRUE(Cpp::IsBuiltin(FoundTy)); \ + } \ + } \ + } \ + } +#include "clang/AST/BuiltinTypes.def" +#undef BUILTIN_TYPE + + auto Verify = [&](const char* Name, QualType Expected) { + void* Found = Cpp::GetType(Name); + EXPECT_EQ(Found, Expected.getAsOpaquePtr()) << "Failed for: " << Name; + }; + + // --- Integer Variations --- + Verify("int", C.IntTy); + Verify("signed int", C.IntTy); + Verify("signed", C.IntTy); + Verify("int signed", C.IntTy); + + // --- Short Variations --- + Verify("short", C.ShortTy); + Verify("short int", C.ShortTy); + Verify("signed short", C.ShortTy); + Verify("signed short int", C.ShortTy); + Verify("int short signed", C.ShortTy); + + // --- Long Variations --- + Verify("long", C.LongTy); + Verify("long int", C.LongTy); + Verify("signed long", C.LongTy); + Verify("signed long int", C.LongTy); + Verify("int long", C.LongTy); + + // --- Long Long Variations --- + Verify("long long", C.LongLongTy); + Verify("long long int", C.LongLongTy); + Verify("signed long long", C.LongLongTy); + Verify("signed long long int", C.LongLongTy); + Verify("int long long signed", C.LongLongTy); + + // --- Unsigned Variations --- + Verify("unsigned", C.UnsignedIntTy); + Verify("unsigned int", C.UnsignedIntTy); + Verify("int unsigned", C.UnsignedIntTy); + + Verify("unsigned short", C.UnsignedShortTy); + Verify("unsigned short int", C.UnsignedShortTy); + Verify("int short unsigned", C.UnsignedShortTy); + + Verify("unsigned long", C.UnsignedLongTy); + Verify("unsigned long int", C.UnsignedLongTy); + + Verify("unsigned long long", C.UnsignedLongLongTy); + Verify("unsigned long long int", C.UnsignedLongLongTy); + + // --- Character Variations (No 'int' suffix allowed) --- + Verify("char", C.CharTy); + Verify("signed char", C.SignedCharTy); + Verify("unsigned char", C.UnsignedCharTy); + + // Negative check: signed char int is NOT a valid builtin + EXPECT_EQ(Cpp::GetType("signed char int"), nullptr); + + // Maybe these should be considered builtins? + // EXPECT_EQ(Cpp::GetType("size_t"), C.getSizeType().getAsOpaquePtr()); + // EXPECT_EQ(Cpp::GetType("ptrdiff_t"), + // C.getPointerDiffType().getAsOpaquePtr()); + // EXPECT_EQ(Cpp::GetType("intptr_t"), C.getIntPtrType().getAsOpaquePtr()); + // EXPECT_EQ(Cpp::GetType("uintptr_t"), C.getUIntPtrType().getAsOpaquePtr()); + // EXPECT_EQ(Cpp::GetType("size_t"), C.getSizeType().getAsOpaquePtr()); + // EXPECT_EQ(Cpp::GetType("size_t"), C.getSizeType().getAsOpaquePtr()); + + // EXPECT_EQ(Cpp::GetType("wint_t"), C.WIntTy.getAsOpaquePtr()); + // EXPECT_EQ(Cpp::GetType("wchar_t"), C.WCharTy.getAsOpaquePtr()); +} + +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetTypeOfTypedef) { + std::string code = R"( + typedef int Type_t; + )"; + + std::vector Decls; + GetAllTopLevelDecls(code, Decls); + auto Ty = Cpp::GetType("Type_t"); + EXPECT_TRUE(Ty); + EXPECT_TRUE(Ty == Cpp::GetTypeFromScope(Decls[0])); + EXPECT_FALSE(Cpp::GetTypeFromScope(nullptr)); +} + TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_IsEnumScope) { std::vector Decls; std::vector SubDecls; From 29786d5f7187b5931d8d4bc1564da04935e2fa84 Mon Sep 17 00:00:00 2001 From: Vassil Vassilev Date: Wed, 8 Apr 2026 17:52:29 +0200 Subject: [PATCH 2/2] Fix exit-time crash by using a leaky singleton for interpreters. Re-enable llvm::InitLLVM to handle system limits and signal handlers. To avoid the "Static Destruction Order Fiasco," this patch transitions the internal interpreter registry from llvm::ManagedStatic to a heap-allocated static pointer. This ensures that the std::deque is not destroyed during the C-runtime exit sequence. Without this, the JIT attempts to run de-initializers (using LLVM globals) after those globals have already been cleared, leading to segfaults on Darwin and Linux. The InitLLVM which does some heavy lifting in installing handlers and bumping system limits. It is designed to work for binaries, however, since we have a pimpl pattern which isolates llvm into the CppInterOp library we need to take extra care. --- .../CppInterOp/lib/CppInterOp/CppInterOp.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index 7136aa37ffabb..426225cc86fc1 100755 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -63,6 +63,7 @@ #include "llvm/Support/Debug.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/InitLLVM.h" #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/Path.h" #include "llvm/Support/Process.h" @@ -173,16 +174,16 @@ struct InterpreterInfo { static void DefaultProcessCrashHandler(void*); // Function-static storage for interpreters static std::deque& GetInterpreters() { - // static int FakeArgc = 1; - // static const std::string VersionStr = GetVersion(); - // static const char* ArgvBuffer[] = {VersionStr.c_str(), nullptr}; - // static const char** FakeArgv = ArgvBuffer; - // static llvm::InitLLVM X(FakeArgc, FakeArgv); + static int FakeArgc = 1; + static const std::string VersionStr = GetVersion(); + static const char* ArgvBuffer[] = {VersionStr.c_str(), nullptr}; + static const char** FakeArgv = ArgvBuffer; + static llvm::InitLLVM X(FakeArgc, FakeArgv); // Cannot be a llvm::ManagedStatic because X will call shutdown which will // trigger destruction on llvm::ManagedStatics and the destruction of the // InterpreterInfos require to have llvm around. // FIXME: Currently we never call llvm::llvm_shutdown and sInterpreters leaks. - static llvm::ManagedStatic> sInterpreters; + static auto sInterpreters = new std::deque(); static std::once_flag ProcessInitialized; std::call_once(ProcessInitialized, []() { llvm::sys::PrintStackTraceOnErrorSignal("CppInterOp"); @@ -195,7 +196,6 @@ static std::deque& GetInterpreters() { llvm::InitializeAllAsmPrinters(); llvm::sys::AddSignalHandler(DefaultProcessCrashHandler, /*Cookie=*/nullptr); - // std::atexit(llvm::llvm_shutdown); }); return *sInterpreters;