mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 08:33:12 -05:00
The default terminate handler formatted its whole report into a 1024-byte buffer, so a long what() was cut off without a trailing newline. Driver refusals that carry a serialized RecompileRequest lost most of it, and the next log line ran into the truncated text. The report header still goes through the bounded RawLog, and what() is now written in full by a raw write to the stderr handle, followed by a newline. Both keep avoiding the CRT stream lock, as before. exception_runtime gains a forked child that throws an uncaught runtime_error with a message over 3000 characters and checks that the full what() reaches stderr before SIGABRT.
268 lines
11 KiB
C++
268 lines
11 KiB
C++
#undef NDEBUG
|
|
#include <cassert>
|
|
#include <cstdio>
|
|
#include <typeinfo>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <cstdlib>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <csignal>
|
|
#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 _ZSt13_Xrange_errorPKc_nid_postfix(const char*);
|
|
extern "C" [[noreturn]] void _ZNSt8__sce_v219_Xbad_function_callEv_nid_postfix();
|
|
extern "C" [[noreturn]] void __cxa_bad_cast_nid_postfix();
|
|
extern "C" [[noreturn]] void _ZSt19_Throw_bad_weak_ptrv_nid_postfix();
|
|
extern "C" [[noreturn]] void _ZNKSt9exception6_RaiseEv_nid_postfix(const void*);
|
|
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; }
|
|
};
|
|
thread_local int counted = 0;
|
|
struct Counted {
|
|
~Counted() { ++counted; }
|
|
};
|
|
struct Base { virtual ~Base() = default; int value = 7; };
|
|
struct Other { virtual ~Other() = default; int padding = 9; };
|
|
struct Derived : Other, Base {};
|
|
struct Virtual : virtual Base {};
|
|
|
|
[[gnu::noinline]] void ThrowInt() { Guard guard; throw 42; }
|
|
[[gnu::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; }
|
|
};
|
|
[[gnu::noinline]] Derived* ToDerived(Base* p) { return dynamic_cast<Derived*>(p); }
|
|
[[gnu::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;
|
|
[[gnu::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);
|
|
}
|
|
[[gnu::noinline]] void UncaughtThrow() { throw 19; }
|
|
[[gnu::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);
|
|
}
|
|
void CheckDefaultTerminateMessage() {
|
|
const std::string message = std::string(3000, 'w') + " end of message";
|
|
int channel[2];
|
|
assert(pipe(channel) == 0);
|
|
pid_t child = fork();
|
|
assert(child >= 0);
|
|
if (child == 0) {
|
|
close(channel[0]);
|
|
dup2(channel[1], 2);
|
|
throw std::runtime_error(message);
|
|
}
|
|
close(channel[1]);
|
|
std::string output;
|
|
char buffer[4096];
|
|
for (ssize_t count; (count = read(channel[0], buffer, sizeof(buffer))) > 0;) output.append(buffer, static_cast<std::size_t>(count));
|
|
close(channel[0]);
|
|
int status;
|
|
assert(waitpid(child, &status, 0) == child);
|
|
assert(WIFSIGNALED(status) && WTERMSIG(status) == SIGABRT);
|
|
assert(output.find(" what(): " + message + "\n") != std::string::npos);
|
|
}
|
|
struct ForcedGuard { ~ForcedGuard() { ++destroyed; } };
|
|
[[gnu::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);
|
|
{
|
|
auto made = std::make_exception_ptr(Counted());
|
|
try { std::rethrow_exception(made); assert(false); } catch (const Counted&) {}
|
|
}
|
|
assert(counted == 2);
|
|
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 { _ZSt13_Xrange_errorPKc_nid_postfix("range message"); }
|
|
catch (const std::range_error& value) { if (std::strcmp(value.what(), "range message") != 0) std::abort(); }
|
|
try { _ZSt13_Xrange_errorPKc_nid_postfix(nullptr); }
|
|
catch (const std::runtime_error& value) { if (std::strcmp(value.what(), "") != 0) std::abort(); }
|
|
try { _ZSt13_Xrange_errorPKc_nid_postfix("not a logic error"); }
|
|
catch (const std::logic_error&) { std::abort(); }
|
|
catch (const std::range_error&) {}
|
|
bool badFunctionCall = false;
|
|
try { _ZNSt8__sce_v219_Xbad_function_callEv_nid_postfix(); }
|
|
catch (const std::bad_function_call&) { badFunctionCall = true; }
|
|
if (!badFunctionCall) std::abort();
|
|
try { __cxa_bad_cast_nid_postfix(); }
|
|
catch (const std::exception& value) { assert(value.what() != nullptr); }
|
|
try { _ZSt19_Throw_bad_weak_ptrv_nid_postfix(); std::abort(); }
|
|
catch (const std::bad_weak_ptr& value) { if (std::strcmp(value.what(), "bad_weak_ptr") != 0) std::abort(); }
|
|
try { _ZNKSt9exception6_RaiseEv_nid_postfix(nullptr); assert(false); }
|
|
catch (const std::invalid_argument&) {}
|
|
try { _ZNKSt9exception6_RaiseEv_nid_postfix(&object); assert(false); }
|
|
catch (const std::exception& value) { assert(std::strcmp(value.what(), "std::exception") == 0); }
|
|
CheckRtti();
|
|
CheckForeign();
|
|
CheckStaticInitialization();
|
|
CheckArrays();
|
|
CheckThreads();
|
|
CheckTerminate(UncaughtThrow);
|
|
CheckTerminate(NoexceptThrow);
|
|
CheckDefaultTerminateMessage();
|
|
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");
|
|
}
|