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..426225cc86fc1 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" @@ -62,8 +63,12 @@ #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" +#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 +83,7 @@ #include #include #include +#include #include #include #include @@ -128,6 +134,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 +171,127 @@ 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 auto sInterpreters = new std::deque(); + 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); + }); + + 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;