diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index d68768c32..ba65ae1e9 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -57,6 +57,16 @@ if(WIN32) endif() if(BUILD_TESTING) + add_test_executable(guest_array_callbacks_tests ${LIBS_INCLUDE_DIR}/tests/GuestArrayCallbacks.cpp) + target_include_directories(guest_array_callbacks_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_array_callbacks_tests PRIVATE libc) + configure_windows_unwind(guest_array_callbacks_tests) + add_test(NAME guest_array_callbacks COMMAND guest_array_callbacks_tests) + set_tests_properties(guest_array_callbacks PROPERTIES TIMEOUT 10) + if(WIN32) + set_tests_properties(guest_array_callbacks PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_time_conversion_tests ${LIBS_INCLUDE_DIR}/tests/GuestTimeConversion.cpp) target_include_directories(guest_time_conversion_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_time_conversion_tests PRIVATE libc) diff --git a/core/libs/prx/libc/src/exception/ArraySupport.cpp b/core/libs/prx/libc/src/exception/ArraySupport.cpp index 8132ade49..6b5302ac9 100644 --- a/core/libs/prx/libc/src/exception/ArraySupport.cpp +++ b/core/libs/prx/libc/src/exception/ArraySupport.cpp @@ -2,13 +2,13 @@ #include extern "C" { -void APS5_VABI __cxa_vec_cleanup_nid_postfix(void* array, std::size_t count, std::size_t size, void (*destroy)(void*)) { +void APS5_VABI __cxa_vec_cleanup_nid_postfix(void* array, std::size_t count, std::size_t size, void (APS5_VABI *destroy)(void*)) { if (!destroy) return; try { while (count) destroy(static_cast(array) + --count * size); } catch (...) { LibcException::Terminate(); } } -void APS5_VABI __cxa_vec_ctor_nid_postfix(void* array, std::size_t count, std::size_t size, void (*construct)(void*), void (*destroy)(void*)) { +void APS5_VABI __cxa_vec_ctor_nid_postfix(void* array, std::size_t count, std::size_t size, void (APS5_VABI *construct)(void*), void (APS5_VABI *destroy)(void*)) { if (!construct) return; std::size_t complete = 0; try { @@ -18,7 +18,7 @@ void APS5_VABI __cxa_vec_ctor_nid_postfix(void* array, std::size_t count, std::s throw; } } -void APS5_VABI __cxa_vec_cctor_nid_postfix(void* destination, void* source, std::size_t count, std::size_t size, void (*copy)(void*, void*), void (*destroy)(void*)) { +void APS5_VABI __cxa_vec_cctor_nid_postfix(void* destination, void* source, std::size_t count, std::size_t size, void (APS5_VABI *copy)(void*, void*), void (APS5_VABI *destroy)(void*)) { if (!copy) return; std::size_t complete = 0; try { @@ -29,7 +29,7 @@ void APS5_VABI __cxa_vec_cctor_nid_postfix(void* destination, void* source, std: throw; } } -void APS5_VABI __cxa_vec_dtor_nid_postfix(void* array, std::size_t count, std::size_t size, void (*destroy)(void*)) { +void APS5_VABI __cxa_vec_dtor_nid_postfix(void* array, std::size_t count, std::size_t size, void (APS5_VABI *destroy)(void*)) { if (!destroy) return; bool unwinding = LibcException::globals.uncaught != 0; try { @@ -53,8 +53,8 @@ void* ArrayAllocate(std::size_t size) { if (!result) _ZSt11_Xbad_allocv_nid_postfix(); return result; } -template -void* NewArray(std::size_t count, std::size_t size, std::size_t padding, void (*construct)(void*), void (*destroy)(void*), void* (*allocate)(std::size_t), Deallocate deallocate) { +template +void* NewArray(std::size_t count, std::size_t size, std::size_t padding, void (APS5_VABI *construct)(void*), void (APS5_VABI *destroy)(void*), TAllocate allocate, TDeallocate deallocate) { std::size_t total = ArraySize(count, size, padding); auto* storage = static_cast(allocate(total)); if (!storage) return nullptr; @@ -64,8 +64,8 @@ void* NewArray(std::size_t count, std::size_t size, std::size_t padding, void (* catch (...) { deallocate(storage, total); throw; } return array; } -template -void DeleteArray(void* array, std::size_t size, std::size_t padding, void (*destroy)(void*), Deallocate deallocate) { +template +void DeleteArray(void* array, std::size_t size, std::size_t padding, void (APS5_VABI *destroy)(void*), TDeallocate deallocate) { if (!array) return; auto* storage = static_cast(array) - padding; std::size_t count = 0; @@ -81,22 +81,22 @@ void DeleteArray(void* array, std::size_t size, std::size_t padding, void (*dest } extern "C" { -void* APS5_VABI __cxa_vec_new_nid_postfix(std::size_t n, std::size_t s, std::size_t p, void (*ctor)(void*), void (*dtor)(void*)) { +void* APS5_VABI __cxa_vec_new_nid_postfix(std::size_t n, std::size_t s, std::size_t p, void (APS5_VABI *ctor)(void*), void (APS5_VABI *dtor)(void*)) { return LibcException::NewArray(n, s, p, ctor, dtor, LibcException::ArrayAllocate, [](void* ptr, std::size_t) { std::free(ptr); }); } -void* APS5_VABI __cxa_vec_new2_nid_postfix(std::size_t n, std::size_t s, std::size_t p, void (*ctor)(void*), void (*dtor)(void*), void* (*alloc)(std::size_t), void (*dealloc)(void*)) { +void* APS5_VABI __cxa_vec_new2_nid_postfix(std::size_t n, std::size_t s, std::size_t p, void (APS5_VABI *ctor)(void*), void (APS5_VABI *dtor)(void*), void* (APS5_VABI *alloc)(std::size_t), void (APS5_VABI *dealloc)(void*)) { return LibcException::NewArray(n, s, p, ctor, dtor, alloc, [dealloc](void* ptr, std::size_t) { dealloc(ptr); }); } -void* APS5_VABI __cxa_vec_new3_nid_postfix(std::size_t n, std::size_t s, std::size_t p, void (*ctor)(void*), void (*dtor)(void*), void* (*alloc)(std::size_t), void (*dealloc)(void*, std::size_t)) { +void* APS5_VABI __cxa_vec_new3_nid_postfix(std::size_t n, std::size_t s, std::size_t p, void (APS5_VABI *ctor)(void*), void (APS5_VABI *dtor)(void*), void* (APS5_VABI *alloc)(std::size_t), void (APS5_VABI *dealloc)(void*, std::size_t)) { return LibcException::NewArray(n, s, p, ctor, dtor, alloc, dealloc); } -void APS5_VABI __cxa_vec_delete_nid_postfix(void* array, std::size_t s, std::size_t p, void (*dtor)(void*)) { +void APS5_VABI __cxa_vec_delete_nid_postfix(void* array, std::size_t s, std::size_t p, void (APS5_VABI *dtor)(void*)) { LibcException::DeleteArray(array, s, p, dtor, [](void* ptr, std::size_t) { std::free(ptr); }); } -void APS5_VABI __cxa_vec_delete2_nid_postfix(void* array, std::size_t s, std::size_t p, void (*dtor)(void*), void (*dealloc)(void*)) { +void APS5_VABI __cxa_vec_delete2_nid_postfix(void* array, std::size_t s, std::size_t p, void (APS5_VABI *dtor)(void*), void (APS5_VABI *dealloc)(void*)) { LibcException::DeleteArray(array, s, p, dtor, [dealloc](void* ptr, std::size_t) { dealloc(ptr); }); } -void APS5_VABI __cxa_vec_delete3_nid_postfix(void* array, std::size_t s, std::size_t p, void (*dtor)(void*), void (*dealloc)(void*, std::size_t)) { +void APS5_VABI __cxa_vec_delete3_nid_postfix(void* array, std::size_t s, std::size_t p, void (APS5_VABI *dtor)(void*), void (APS5_VABI *dealloc)(void*, std::size_t)) { LibcException::DeleteArray(array, s, p, dtor, dealloc); } } diff --git a/core/libs/tests/GuestArrayCallbacks.cpp b/core/libs/tests/GuestArrayCallbacks.cpp new file mode 100644 index 000000000..92bfdfdef --- /dev/null +++ b/core/libs/tests/GuestArrayCallbacks.cpp @@ -0,0 +1,201 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +using ElementCallback = void (APS5_VABI *)(void*); +using CopyCallback = void (APS5_VABI *)(void*, void*); +using AllocateCallback = void* (APS5_VABI *)(std::size_t); +using FreeCallback = void (APS5_VABI *)(void*); +using SizedFreeCallback = void (APS5_VABI *)(void*, std::size_t); + +extern "C" { +void APS5_VABI __cxa_vec_ctor_nid_postfix(void*, std::size_t, std::size_t, ElementCallback, ElementCallback); +void APS5_VABI __cxa_vec_cctor_nid_postfix(void*, void*, std::size_t, std::size_t, CopyCallback, ElementCallback); +void APS5_VABI __cxa_vec_dtor_nid_postfix(void*, std::size_t, std::size_t, ElementCallback); +void APS5_VABI __cxa_vec_cleanup_nid_postfix(void*, std::size_t, std::size_t, ElementCallback); +void* APS5_VABI __cxa_vec_new_nid_postfix(std::size_t, std::size_t, std::size_t, ElementCallback, ElementCallback); +void* APS5_VABI __cxa_vec_new2_nid_postfix(std::size_t, std::size_t, std::size_t, ElementCallback, ElementCallback, AllocateCallback, FreeCallback); +void* APS5_VABI __cxa_vec_new3_nid_postfix(std::size_t, std::size_t, std::size_t, ElementCallback, ElementCallback, AllocateCallback, SizedFreeCallback); +void APS5_VABI __cxa_vec_delete_nid_postfix(void*, std::size_t, std::size_t, ElementCallback); +void APS5_VABI __cxa_vec_delete2_nid_postfix(void*, std::size_t, std::size_t, ElementCallback, FreeCallback); +void APS5_VABI __cxa_vec_delete3_nid_postfix(void*, std::size_t, std::size_t, ElementCallback, SizedFreeCallback); +} + +namespace { + +constexpr std::size_t Count = 4; +constexpr std::size_t ElementBytes = 16; +constexpr std::size_t Padding = 16; +constexpr std::size_t TotalBytes = Count * ElementBytes + Padding; +alignas(16) std::array storage{}; +alignas(16) std::array source{}; +std::byte* destination = nullptr; +std::size_t constructed = 0; +std::size_t copied = 0; +std::size_t destroyed = 0; +std::size_t allocated = 0; +std::size_t freed = 0; +bool failAllocation = false; +bool failConstruct = false; +bool failCopy = false; +bool failDestroy = false; + +void require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +void reset(std::byte* first) { + destination = first; + constructed = copied = destroyed = allocated = freed = 0; + failAllocation = false; + failConstruct = failCopy = failDestroy = false; +} + +void APS5_VABI construct(void* pointer) { + require(constructed < Count && pointer == destination + constructed * ElementBytes, "constructor received the wrong element"); + if (failConstruct && constructed == 2) throw 73; + std::memset(pointer, static_cast(++constructed), ElementBytes); +} + +void APS5_VABI copy(void* target, void* original) { + require(copied < Count && target == destination + copied * ElementBytes, "copy constructor received the wrong destination"); + require(original == source.data() + copied * ElementBytes, "copy constructor received the wrong source"); + if (failCopy && copied == 2) throw 73; + std::memcpy(target, original, ElementBytes); + ++copied; +} + +void APS5_VABI destroy(void* pointer) { + const auto count = copied != 0 ? copied : constructed; + require(destroyed < count && pointer == destination + (count - destroyed - 1) * ElementBytes, "destructor received the wrong element or order"); + require(*static_cast(pointer) == std::byte(count - destroyed), "destructor received the wrong element contents"); + ++destroyed; + if (failDestroy && destroyed == 1) throw 73; +} + +void* APS5_VABI allocate(std::size_t bytes) { + require(bytes == TotalBytes, "allocator received the wrong size"); + ++allocated; + return failAllocation ? nullptr : storage.data(); +} + +void APS5_VABI release(void* pointer) { + require(pointer == storage.data(), "deallocator received the wrong storage"); + require(destroyed == constructed, "deallocator ran before element destruction finished"); + ++freed; +} + +void APS5_VABI releaseSized(void* pointer, std::size_t bytes) { + require(bytes == TotalBytes, "sized deallocator received the wrong size"); + release(pointer); +} + +void checkCookie(const void* array) { + std::size_t count = 0; + std::memcpy(&count, static_cast(array) - sizeof(count), sizeof(count)); + require(count == Count, "array cookie has the wrong count"); +} + +void checkElements() { + reset(source.data()); + __cxa_vec_ctor_nid_postfix(source.data(), Count, ElementBytes, construct, destroy); + require(constructed == Count, "constructor count is wrong"); + __cxa_vec_dtor_nid_postfix(source.data(), Count, ElementBytes, destroy); + require(destroyed == Count, "destructor count is wrong"); + reset(storage.data()); + __cxa_vec_cctor_nid_postfix(storage.data(), source.data(), Count, ElementBytes, copy, destroy); + require(copied == Count && std::memcmp(storage.data(), source.data(), source.size()) == 0, "copy construction failed"); + __cxa_vec_cleanup_nid_postfix(storage.data(), Count, ElementBytes, destroy); + require(destroyed == Count, "cleanup count is wrong"); + __cxa_vec_ctor_nid_postfix(nullptr, Count, ElementBytes, nullptr, nullptr); + __cxa_vec_cctor_nid_postfix(nullptr, nullptr, Count, ElementBytes, nullptr, nullptr); + __cxa_vec_dtor_nid_postfix(nullptr, Count, ElementBytes, nullptr); + __cxa_vec_cleanup_nid_postfix(nullptr, Count, ElementBytes, nullptr); +} + +void checkAllocation(bool sized) { + reset(storage.data() + Padding); + void* array = sized + ? __cxa_vec_new3_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, releaseSized) + : __cxa_vec_new2_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, release); + require(array == destination && constructed == Count && allocated == 1, "array allocation failed"); + checkCookie(array); + if (sized) __cxa_vec_delete3_nid_postfix(array, ElementBytes, Padding, destroy, releaseSized); + else __cxa_vec_delete2_nid_postfix(array, ElementBytes, Padding, destroy, release); + require(destroyed == Count && freed == 1, "array deletion failed"); + reset(storage.data() + Padding); + failAllocation = true; + array = sized + ? __cxa_vec_new3_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, releaseSized) + : __cxa_vec_new2_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, release); + require(array == nullptr && allocated == 1 && constructed == 0 && freed == 0, "failed allocation invoked element callbacks"); + if (sized) __cxa_vec_delete3_nid_postfix(nullptr, ElementBytes, Padding, destroy, releaseSized); + else __cxa_vec_delete2_nid_postfix(nullptr, ElementBytes, Padding, destroy, release); + require(freed == 0, "null array invoked deallocation"); +} + +void checkDefaultAllocation() { + void* array = __cxa_vec_new_nid_postfix(Count, ElementBytes, Padding, nullptr, nullptr); + require(array != nullptr, "default array allocation failed"); + checkCookie(array); + reset(static_cast(array)); + __cxa_vec_ctor_nid_postfix(array, Count, ElementBytes, construct, destroy); + __cxa_vec_delete_nid_postfix(array, ElementBytes, Padding, destroy); + require(constructed == Count && destroyed == Count, "default array deletion failed"); + __cxa_vec_delete_nid_postfix(nullptr, ElementBytes, Padding, destroy); +} + +template +void expectException(TAction action) { + bool caught = false; + try { action(); } + catch (int value) { require(value == 73, "callback exception changed"); caught = true; } + require(caught, "callback exception was swallowed"); +} + +void checkRollback(bool sized) { + reset(storage.data() + Padding); + failConstruct = true; + expectException([&] { + if (sized) __cxa_vec_new3_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, releaseSized); + else __cxa_vec_new2_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, release); + }); + require(constructed == 2 && destroyed == 2 && allocated == 1 && freed == 1, "constructor rollback failed"); + reset(storage.data() + Padding); + void* array = sized + ? __cxa_vec_new3_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, releaseSized) + : __cxa_vec_new2_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, release); + failDestroy = true; + expectException([&] { + if (sized) __cxa_vec_delete3_nid_postfix(array, ElementBytes, Padding, destroy, releaseSized); + else __cxa_vec_delete2_nid_postfix(array, ElementBytes, Padding, destroy, release); + }); + require(constructed == Count && destroyed == Count && allocated == 1 && freed == 1, "destructor rollback failed"); +} + +void checkCopyRollback() { + reset(storage.data()); + failCopy = true; + expectException([] { + __cxa_vec_cctor_nid_postfix(storage.data(), source.data(), Count, ElementBytes, copy, destroy); + }); + require(copied == 2 && destroyed == 2, "copy constructor rollback failed"); +} + +} + +int main() { + checkElements(); + checkAllocation(false); + checkAllocation(true); + checkDefaultAllocation(); + checkRollback(false); + checkRollback(true); + checkCopyRollback(); + std::puts("guest array callback arguments and lifecycle: PASS"); +}