feat(libc): add ex tests + remove bad_alloc dup

This commit is contained in:
boykopovar 2026-09-06 07:30:59 +03:00
parent 57aedfcff0
commit baa8a8d9d3
3 changed files with 283 additions and 4 deletions

View File

@ -2,10 +2,11 @@
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <new>
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<std::uintptr_t*>(rawPtr) = 0;
return static_cast<char*>(rawPtr) + headerSize;
@ -70,7 +71,7 @@ void* realloc_nid_postfix(void* ptr, size_t newSize) {
void* rawPtr = static_cast<char*>(ptr) - headerSize;
void* newRawPtr = std::realloc(rawPtr, newSize + headerSize);
if (newRawPtr == nullptr) {
throw std::bad_alloc();
_ZSt11_Xbad_allocv_nid_postfix();
}
return static_cast<char*>(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<std::uintptr_t>(rawPtr) + headerSize;
const std::uintptr_t alignedAddress = (rawAddress + alignment - 1) & ~(alignment - 1);

View File

@ -0,0 +1,210 @@
#include <cassert>
#include <cstdio>
#include <typeinfo>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <cstdlib>
#include <stdexcept>
#include <pthread.h>
#include <sys/wait.h>
#include <unistd.h>
#include <unwind.h>
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<Derived*>(p); }
__attribute__((noinline)) Repeated* ToRepeated(Base* p) { return dynamic_cast<Repeated*>(p); }
void CheckRtti() {
Repeated repeated;
Base* left = static_cast<Left*>(&repeated);
assert(ToRepeated(left) == &repeated);
assert(dynamic_cast<Right*>(left) == static_cast<Right*>(&repeated));
PrivateDerived hidden;
assert(ToDerived(hidden.source()) == hidden.target());
try { throw static_cast<Repeated*>(nullptr); }
catch (Base*) { assert(false); }
catch (Repeated* p) { assert(p == nullptr); }
try { throw static_cast<Diamond*>(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<int*>(pointer) = arrayConstructed++;
}
void ArrayDestroy(void* pointer) {
assert(*static_cast<int*>(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<Derived*>(pointer) == &object);
assert(dynamic_cast<Other*>(pointer) == static_cast<Other*>(&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<int*>(argument);
return _URC_NO_REASON;
}, &frames);
assert(backtrace == _URC_END_OF_STACK && frames >= 2);
std::puts("exception runtime tests passed");
}

View File

@ -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)