diff --git a/unittests/CppInterOp/CMakeLists.txt b/unittests/CppInterOp/CMakeLists.txt index 87492280a..1736b011c 100644 --- a/unittests/CppInterOp/CMakeLists.txt +++ b/unittests/CppInterOp/CMakeLists.txt @@ -46,10 +46,10 @@ add_cppinterop_unittest(CppInterOpTests CAPITest.cpp CommentReflectionTest.cpp CppInterOpThunksTest.cpp + CppInterOpTypesTest.cpp DiagnosticTest.cpp EnumReflectionTest.cpp FunctionReflectionTest.cpp - HandleTypesTest.cpp InterpreterTest.cpp JitTest.cpp ResultTest.cpp @@ -127,6 +127,11 @@ endif() add_subdirectory(TestSharedLib) add_dependencies(DynamicLibraryManagerTests TestSharedLib) +# Regression guard that CppInterOpTypes.h stays a self-contained header +# (no out-of-line .cpp dependency from libclangCppInterOp). See +# unittests/CppInterOp/TypesSelfContainedTest/ for the rationale. +add_subdirectory(TypesSelfContainedTest) + # Cross-TU VTableOverlay perf check, built as part of the test suite. # Standalone-only (its Google Benchmark dependency is provisioned only then; # keeps it out of embedded host builds such as ROOT). Excluded on WASM diff --git a/unittests/CppInterOp/HandleTypesTest.cpp b/unittests/CppInterOp/CppInterOpTypesTest.cpp similarity index 100% rename from unittests/CppInterOp/HandleTypesTest.cpp rename to unittests/CppInterOp/CppInterOpTypesTest.cpp diff --git a/unittests/CppInterOp/TypesSelfContainedTest/CMakeLists.txt b/unittests/CppInterOp/TypesSelfContainedTest/CMakeLists.txt new file mode 100644 index 000000000..c7174c8dc --- /dev/null +++ b/unittests/CppInterOp/TypesSelfContainedTest/CMakeLists.txt @@ -0,0 +1,42 @@ +# Regression guard for CppInterOpTypes.h staying header-only. This +# executable includes only CppInterOpTypes.h, exercises every helper, +# and DELIBERATELY does not link libclangCppInterOp or any LLVM library +# — if any header helper needs an out-of-line symbol, the link fails. +# See TypesSelfContainedTest.cpp for the full rationale. +# +# Not a runtime test — the built binary is not invoked. Success is +# purely that the link stage completes; ctest just runs it to confirm +# the process starts. + +add_executable(CppInterOpTypesSelfContainedTest EXCLUDE_FROM_ALL + TypesSelfContainedTest.cpp) + +# The one and only include CppInterOpTypes.h needs is its own header +# directory. NOTHING from libclangCppInterOp or LLVM. +target_include_directories(CppInterOpTypesSelfContainedTest PRIVATE + ${CMAKE_SOURCE_DIR}/include) + +# Match the Release-ish flags real Dispatch consumers see so any +# NDEBUG-guarded regressions surface here too. +target_compile_definitions(CppInterOpTypesSelfContainedTest PRIVATE NDEBUG) + +# Route the executable to the same bin dir the other cppinterop +# unittests land in, so ctest's default search path finds it. Follow +# the pattern from add_cppinterop_unittest (see unittests/CMakeLists.txt). +set_output_directory(CppInterOpTypesSelfContainedTest + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/../bin/$/ + LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/../bin/$/ +) + +# Register with ctest — the invocation just checks the process +# launches; the load-bearing signal is that the link succeeded at +# build time. +add_test(NAME cppinterop-CppInterOpTypesSelfContainedTest + COMMAND CppInterOpTypesSelfContainedTest) + +# Ensure the target is built as part of CppInterOpUnitTests (parallel +# to how add_cppinterop_unittest chains dependencies). +add_dependencies(CppInterOpUnitTests CppInterOpTypesSelfContainedTest) + +set_target_properties(CppInterOpTypesSelfContainedTest PROPERTIES + FOLDER "Tests") diff --git a/unittests/CppInterOp/TypesSelfContainedTest/TypesSelfContainedTest.cpp b/unittests/CppInterOp/TypesSelfContainedTest/TypesSelfContainedTest.cpp new file mode 100644 index 000000000..3bc01876c --- /dev/null +++ b/unittests/CppInterOp/TypesSelfContainedTest/TypesSelfContainedTest.cpp @@ -0,0 +1,67 @@ +// Regression guard: CppInterOpTypes.h must stay a fully self-contained +// header — every helper's implementation lives either inline in the +// header or in a plain-libc-shaped body. If a future contributor adds +// a helper backed by an out-of-line .cpp definition in +// libclangCppInterOp, this translation unit will fail to LINK +// (undefined symbol at ld time) because the executable deliberately +// does not link against libclangCppInterOp. +// +// Consumers reaching the CppInterOp API through Dispatch.h +// (cppyy-backend, xeus-cpp) do not link against libclangCppInterOp — +// they dlopen it lazily via LoadDispatchAPI. A CppInterOpTypes.h that +// referenced an out-of-line symbol from libclangCppInterOp would +// either force those consumers into a DT_NEEDED against the full +// library (the exact class of hazard we hit with +// Cpp::ResultAbort_UncheckedOnDtor) or require routing the helper +// through the dispatch table. +// +// This binary is not run — it's a compile-and-link probe. Success == +// build succeeded. Failure looks like: +// undefined reference to `some_new_helper_that_snuck_in' +// +// If you land such a symbol here, either (a) make it a static inline +// in the header, (b) route it through the dispatch table, or (c) split +// the ABI-helper prelude into a truly no-op runtime archive that +// downstream Dispatch consumers can link cheaply. +// +// Follow-up commits extend the probe body as new helpers land in +// CppInterOpTypes.h. Today only the pre-existing types are exercised: +// the opaque handles, TemplateArgInfo, and the CppInterOp{Array, +// StringArray} PODs. + +#include "CppInterOp/CppInterOpTypes.h" + +#include + +// Force ODR-use of every currently-provided CppInterOpTypes.h helper +// so the linker resolves each one. If any helper picks up an +// out-of-line dependency, this function fails to link. +extern "C" int cppinterop_types_self_contained_probe(int seed) { + // Handle types (Cpp::DeclRef etc.) — POD trivially copyable; a + // default-constructed handle compares equal to nullptr and yields + // false in a bool context. All inline, no external symbols. + Cpp::DeclRef d; + if (d) + return -1; + + // TemplateArgInfo — standard-layout POD with a #ifdef __cplusplus + // constructor. Instantiation touches the ctor without pulling any + // implementation symbol. + Cpp::TemplateArgInfo tai(nullptr); + (void)tai; + + // Array/StringArray helpers — pure PODs. No methods, no ctors that + // reach out-of-line code. + Cpp::CppInterOpArray arr = {nullptr, 0}; + Cpp::CppInterOpStringArray sarr = {nullptr, 0}; + return static_cast(seed + arr.size + sarr.size); +} + +int main(int argc, char** /*argv*/) { + // Volatile / argc mixing keeps the compiler from optimizing the probe + // call away in a Release build. The returned value is uninteresting; + // the load-bearing signal is that the link succeeded. + volatile int r = cppinterop_types_self_contained_probe(argc); + (void)r; + return 0; +}