From baa8a8d9d30ced9639c3b6a8a46e798267fe9c85 Mon Sep 17 00:00:00 2001 From: boykopovar Date: Sun, 6 Sep 2026 07:30:59 +0300 Subject: [PATCH] feat(libc): add ex tests + remove bad_alloc dup --- core/libs/prx/libc/src/FileAndHeap.cpp | 9 +- core/libs/tests/ExceptionRuntime.cpp | 210 +++++++++++++++++++++++++ core/libs/tests/run_exception_tests.py | 68 ++++++++ 3 files changed, 283 insertions(+), 4 deletions(-) create mode 100644 core/libs/tests/ExceptionRuntime.cpp create mode 100644 core/libs/tests/run_exception_tests.py diff --git a/core/libs/prx/libc/src/FileAndHeap.cpp b/core/libs/prx/libc/src/FileAndHeap.cpp index 25c65099d..7966b4f7c 100644 --- a/core/libs/prx/libc/src/FileAndHeap.cpp +++ b/core/libs/prx/libc/src/FileAndHeap.cpp @@ -2,10 +2,11 @@ #include #include #include -#include extern "C" { +[[noreturn]] void _ZSt11_Xbad_allocv_nid_postfix(); + FILE* fopen_nid_postfix(const char* filename, const char* mode) { return std::fopen(filename, mode); } @@ -44,7 +45,7 @@ void* malloc_nid_postfix(size_t size) { const size_t headerSize = sizeof(std::uintptr_t); void* rawPtr = std::malloc(size + headerSize); if (rawPtr == nullptr) { - throw std::bad_alloc(); + _ZSt11_Xbad_allocv_nid_postfix(); } *reinterpret_cast(rawPtr) = 0; return static_cast(rawPtr) + headerSize; @@ -70,7 +71,7 @@ void* realloc_nid_postfix(void* ptr, size_t newSize) { void* rawPtr = static_cast(ptr) - headerSize; void* newRawPtr = std::realloc(rawPtr, newSize + headerSize); if (newRawPtr == nullptr) { - throw std::bad_alloc(); + _ZSt11_Xbad_allocv_nid_postfix(); } return static_cast(newRawPtr) + headerSize; } @@ -80,7 +81,7 @@ void* memalign_nid_postfix(size_t alignment, size_t size) { const size_t worstCaseSize = size + alignment + headerSize; void* rawPtr = std::malloc(worstCaseSize); if (rawPtr == nullptr) { - throw std::bad_alloc(); + _ZSt11_Xbad_allocv_nid_postfix(); } const std::uintptr_t rawAddress = reinterpret_cast(rawPtr) + headerSize; const std::uintptr_t alignedAddress = (rawAddress + alignment - 1) & ~(alignment - 1); diff --git a/core/libs/tests/ExceptionRuntime.cpp b/core/libs/tests/ExceptionRuntime.cpp new file mode 100644 index 000000000..0c40d8fb3 --- /dev/null +++ b/core/libs/tests/ExceptionRuntime.cpp @@ -0,0 +1,210 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" unsigned __cxa_uncaught_exceptions_nid_postfix(); +extern "C" void* __cxa_current_primary_exception_nid_postfix(); +extern "C" void __cxa_decrement_exception_refcount_nid_postfix(void*); +extern "C" void __cxa_rethrow_primary_exception_nid_postfix(void*); + +extern "C" [[noreturn]] void _ZSt14_Xout_of_rangePKc_nid_postfix(const char*); +extern "C" [[noreturn]] void __cxa_bad_cast_nid_postfix(); +extern "C" void* __cxa_vec_new3_nid_postfix(std::size_t, std::size_t, std::size_t, void(*)(void*), void(*)(void*), void*(*)(std::size_t), void(*)(void*, std::size_t)); +extern "C" void __cxa_vec_delete3_nid_postfix(void*, std::size_t, std::size_t, void(*)(void*), void(*)(void*, std::size_t)); +extern "C" _Unwind_Reason_Code _Unwind_Backtrace_nid_postfix(_Unwind_Trace_Fn, void*); +extern "C" _Unwind_Reason_Code _Unwind_ForcedUnwind_nid_postfix(_Unwind_Exception*, _Unwind_Stop_Fn, void*); +extern "C" std::uintptr_t _Unwind_GetIP_nid_postfix(_Unwind_Context*); +extern "C" void (*_ZSt13set_terminatePFvvE_nid_postfix(void(*)()))(); + +thread_local int destroyed = 0; +struct Guard { + ~Guard() { assert(__cxa_uncaught_exceptions_nid_postfix() > 0); ++destroyed; } +}; +struct Base { virtual ~Base() = default; int value = 7; }; +struct Other { virtual ~Other() = default; int padding = 9; }; +struct Derived : Other, Base {}; +struct Virtual : virtual Base {}; + +__attribute__((noinline)) void ThrowInt() { Guard guard; throw 42; } +__attribute__((noinline)) void ThrowClass() { Guard guard; throw Derived(); } + +struct Left : Base {}; +struct Right : Base {}; +struct Repeated : Left, Right {}; +struct LeftVirtual : virtual Base {}; +struct RightVirtual : virtual Base {}; +struct Diamond : LeftVirtual, RightVirtual {}; +struct PrivateDerived : private Derived { + Base* source() { return this; } + Derived* target() { return this; } +}; +__attribute__((noinline)) Derived* ToDerived(Base* p) { return dynamic_cast(p); } +__attribute__((noinline)) Repeated* ToRepeated(Base* p) { return dynamic_cast(p); } +void CheckRtti() { + Repeated repeated; + Base* left = static_cast(&repeated); + assert(ToRepeated(left) == &repeated); + assert(dynamic_cast(left) == static_cast(&repeated)); + PrivateDerived hidden; + assert(ToDerived(hidden.source()) == hidden.target()); + try { throw static_cast(nullptr); } + catch (Base*) { assert(false); } + catch (Repeated* p) { assert(p == nullptr); } + try { throw static_cast(nullptr); } + catch (Base* p) { assert(p == nullptr); } + try { throw nullptr; } + catch (Base* p) { assert(p == nullptr); } + int value = 1; int* pointer = &value; + try { throw &pointer; } + catch (const int**) { assert(false); } + catch (int** p) { assert(p == &pointer); } + try { throw &pointer; } + catch (const int* const* p) { assert(**p == value); } +} +int foreignDeleted; +void CheckForeign() { + auto* exception = new _Unwind_Exception {}; + exception->exception_class = 0x54455354464f5200; + exception->exception_cleanup = [](_Unwind_Reason_Code reason, _Unwind_Exception* p) { + assert(reason == _URC_FOREIGN_EXCEPTION_CAUGHT); ++foreignDeleted; delete p; + }; + try { + try { _Unwind_RaiseException(exception); assert(false); } + catch (...) { assert(__cxa_current_primary_exception_nid_postfix() == nullptr); throw; } + } catch (...) {} + assert(foreignDeleted == 1); +} + +int staticAttempts = 0; +__attribute__((noinline)) int StaticValue() { + static int value = [] { if (++staticAttempts == 1) throw 91; return 37; }(); + return value; +} +void CheckStaticInitialization() { + try { StaticValue(); assert(false); } catch (int value) { assert(value == 91); } + assert(StaticValue() == 37 && StaticValue() == 37 && staticAttempts == 2); +} + +int arrayConstructed, arrayDestroyed, arrayFreed; +bool failArray; +void ArrayConstruct(void* pointer) { + if (failArray && arrayConstructed == 2) throw 73; + *static_cast(pointer) = arrayConstructed++; +} +void ArrayDestroy(void* pointer) { + assert(*static_cast(pointer) == arrayConstructed - 1 - arrayDestroyed); + ++arrayDestroyed; +} +void ArrayFree(void* pointer, std::size_t size) { + assert(size == 4 * sizeof(int) + sizeof(std::size_t)); + ++arrayFreed; + std::free(pointer); +} +void CheckArrays() { + failArray = true; + try { + __cxa_vec_new3_nid_postfix(4, sizeof(int), sizeof(std::size_t), ArrayConstruct, ArrayDestroy, std::malloc, ArrayFree); + assert(false); + } catch (int value) { assert(value == 73); } + assert(arrayConstructed == 2 && arrayDestroyed == 2 && arrayFreed == 1); + arrayConstructed = arrayDestroyed = arrayFreed = 0; + failArray = false; + void* array = __cxa_vec_new3_nid_postfix(4, sizeof(int), sizeof(std::size_t), ArrayConstruct, ArrayDestroy, std::malloc, ArrayFree); + __cxa_vec_delete3_nid_postfix(array, sizeof(int), sizeof(std::size_t), ArrayDestroy, ArrayFree); + assert(arrayConstructed == 4 && arrayDestroyed == 4 && arrayFreed == 1); +} +void* ThreadTest(void*) { + for (int i = 0; i < 100; ++i) { + try { ThrowInt(); } catch (int value) { assert(value == 42); } + assert(__cxa_uncaught_exceptions_nid_postfix() == 0); + } + assert(destroyed == 100); + return nullptr; +} +void CheckThreads() { + pthread_t threads[4]; + for (auto& thread : threads) assert(pthread_create(&thread, nullptr, ThreadTest, nullptr) == 0); + for (auto& thread : threads) assert(pthread_join(thread, nullptr) == 0); +} +__attribute__((noinline)) void UncaughtThrow() { throw 19; } +__attribute__((noinline)) void NoexceptThrow() noexcept { UncaughtThrow(); } +void CheckTerminate(void (*function)()) { + pid_t child = fork(); + assert(child >= 0); + if (child == 0) { + _ZSt13set_terminatePFvvE_nid_postfix([] { _exit(61); }); + function(); + _exit(62); + } + int status; + assert(waitpid(child, &status, 0) == child); + assert(WIFEXITED(status) && WEXITSTATUS(status) == 61); +} +struct ForcedGuard { ~ForcedGuard() { ++destroyed; } }; +__attribute__((noinline)) void ForceUnwind() { + ForcedGuard guard; + auto* exception = new _Unwind_Exception {}; + _Unwind_ForcedUnwind_nid_postfix(exception, + [](int, _Unwind_Action actions, std::uint64_t, _Unwind_Exception*, _Unwind_Context*, void*) { + if (actions & _UA_END_OF_STACK) _exit(destroyed == 1 ? 63 : 64); + return _URC_NO_REASON; + }, nullptr); + _exit(65); +} +void CheckForcedUnwind() { + pid_t child = fork(); assert(child >= 0); + if (child == 0) { destroyed = 0; ForceUnwind(); } + int status; assert(waitpid(child, &status, 0) == child); + assert(WIFEXITED(status) && WEXITSTATUS(status) == 63); +} + +int main() { + try { ThrowInt(); assert(false); } catch (int value) { assert(value == 42); } + assert(destroyed == 1); + try { ThrowClass(); assert(false); } catch (const Base& value) { assert(value.value == 7); } + assert(destroyed == 2); + try { + try { throw 13; } catch (int value) { assert(value == 13); throw; } + } catch (int value) { assert(value == 13); } + assert(__cxa_uncaught_exceptions_nid_postfix() == 0); + void* retained = nullptr; + try { throw 27; } catch (...) { retained = __cxa_current_primary_exception_nid_postfix(); } + try { __cxa_rethrow_primary_exception_nid_postfix(retained); assert(false); } + catch (int value) { assert(value == 27); } + __cxa_decrement_exception_refcount_nid_postfix(retained); + Derived object; + Base* pointer = &object; + try { throw &object; } catch (Base* value) { assert(value == pointer); } + assert(dynamic_cast(pointer) == &object); + assert(dynamic_cast(pointer) == static_cast(&object)); + try { throw Virtual(); } catch (Base& value) { assert(value.value == 7); } + try { _ZSt14_Xout_of_rangePKc_nid_postfix("test message"); } + catch (const std::logic_error& value) { assert(std::strcmp(value.what(), "test message") == 0); } + try { __cxa_bad_cast_nid_postfix(); } + catch (const std::exception& value) { assert(value.what() != nullptr); } + CheckRtti(); + CheckForeign(); + CheckStaticInitialization(); + CheckArrays(); + CheckThreads(); + CheckTerminate(UncaughtThrow); + CheckTerminate(NoexceptThrow); + CheckForcedUnwind(); + int frames = 0; + auto backtrace = _Unwind_Backtrace_nid_postfix([](_Unwind_Context* context, void* argument) { + assert(_Unwind_GetIP_nid_postfix(context) != 0); + ++*static_cast(argument); + return _URC_NO_REASON; + }, &frames); + assert(backtrace == _URC_END_OF_STACK && frames >= 2); + std::puts("exception runtime tests passed"); +} diff --git a/core/libs/tests/run_exception_tests.py b/core/libs/tests/run_exception_tests.py new file mode 100644 index 000000000..bd803848f --- /dev/null +++ b/core/libs/tests/run_exception_tests.py @@ -0,0 +1,68 @@ +import argparse +import base64 +import hashlib +import os +from pathlib import Path +import subprocess +import tempfile + +ROOT = Path(__file__).resolve().parents[3] +RUNTIME = ROOT / 'core/libs/prx/libc/src/exception/Exports.cpp' +TEST = Path(__file__).with_name('ExceptionRuntime.cpp') +parser = argparse.ArgumentParser() +parser.add_argument('--prx', type=Path) +arguments = parser.parse_args() + + +def nid(symbol): + digest = hashlib.sha1(symbol.encode() + bytes.fromhex('518d64a635ded8c1e6b039b1c3e55230')).digest() + return base64.b64encode(digest[:8][::-1]).decode().rstrip('=').replace('/', '-') + + +def run(*args): + subprocess.run([str(arg) for arg in args], check=True) + + +with tempfile.TemporaryDirectory(prefix='anyps5-exceptions-') as directory: + work = Path(directory) + for optimization in ('-O0', '-O2'): + runtime = work / 'runtime.o' + test = work / 'test.o' + executable = work / 'test' + compiler = os.environ.get('CXX', 'c++') + run(compiler, '-std=c++20', optimization, '-g', '-fPIC', '-c', RUNTIME, '-o', runtime) + symbols = subprocess.check_output(['nm', '--defined-only', str(runtime)], text=True) + exports = {line.split()[-1] for line in symbols.splitlines() if line.split()} + unresolved = subprocess.check_output(['nm', '-u', str(runtime)], text=True) + forbidden = ('__cxa_throw', '__cxa_begin_catch', '__cxa_end_catch', '__cxa_rethrow', + '__gxx_personality_v0', '_Unwind_Resume', '_Unwind_RaiseException', '_ZSt9terminatev') + assert not any(line.split()[-1] in forbidden for line in unresolved.splitlines()), unresolved + run(compiler, '-std=c++20', optimization, '-g', '-c', TEST, '-o', test) + unresolved = subprocess.check_output(['nm', '-u', str(test)], text=True) + mapping = [] + for line in unresolved.splitlines(): + symbol = line.split()[-1] + if symbol + '_nid_postfix' in exports: + mapping += ['--redefine-sym', symbol + '=' + symbol + '_nid_postfix'] + run('objcopy', *mapping, test) + run(compiler, '-g', runtime, test, '-pthread', '-Wl,--eh-frame-hdr', '-o', executable) + run(executable) + print(compiler, optimization, 'standalone passed', flush=True) + if arguments.prx: + prx = arguments.prx.resolve() + dynamic = subprocess.check_output(['readelf', '--dyn-syms', '--wide', str(prx)], text=True) + prx_exports = {parts[7] for line in dynamic.splitlines() + if len(parts := line.split()) >= 8 and parts[6] != 'UND'} + expected = {nid(symbol.removesuffix('_nid_postfix')) for symbol in exports if symbol.endswith('_nid_postfix')} + assert expected <= prx_exports, sorted(expected - prx_exports) + run(compiler, '-std=c++20', optimization, '-g', '-c', TEST, '-o', test) + unresolved = subprocess.check_output(['nm', '-u', str(test)], text=True) + mapping = [] + for line in unresolved.splitlines(): + symbol = line.split()[-1] + if symbol in exports and symbol.endswith('_nid_postfix') or symbol + '_nid_postfix' in exports: + mapping += ['--redefine-sym', symbol + '=' + nid(symbol.removesuffix('_nid_postfix'))] + run('objcopy', *mapping, test) + run(compiler, '-g', test, prx, '-pthread', '-Wl,--eh-frame-hdr', '-Wl,-rpath,' + str(prx.parent), '-o', executable) + run(executable) + print(compiler, optimization, 'NID PRX passed', flush=True)