diff --git a/.github/actions/Build_and_Test_CppInterOp/action.yml b/.github/actions/Build_and_Test_CppInterOp/action.yml index 8ba767875..5282e4657 100644 --- a/.github/actions/Build_and_Test_CppInterOp/action.yml +++ b/.github/actions/Build_and_Test_CppInterOp/action.yml @@ -28,10 +28,6 @@ inputs: required: false default: 'false' description: 'matrix.wasm. Gates emscripten cross-build vs native steps.' - arch: - required: false - default: '' - description: 'matrix.arch. "x86" targets 32-bit on Windows.' runs: using: composite @@ -266,17 +262,9 @@ runs: $env:CPPINTEROP_BUILD_DIR="$env:PWD_DIR" echo "CPPINTEROP_BUILD_DIR=$env:CPPINTEROP_BUILD_DIR" echo "CPPINTEROP_BUILD_DIR=$env:CPPINTEROP_BUILD_DIR" >> $env:GITHUB_ENV - # arch=x86: 32-bit target on the VS generator with the 64-bit- - # hosted toolset. The LLVM cell consumed above must carry - # matching Win32 .libs or the link fails with LNK4272. - $PLATFORM_FLAGS = @() - if ( "${{ inputs.arch }}" -eq "x86" ) - { - $PLATFORM_FLAGS = @("-A", "Win32", "-Thost=x64") - } if ( "${{ inputs.cling }}" -imatch "On" ) { - cmake @PLATFORM_FLAGS -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} ` + cmake -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} ` -DCPPINTEROP_USE_CLING=ON ` -DCPPINTEROP_USE_REPL=OFF ` -DCling_DIR="$env:CLING_CMAKE_DIR" ` @@ -286,7 +274,7 @@ runs: } else { - cmake @PLATFORM_FLAGS -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} ` + cmake -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} ` -DLLVM_DIR="$env:LLVM_BUILD_DIR\lib\cmake\llvm" ` -DLLVM_ENABLE_WERROR=On ` -DClang_DIR="$env:LLVM_BUILD_DIR\lib\cmake\clang" -DCODE_COVERAGE=${{ env.CODE_COVERAGE }} -DCMAKE_INSTALL_PREFIX="$env:CPPINTEROP_DIR" ..\ diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 11e300b08..abf48f4fd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,9 +26,8 @@ jobs: # flavor: '' (default — pull llvm-release from # ci-workflows' Releases cache) # arch is derived from the os slug inside setup-llvm; pass an - # explicit `arch` only when the os name doesn't carry the - # architecture, or when the build targets something other than - # the runner's own arch (e.g win32: 'x86' = 32-bit MSVC). + # explicit `flavor-arch` only on hosts whose os name doesn't + # carry the architecture (none of GHA's runner images today). # `flavor` selects the LLVM source per row: # '' latest LLVM (cells.yaml carries the cell) # system apt-llvm.org / brew llvm@N — for older majors @@ -61,16 +60,13 @@ jobs: - { name: osx26-x86-clang-llvm21-cppyy, os: macos-26-intel, compiler: clang, clang-runtime: '21', cppyy: On, flavor: system } - { name: osx26-x86-clang-cling-llvm20-cppyy, os: macos-26-intel, compiler: clang, clang-runtime: '20', cling: 'On', cppyy: On, flavor: cling, flavor-version: 'ROOT-llvm20' } # Windows - # No windows-11-arm row: x64 (win2025-msvc-llvm22) covers the + # No windows-11-arm row: x86 (win2025-msvc-llvm22) covers the # same MSVC+LLVM22 surface, and the arm runner ships an x64 # MSVC environment by default. ci-workflows can publish arm64 # .libs once `arch: arm64` lands in install-build-deps and the # poisoned arm64 cache entry is purged; restore the row then. - { name: win2025-msvc-llvm22, os: windows-2025, compiler: msvc, clang-runtime: '22' } - { name: win2025-msvc-cling-llvm20, os: windows-2025, compiler: msvc, clang-runtime: '20', cling: 'On', flavor: cling, flavor-version: 'ROOT-llvm20' } - # 32-bit MSVC target: consumes the Win32 llvm-release cell and - # builds CppInterOp with -A Win32 -Thost=x64 - - { name: win2025-msvc32-llvm22, os: windows-2025, compiler: msvc, clang-runtime: '22', arch: x86 } steps: &build_steps - uses: actions/checkout@v6 @@ -110,7 +106,6 @@ jobs: with: version: ${{ matrix.clang-runtime }} os: ${{ matrix.self-hosted-os || matrix.os }} - arch: ${{ matrix.arch }} flavor: ${{ matrix.flavor }} flavor-version: ${{ matrix.flavor-version }} @@ -133,7 +128,6 @@ jobs: documentation: ${{ matrix.documentation }} sanitizer: ${{ matrix.sanitizer }} valgrind: ${{ matrix.Valgrind }} - arch: ${{ matrix.arch }} - name: Prepare code coverage report if: ${{ success() && (matrix.coverage == true) }} diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp index 986e50ce2..1a20f2252 100644 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -1584,7 +1584,7 @@ bool IsFunctionProtoType(ConstTypeRef TyRef) { INTEROP_TRACE(TyRef); QualType QT = QualType::getFromOpaquePtr(TyRef.data); const auto* T = QT.getTypePtr(); - return INTEROP_RETURN(llvm::isa_and_nonnull(T)); + return llvm::isa_and_nonnull(T); } void GetFnTypeSignature(ConstTypeRef fn_type, std::vector& sig) { @@ -2133,38 +2133,6 @@ constexpr int kABIPrefixSize = 2; static_assert(detail::kVTableOverlayPrefixSize == kABIPrefixSize + 1, "public prefix size must equal ABI prefix + 1 hidden slot"); -// Vtable slots hold member functions: on 32-bit MSVC that means __thiscall, -// which a free function cannot be declared as (C3865). Use a member of a -// base-less class instead; `this` is the object being destroyed. MSVC's -// deleting dtor takes (this, int flags), Itanium's D0 only `this`. -struct VTableOverlayDtorHost { -#ifdef _WIN32 - void Wrapper(int flags); -#else - void Wrapper(); -#endif -}; -#ifdef _WIN32 -using VTableOverlayDtorSlotFn = void (VTableOverlayDtorHost::*)(int); -#else -using VTableOverlayDtorSlotFn = void (VTableOverlayDtorHost::*)(); -#endif - -// A non-virtual mfp of a base-less class keeps the entry point in its first -// pointer-sized word on every ABI we target (MSVC: just the address; -// Itanium: {fnptr, adj} with adj = 0). -static VTableOverlayDtorSlotFn SlotToDtorFn(void* slot) { - static_assert(sizeof(VTableOverlayDtorSlotFn) >= sizeof(void*)); - VTableOverlayDtorSlotFn fn{}; - std::memcpy(&fn, static_cast(&slot), sizeof(slot)); - return fn; -} -static void* DtorFnToSlot(VTableOverlayDtorSlotFn fn) { - void* slot; - std::memcpy(static_cast(&slot), &fn, sizeof(slot)); - return slot; -} - // Single locus for vptr reads/writes and slot arithmetic in this file; // the public header's VTableOverlayExtraSlot covers the symmetric pun // thunks need on the read side. The owned block layout is: @@ -2175,7 +2143,7 @@ static void* DtorFnToSlot(VTableOverlayDtorSlotFn fn) { // address_point // // The wrapper at the deleting-dtor slot recovers its VTableOverlay from -// the hidden self-ptr slot via a fixed-offset load from `this`'s vptr. +// the hidden self-ptr slot via a fixed-offset load from `self`'s vptr. struct VTableOverlay { void** block; // owned, freed in ~VTableOverlay void** original_vptr; // restored on caller-driven teardown @@ -2184,7 +2152,7 @@ struct VTableOverlay { bool dtor_fired = false; // wrapper started -- skip vptr restore // Dtor-hook fields. orig_dtor stays null when the caller passed // on_destroy = nullptr; the wrapper is then not installed at all. - VTableOverlayDtorSlotFn orig_dtor = nullptr; + void (*orig_dtor)(void*) = nullptr; VTableOverlayDtorHook cleanup = nullptr; void* cleanup_data = nullptr; @@ -2232,16 +2200,12 @@ struct VTableOverlay { // Wrapper installed in the deleting-dtor slot when MakeVTableOverlay was // called with a non-null on_destroy. Recovers the owning VTableOverlay -// from `this`'s vptr (hidden-slot fixed-offset load) and runs the -// callback BEFORE the original destructor: the object is alive at that +// from `self`'s vptr (hidden-slot fixed-offset load) and runs the +// callback BEFORE the original destructor: `self` is alive at that // point so the callback can inspect it, and after the original // deleting-destructor returns memory has been freed. -#ifdef _WIN32 -void VTableOverlayDtorHost::Wrapper(int flags) { -#else -void VTableOverlayDtorHost::Wrapper() { -#endif - void** vptr = VTableOverlay::ReadVPtr(this); +extern "C" void cppinteropVTableOverlayDtorWrapper(void* self) { + void** vptr = VTableOverlay::ReadVPtr(self); VTableOverlay* ov = *reinterpret_cast( vptr - detail::kVTableOverlayPrefixSize); // Snapshot orig_dtor before user code runs: a misbehaving callback @@ -2249,12 +2213,8 @@ void VTableOverlayDtorHost::Wrapper() { auto orig_dtor = ov->orig_dtor; ov->dtor_fired = true; if (ov->cleanup) - ov->cleanup(this, ov->cleanup_data); -#ifdef _WIN32 - (this->*orig_dtor)(flags); -#else - (this->*orig_dtor)(); -#endif + ov->cleanup(self, ov->cleanup_data); + orig_dtor(self); } // Minimum slot count a polymorphic class can have from its address point: @@ -2363,10 +2323,12 @@ MakeVTableOverlay(void* inst, ConstDeclRef base, const ConstFuncRef* methods, // destruction could fire the wrapper with stale state. if (on_destroy) { void** vptr = ov->address_point(); - ov->orig_dtor = SlotToDtorFn(vptr[kDeletingDtorSlot]); + ov->orig_dtor = + VTableOverlay::BitCastFn(vptr[kDeletingDtorSlot]); ov->cleanup = on_destroy; ov->cleanup_data = cleanup_data; - vptr[kDeletingDtorSlot] = DtorFnToSlot(&VTableOverlayDtorHost::Wrapper); + vptr[kDeletingDtorSlot] = + VTableOverlay::BitCastFn(&cppinteropVTableOverlayDtorWrapper); } return INTEROP_RETURN(ov); diff --git a/lib/CppInterOp/CppInterOpInterpreter.h b/lib/CppInterOp/CppInterOpInterpreter.h index 086770078..8c3a46418 100644 --- a/lib/CppInterOp/CppInterOpInterpreter.h +++ b/lib/CppInterOp/CppInterOpInterpreter.h @@ -41,11 +41,6 @@ #include #include #endif -#if defined(_WIN32) && (defined(_M_IX86) || defined(__i386__)) -#include "llvm/ExecutionEngine/Orc/AbsoluteSymbols.h" -#include "llvm/Support/DynamicLibrary.h" -#include -#endif #include #include #include @@ -149,66 +144,6 @@ inline clang::NamedDecl* Named(clang::Sema* S, const char* Name, namespace CppInternal { -#if defined(_WIN32) && (defined(_M_IX86) || defined(__i386__)) -/// COFF-i386 linker names carry decorations the ORC in-process resolver -/// never undoes: a '_' prefix on cdecl symbols, '@' suffixes on -/// stdcall ones, and '__imp_' pointer indirections for dllimport. ORC -/// strips a global prefix for MachO only, so JIT'd code referencing the -/// CRT (_memset, __CxxThrowException@8, ...) or libraries loaded at -/// runtime fails to materialize. Normalize the way cling's -/// platform::DLSym does and retry against the loaded modules. -class COFFi386SymbolGenerator : public llvm::orc::DefinitionGenerator { -public: - llvm::Error - tryToGenerate(llvm::orc::LookupState& LS, llvm::orc::LookupKind K, - llvm::orc::JITDylib& JD, - llvm::orc::JITDylibLookupFlags JDLookupFlags, - const llvm::orc::SymbolLookupSet& LookupSet) override { - llvm::orc::SymbolMap NewSymbols; - for (const auto& KV : LookupSet) { - llvm::StringRef Name = *KV.first; - // MSVC C++ manglings ('?...') take no i386 decoration; the default - // process generator already resolves them. - if (Name.empty() || Name.starts_with("?")) - continue; - if (void* Addr = lookupDecorated(Name)) - NewSymbols[KV.first] = {llvm::orc::ExecutorAddr::fromPtr(Addr), - llvm::JITSymbolFlags::Exported}; - } - if (NewSymbols.empty()) - return llvm::Error::success(); - return JD.define(llvm::orc::absoluteSymbols(std::move(NewSymbols))); - } - -private: - void* lookupDecorated(llvm::StringRef Name) { - bool Dllimport = Name.consume_front("__imp_"); - Name.consume_front("_"); // i386 cdecl prefix - std::string S = Name.str(); - void* Addr = llvm::sys::DynamicLibrary::SearchForAddressOfSymbol(S.c_str()); - if (!Addr) { - // stdcall '@' suffixes don't appear in export tables - // (vcruntime exports _CxxThrowException, not _CxxThrowException@8). - size_t At = S.rfind('@'); - if (At != std::string::npos && At + 1 < S.size() && - S.find_first_not_of("0123456789", At + 1) == std::string::npos) { - S.resize(At); - Addr = llvm::sys::DynamicLibrary::SearchForAddressOfSymbol(S.c_str()); - } - } - if (Addr && Dllimport) { - // dllimport references bind to a pointer to the symbol, not the - // symbol itself; park the address in a stable slot. - m_ImportSlots.push_back(Addr); - return &m_ImportSlots.back(); - } - return Addr; - } - - std::deque m_ImportSlots; -}; -#endif // _WIN32 && i386 - /// CppInterOp Interpreter /// class Interpreter { @@ -310,12 +245,6 @@ class Interpreter { return nullptr; } -#if defined(_WIN32) && (defined(_M_IX86) || defined(__i386__)) - // Forces executor creation, so install before any execution. - compat::getExecutionEngine(*CI)->getMainJITDylib().addGenerator( - std::make_unique()); -#endif - return std::make_unique(std::move(CI), std::move(io_ctx), outOfProcess); } diff --git a/unittests/CppInterOp/TracingTests.cpp b/unittests/CppInterOp/TracingTests.cpp index 116190154..e5be411da 100644 --- a/unittests/CppInterOp/TracingTests.cpp +++ b/unittests/CppInterOp/TracingTests.cpp @@ -1169,8 +1169,6 @@ TEST_F(TracingTest, ReproducerCompilesViaInterpreter) { auto FooType = Cpp::GetTypeFromScope(Foo); ASSERT_NE(FooType, nullptr); - EXPECT_FALSE(Cpp::IsFunctionProtoType(FooType)); - Cpp::GetName(Foo); Cpp::SizeOf(Foo); diff --git a/unittests/CppInterOp/VTableOverlayTest.cpp b/unittests/CppInterOp/VTableOverlayTest.cpp index 848ffdab9..a4a41ab0d 100644 --- a/unittests/CppInterOp/VTableOverlayTest.cpp +++ b/unittests/CppInterOp/VTableOverlayTest.cpp @@ -5,17 +5,30 @@ #include "gtest/gtest.h" #include -#include #include namespace { +// Replacement functions installed into vtable slots. The leading self +// parameter matches the implicit `this` of the virtual they replace. +extern "C" int repl_negate(void* /*self*/, int x) { return -x; } +extern "C" int repl_double(void* /*self*/, int x) { return x * 2; } + +// Replacement that reads the object via `this`: layout is { vptr, int value } +// on every ABI we target, so the data member sits at sizeof(void*). +extern "C" int repl_read_value(void* self, int x) { + int v = *reinterpret_cast(static_cast(self) + sizeof(void*)); + return v + x; +} + // Parallel test-TU definitions of A / B used by the OverlayThroughHierarchy // test. Layout and ABI come from the host compiler here; the same struct // shape is declared at interpreter level inside the test so reflection -// computes matching vtable slot indices. Virtual destructors keep the -// vtable layout in line with the other tests here (Itanium D1/D0 pair -// before user virtuals; MSVC single deleting-dtor). +// computes matching vtable slot indices. foo / bar access the object's +// data members via typed static_cast on the self pointer the vtable slot +// hands them -- exactly the pattern a language binding's adapter writes. +// Virtual destructors keep the vtable layout in line with the other tests +// here (Itanium D1/D0 pair before user virtuals; MSVC single deleting-dtor). struct A { int m_x; A(int x) : m_x(x) {} @@ -29,45 +42,17 @@ struct B : A { int method() override { return static_cast(m_d); } }; -// Replacement functions installed into vtable slots. Non-static on purpose -// (vtable slots take __thiscall on 32-bit MSVC); the casts alias the real -// object's layout through an unrelated `this` -- the point of the test. -// NOLINTBEGIN(readability-convert-member-functions-to-static, cppcoreguidelines-pro-type-reinterpret-cast) -struct Repl { - int negate(int x) { return -x; } - int twice(int x) { return x * 2; } - // Reads the object via `this`: layout is { vptr, int value } on every - // ABI we target, so the data member sits at sizeof(void*). - int read_value(int x) { - return *reinterpret_cast(reinterpret_cast(this) + - sizeof(void*)) + - x; - } - int foo() { return reinterpret_cast(this)->m_x + 10; } - int bar() { - B* b = reinterpret_cast(this); - return static_cast(b->m_x + b->m_d); - } -}; -// NOLINTEND(readability-convert-member-functions-to-static, cppcoreguidelines-pro-type-reinterpret-cast) - -template void* MethodAddr(MFP mfp) { - static_assert(sizeof(MFP) >= sizeof(void*), "unexpected MFP layout"); - void* addr; - std::memcpy(static_cast(&addr), &mfp, sizeof(addr)); - return addr; +extern "C" int foo(void* self) { + return static_cast(self)->m_x + 10; +} +extern "C" int bar(void* self) { + return static_cast(static_cast(self)->m_x + + static_cast(self)->m_d); } - -#if defined(_MSC_VER) && defined(_M_IX86) -# define CPPINTEROP_VTABLE_SLOT_CC __thiscall -#else -# define CPPINTEROP_VTABLE_SLOT_CC -#endif int call_slot_no_arg(void* inst, int slot) { void** vptr = *reinterpret_cast(inst); - return TestUtils::BitCastFn( - vptr[slot])(inst); + return TestUtils::BitCastFn(vptr[slot])(inst); } // OverlayB declared through the interpreter so the overlay runs against @@ -100,8 +85,7 @@ Cpp::FuncRef Method(Cpp::DeclRef scope, const char* name) { // reading the slot tests what the overlay actually installs. int call_slot(void* inst, int slot, int arg) { void** vptr = *reinterpret_cast(inst); - return TestUtils::BitCastFn( - vptr[slot])(inst, arg); + return TestUtils::BitCastFn(vptr[slot])(inst, arg); } #ifdef _WIN32 @@ -119,7 +103,7 @@ TEST(VTableOverlay, ReplacesSlotPreservingOthers) { void* inst = Cpp::Construct(B).data; ASSERT_NE(inst, nullptr); auto ov = Cpp::MakeUniqueVTableOverlay( - inst, B, {{Method(B, "beta"), MethodAddr(&Repl::negate)}}); + inst, B, {{Method(B, "beta"), TestUtils::BitCastFn(&repl_negate)}}); ASSERT_TRUE(ov); EXPECT_EQ(call_slot(inst, kBeta, 5), -5); // overlaid EXPECT_EQ(call_slot(inst, kAlpha, 5), 15); // preserved: alpha -> x+10 @@ -141,7 +125,7 @@ TEST(VTableOverlay, PreservesPrefixAndUnrelatedSlots) { #endif void* alpha_slot = aot[kAlpha]; auto ov = Cpp::MakeUniqueVTableOverlay( - inst, B, {{Method(B, "beta"), MethodAddr(&Repl::negate)}}); + inst, B, {{Method(B, "beta"), TestUtils::BitCastFn(&repl_negate)}}); ASSERT_TRUE(ov); void** now = *reinterpret_cast(inst); EXPECT_EQ(now[-1], prefix_m1); @@ -160,7 +144,7 @@ TEST(VTableOverlay, RestoresOnDestroy) { void* aot = *reinterpret_cast(inst); { auto ov = Cpp::MakeUniqueVTableOverlay( - inst, B, {{Method(B, "beta"), MethodAddr(&Repl::negate)}}); + inst, B, {{Method(B, "beta"), TestUtils::BitCastFn(&repl_negate)}}); ASSERT_TRUE(ov); EXPECT_NE(*reinterpret_cast(inst), aot); } @@ -175,8 +159,8 @@ TEST(VTableOverlay, ReplacesMultipleSlots) { ASSERT_NE(inst, nullptr); auto ov = Cpp::MakeUniqueVTableOverlay( inst, B, - {{Method(B, "alpha"), MethodAddr(&Repl::negate)}, - {Method(B, "beta"), MethodAddr(&Repl::twice)}}); + {{Method(B, "alpha"), TestUtils::BitCastFn(&repl_negate)}, + {Method(B, "beta"), TestUtils::BitCastFn(&repl_double)}}); ASSERT_TRUE(ov); EXPECT_EQ(call_slot(inst, kAlpha, 5), -5); EXPECT_EQ(call_slot(inst, kBeta, 5), 10); @@ -190,7 +174,7 @@ TEST(VTableOverlay, RejectsInvalidInput) { ASSERT_NE(inst, nullptr); Cpp::ConstFuncRef beta = Method(B, "beta"); Cpp::ConstFuncRef none = nullptr; - void* fn = MethodAddr(&Repl::negate); + void* fn = TestUtils::BitCastFn(&repl_negate); EXPECT_EQ(Cpp::MakeVTableOverlay(nullptr, B, &beta, &fn, 1), nullptr); // inst EXPECT_EQ(Cpp::MakeVTableOverlay(inst, nullptr, &beta, &fn, 1), @@ -210,7 +194,7 @@ TEST(VTableOverlay, OverlayIsPerInstance) { void* b_vptr_before = *reinterpret_cast(b); auto ov = Cpp::MakeUniqueVTableOverlay( - a, B, {{Method(B, "beta"), MethodAddr(&Repl::negate)}}); + a, B, {{Method(B, "beta"), TestUtils::BitCastFn(&repl_negate)}}); ASSERT_TRUE(ov); EXPECT_NE(*reinterpret_cast(a), b_vptr_before); // a swapped @@ -240,7 +224,7 @@ TEST(VTableOverlay, ThunkReadsThisAndDataMember) { ASSERT_NE(inst, nullptr); auto ov = Cpp::MakeUniqueVTableOverlay( - inst, T, {{Method(T, "frob"), MethodAddr(&Repl::read_value)}}); + inst, T, {{Method(T, "frob"), TestUtils::BitCastFn(&repl_read_value)}}); ASSERT_TRUE(ov); // First user virtual lives at kAlpha (Itanium D1/D0 prefix; MSVC single @@ -270,7 +254,7 @@ TEST(VTableOverlay, DerivedClassWithOverride) { EXPECT_EQ(call_slot(inst, kAlpha, 5), 7); auto ov = Cpp::MakeUniqueVTableOverlay( - inst, D, {{Method(D, "frob"), MethodAddr(&Repl::negate)}}); + inst, D, {{Method(D, "frob"), TestUtils::BitCastFn(&repl_negate)}}); ASSERT_TRUE(ov); EXPECT_EQ(call_slot(inst, kAlpha, 5), -5); ov.reset(); @@ -292,7 +276,7 @@ TEST(VTableOverlay, MultiLevelInheritance) { EXPECT_EQ(call_slot(inst, kAlpha, 5), 8); // MlC::frob: x+3 auto ov = Cpp::MakeUniqueVTableOverlay( - inst, C, {{Method(C, "frob"), MethodAddr(&Repl::twice)}}); + inst, C, {{Method(C, "frob"), TestUtils::BitCastFn(&repl_double)}}); ASSERT_TRUE(ov); EXPECT_EQ(call_slot(inst, kAlpha, 5), 10); // overlay: x*2 ov.reset(); @@ -318,7 +302,7 @@ TEST(VTableOverlay, RejectsMultipleInheritance) { ASSERT_NE(inst, nullptr); auto ov = Cpp::MakeUniqueVTableOverlay( - inst, C, {{Method(C, "af"), MethodAddr(&Repl::negate)}}); + inst, C, {{Method(C, "af"), TestUtils::BitCastFn(&repl_negate)}}); EXPECT_FALSE(ov); // refuses the layout Cpp::Destruct(inst, C); @@ -339,7 +323,7 @@ TEST(VTableOverlay, RejectsNonPolymorphicBase) { void* inst = Cpp::Construct(NP).data; ASSERT_NE(inst, nullptr); Cpp::ConstFuncRef dummy = Method(PH, "dummy"); - void* fn = MethodAddr(&Repl::negate); + void* fn = TestUtils::BitCastFn(&repl_negate); EXPECT_EQ(Cpp::MakeVTableOverlay(inst, NP, &dummy, &fn, 1), nullptr); Cpp::Destruct(inst, NP); } @@ -373,7 +357,7 @@ TEST(VTableOverlay, RejectsOutOfRangeMethodSlot) { void* inst = Cpp::Construct(Small).data; ASSERT_NE(inst, nullptr); Cpp::ConstFuncRef v10 = Method(Large, "v10"); - void* fn = MethodAddr(&Repl::negate); + void* fn = TestUtils::BitCastFn(&repl_negate); EXPECT_EQ(Cpp::MakeVTableOverlay(inst, Small, &v10, &fn, 1), nullptr); Cpp::Destruct(inst, Small); } @@ -411,11 +395,11 @@ TEST(VTableOverlay, OverlayThroughHierarchyAccessesDataMembers) { auto ov_a = Cpp::MakeUniqueVTableOverlay( &a, A_scope, - {{Method(A_scope, "method"), MethodAddr(&Repl::foo)}}); + {{Method(A_scope, "method"), TestUtils::BitCastFn(&foo)}}); ASSERT_TRUE(ov_a); auto ov_b = Cpp::MakeUniqueVTableOverlay( &b, B_scope, - {{Method(B_scope, "method"), MethodAddr(&Repl::bar)}}); + {{Method(B_scope, "method"), TestUtils::BitCastFn(&bar)}}); ASSERT_TRUE(ov_b); EXPECT_EQ(call_slot_no_arg(&a, kAlpha), 15); // foo: A::m_x(5) + 10 @@ -447,7 +431,7 @@ TEST(VTableOverlay, RejectsVirtualInheritance) { ASSERT_NE(inst, nullptr); auto ov = Cpp::MakeUniqueVTableOverlay( - inst, D, {{Method(D, "frob"), MethodAddr(&Repl::negate)}}); + inst, D, {{Method(D, "frob"), TestUtils::BitCastFn(&repl_negate)}}); EXPECT_FALSE(ov); // refuses the layout Cpp::Destruct(inst, D); @@ -592,7 +576,7 @@ TEST(VTableOverlay, SetsExtraPrefixSlots) { void* inst = Cpp::Construct(B).data; ASSERT_NE(inst, nullptr); auto ov = Cpp::MakeUniqueVTableOverlay( - inst, B, {{Method(B, "beta"), MethodAddr(&Repl::negate)}}, + inst, B, {{Method(B, "beta"), TestUtils::BitCastFn(&repl_negate)}}, /*n_extra_prefix_slots=*/2); ASSERT_TRUE(ov); void*& slot0 = Cpp::VTableOverlayExtraSlot(inst, 0);