diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 670c6d3531..aa8fa85c27 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -873,6 +873,16 @@ if(BUILD_TESTING) set_tests_properties(guest_generic_category PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_future_category_tests ${LIBS_INCLUDE_DIR}/tests/GuestFutureCategory.cpp) + target_include_directories(guest_future_category_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_future_category_tests PRIVATE libc) + configure_windows_unwind(guest_future_category_tests) + add_test(NAME guest_future_category COMMAND guest_future_category_tests) + set_tests_properties(guest_future_category PROPERTIES TIMEOUT 10) + if(WIN32) + set_tests_properties(guest_future_category PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test(NAME guest_new_handler COMMAND guest_new_handler_tests) if(WIN32) set_tests_properties(guest_new_handler PROPERTIES ENVIRONMENT_MODIFICATION diff --git a/core/libs/prx/libc/include/Api.hpp b/core/libs/prx/libc/include/Api.hpp index 4ac85fedc2..459d7a2b29 100644 --- a/core/libs/prx/libc/include/Api.hpp +++ b/core/libs/prx/libc/include/Api.hpp @@ -98,6 +98,8 @@ struct ErrorCode { int reserved; int value; const CategoryObject* category; }; struct SystemErrorObject { ExceptionObject base; ErrorCode code; }; +struct FutureErrorObject { const void* vtable; GuestLocale::String message; ErrorCode code; }; + } extern "C" { @@ -1179,6 +1181,11 @@ const char* APS5_VABI _ZSt13_Syserror_mapi_nid_postfix(int value); const LibcException::CategoryObject* APS5_VABI _ZSt16generic_categoryv_nid_postfix(); const LibcException::CategoryObject* APS5_VABI _ZSt15system_categoryv_nid_postfix(); GuestLocale::String* APS5_VABI _ZNKSt23_Generic_error_category7messageEi_nid_postfix( GuestLocale::String* result, const LibcException::CategoryObject* self, int value); +const LibcException::CategoryObject* APS5_VABI _ZSt15future_categoryv_nid_postfix(); +LibcException::ErrorCode APS5_VABI _ZNKSt14error_category23default_error_conditionEi_nid_postfix(const LibcException::CategoryObject* self, int value); +bool APS5_VABI _ZNKSt14error_category10equivalentEiRKSt15error_condition_nid_postfix(const LibcException::CategoryObject* self, int value, const LibcException::ErrorCode* condition); +bool APS5_VABI _ZNKSt14error_category10equivalentERKSt10error_codei_nid_postfix(const LibcException::CategoryObject* self, const LibcException::ErrorCode* code, int value); +void APS5_VABI _ZNSt14error_categoryD2Ev_nid_postfix(LibcException::CategoryObject* self); void APS5_VABI _ZNSt9exceptionD1Ev_nid_postfix(LibcException::ExceptionObject* self); void APS5_VABI _ZNSt9exceptionD2Ev_nid_postfix(LibcException::ExceptionObject* self); void APS5_VABI _ZNSt9exceptionD0Ev_nid_postfix(LibcException::ExceptionObject* self); @@ -1280,6 +1287,7 @@ void APS5_VABI _ZNSt12length_errorC1ERKS__nid_postfix(LibcException::ExceptionOb void APS5_VABI _ZNSt12length_errorC2ERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); LibcException::ExceptionObject* APS5_VABI _ZNSt12length_erroraSERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source); void APS5_VABI _ZNSt12system_errorD1Ev_nid_postfix(LibcException::ExceptionObject* self); +void APS5_VABI _ZNSt12future_errorD1Ev_nid_postfix(LibcException::FutureErrorObject* self); void APS5_VABI _ZNSt12system_errorD2Ev_nid_postfix(LibcException::ExceptionObject* self); void APS5_VABI _ZNSt12system_errorD0Ev_nid_postfix(LibcException::ExceptionObject* self); const char* APS5_VABI _ZNKSt12system_error4whatEv_nid_postfix(const LibcException::ExceptionObject* self); @@ -1325,6 +1333,8 @@ int APS5_VABI dl_iterate_phdr_nid_postfix(int (*callback)(dl_phdr_info*, std::si [[noreturn]] void APS5_VABI _ZSt16_Throw_Cpp_errori_nid_postfix(int code); [[noreturn]] void APS5_VABI _Thrd_exit_nid_postfix(int code); [[noreturn]] void APS5_VABI _ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE_nid_postfix(GuestExceptionPointerArgument exception); +[[noreturn]] void APS5_VABI _ZSt19_Throw_future_errorRKSt10error_code_nid_postfix(const LibcException::ErrorCode* code); +[[noreturn]] void APS5_VABI _ZSt25_Rethrow_future_exceptionSt13exception_ptr_nid_postfix(GuestExceptionPointerArgument exception); [[noreturn]] void APS5_VABI _ZSt9terminatev_nid_postfix(); [[noreturn]] void APS5_VABI __cxa_call_terminate_nid_postfix(void* exception); [[noreturn]] void APS5_VABI __cxa_pure_virtual_nid_postfix(); diff --git a/core/libs/prx/libc/src/exception/StandardExceptions.cpp b/core/libs/prx/libc/src/exception/StandardExceptions.cpp index 83ffdb0b78..e6bca47778 100644 --- a/core/libs/prx/libc/src/exception/StandardExceptions.cpp +++ b/core/libs/prx/libc/src/exception/StandardExceptions.cpp @@ -11,6 +11,8 @@ namespace LibcException { struct TypeRecord { const void* const* vtable; const char* name; const TypeRecord* base; }; +struct ClassTypeRecord { const void* const* vtable; const char* name; }; + struct ExceptionVtable { std::ptrdiff_t offset; const TypeRecord* type; @@ -18,6 +20,17 @@ struct ExceptionVtable { void (APS5_VABI *deleteObject)(ExceptionObject*); const char* (APS5_VABI *what)(const ExceptionObject*); }; + +struct FutureErrorVtable { + std::ptrdiff_t offset; + const TypeRecord* type; + void (APS5_VABI *destroy)(FutureErrorObject*); + void (APS5_VABI *deleteObject)(FutureErrorObject*); + const char* (APS5_VABI *what)(const FutureErrorObject*); + void (APS5_VABI *doraise)(const FutureErrorObject*); +}; +static_assert(sizeof(ClassTypeRecord) == 0x10); +static_assert(sizeof(FutureErrorVtable) == 0x30); struct Message { std::size_t length; std::size_t capacity; @@ -129,6 +142,7 @@ ExceptionObject* Assign(ExceptionObject* object, const ExceptionObject* source) static_assert(sizeof(ErrorCode) == 16 && offsetof(ErrorCode, value) == 4 && offsetof(ErrorCode, category) == 8); static_assert(sizeof(SystemErrorObject) == 32 && offsetof(SystemErrorObject, code) == 16); +static_assert(sizeof(FutureErrorObject) == 0x40 && offsetof(FutureErrorObject, message) == 0x8 && offsetof(FutureErrorObject, code) == 0x30); struct CppError { int value; const char* message; }; static const char* GenericErrorMessage(int value) { @@ -220,10 +234,8 @@ static void APS5_VABI CategoryDelete_nid_no_patch(CategoryObject*) { } static const char* APS5_VABI GenericCategoryName_nid_no_patch(const CategoryObject*) { return "generic"; } static const char* APS5_VABI SystemCategoryName_nid_no_patch(const CategoryObject*) { return "system"; } -static GuestLocale::String* APS5_VABI CategoryMessage_nid_no_patch(GuestLocale::String* result, const CategoryObject*, int value) { +static GuestLocale::String* FillMessage(GuestLocale::String* result, const char* message) { if (!result) throw std::invalid_argument("std::error_category::message: null result"); - const char* message = GenericErrorMessage(value); - if (!message) message = "unknown error"; const auto length = std::strlen(message); const auto capacity = length | std::size_t{15}; char* storage = length <= 15 ? result->buffer : static_cast(_Znwm_nid_postfix(capacity + 1)); @@ -234,6 +246,23 @@ static GuestLocale::String* APS5_VABI CategoryMessage_nid_no_patch(GuestLocale:: result->capacity = capacity; return result; } +static const char* FutureErrorMessage(int value) { + static constexpr const char* messages[] {"broken promise", "future already retrieved", "promise already satisfied", "no state"}; + if (value < 1 || value > 4) return nullptr; + return messages[value - 1]; +} +static GuestLocale::String* APS5_VABI CategoryMessage_nid_no_patch(GuestLocale::String* result, const CategoryObject*, int value) { + const char* message = GenericErrorMessage(value); + if (!message) message = "unknown error"; + return FillMessage(result, message); +} +static GuestLocale::String* APS5_VABI FutureCategoryMessage_nid_no_patch(GuestLocale::String* result, const CategoryObject*, int value) { + const char* message = FutureErrorMessage(value); + if (!message) message = GenericErrorMessage(value); + if (!message) message = "unknown error"; + return FillMessage(result, message); +} +static const char* APS5_VABI FutureCategoryName_nid_no_patch(const CategoryObject*) { return "future"; } static ErrorCode APS5_VABI CategoryCondition_nid_no_patch(const CategoryObject* self, int value) { return {0, value, self}; } static bool APS5_VABI CategoryEquivalentCondition_nid_no_patch(const CategoryObject* self, int value, const ErrorCode* condition) { using Condition = ErrorCode (APS5_VABI *)(const CategoryObject*, int); @@ -637,4 +666,89 @@ const char* APS5_VABI _ZNSt8__sce_v213_Syserror_mapEi_nid_postfix(int value) { const char* message = LibcException::GenericErrorMessage(value); return message ? message : "unknown error"; } +LibcException::ClassTypeRecord _ZTISt14error_category_nid_postfix {LibcException::ClassTypeVtable + 2, "St14error_category"}; +LibcException::TypeRecord _ZTISt22_Future_error_category_nid_postfix {LibcException::SingleTypeVtable + 2, "St22_Future_error_category", &LibcException::GenericCategoryType}; +static void APS5_VABI FutureCategoryDelete_nid_no_patch(LibcException::CategoryObject*) { + throw std::invalid_argument("std::error_category: the future category object is not heap allocated"); +} +extern const void* const _ZTVSt22_Future_error_category_nid_postfix[] { + nullptr, &_ZTISt22_Future_error_category_nid_postfix, + reinterpret_cast(LibcException::CategoryDestroy_nid_no_patch), reinterpret_cast(FutureCategoryDelete_nid_no_patch), + reinterpret_cast(LibcException::FutureCategoryName_nid_no_patch), reinterpret_cast(LibcException::FutureCategoryMessage_nid_no_patch), + reinterpret_cast(LibcException::CategoryCondition_nid_no_patch), + reinterpret_cast(LibcException::CategoryEquivalentCondition_nid_no_patch), + reinterpret_cast(LibcException::CategoryEquivalentCode_nid_no_patch) +}; +static const LibcException::CategoryObject FutureCategory {_ZTVSt22_Future_error_category_nid_postfix + 2}; +const LibcException::CategoryObject* APS5_VABI _ZSt15future_categoryv_nid_postfix() { + return &FutureCategory; +} +LibcException::ErrorCode APS5_VABI _ZNKSt14error_category23default_error_conditionEi_nid_postfix(const LibcException::CategoryObject* self, int value) { + return LibcException::CategoryCondition_nid_no_patch(self, value); +} +bool APS5_VABI _ZNKSt14error_category10equivalentEiRKSt15error_condition_nid_postfix(const LibcException::CategoryObject* self, int value, const LibcException::ErrorCode* condition) { + return LibcException::CategoryEquivalentCondition_nid_no_patch(self, value, condition); +} +bool APS5_VABI _ZNKSt14error_category10equivalentERKSt10error_codei_nid_postfix(const LibcException::CategoryObject* self, const LibcException::ErrorCode* code, int value) { + return LibcException::CategoryEquivalentCode_nid_no_patch(self, code, value); +} +void APS5_VABI _ZNSt14error_categoryD2Ev_nid_postfix(LibcException::CategoryObject*) {} +LibcException::TypeRecord _ZTISt12future_error_nid_postfix {LibcException::SingleTypeVtable + 2, "St12future_error", &_ZTISt11logic_error_nid_postfix}; +static void DestroyFutureErrorString(GuestLocale::String* message) { + if (message->capacity > 15) _ZdlPv_nid_postfix(message->pointer); + message->reserved = 0; + message->size = 0; + message->capacity = 15; + std::memset(message->buffer, 0, sizeof(message->buffer)); +} +static void APS5_VABI DestroyFutureError_nid_no_patch(LibcException::FutureErrorObject* self) { + DestroyFutureErrorString(&self->message); +} +static void APS5_VABI DeleteFutureError_nid_no_patch(LibcException::FutureErrorObject* self) { + DestroyFutureErrorString(&self->message); + std::free(self); +} +static const char* APS5_VABI FutureErrorWhat_nid_no_patch(const LibcException::FutureErrorObject* self) { + return LibcException::FutureErrorMessage(self->code.value); +} +static void CopyFutureError(LibcException::FutureErrorObject* self, const LibcException::FutureErrorObject* source) { + *self = *source; + if (source->message.capacity > 15) { + const auto capacity = source->message.capacity; + auto* storage = static_cast(_Znwm_nid_postfix(capacity + 1)); + std::memcpy(storage, source->message.pointer, source->message.size + 1); + self->message.pointer = storage; + } +} +[[noreturn]] static void APS5_VABI RaiseFutureError_nid_no_patch(const LibcException::FutureErrorObject* self) { + auto* object = static_cast(__cxa_allocate_exception_nid_postfix(sizeof(LibcException::FutureErrorObject))); + try { + CopyFutureError(object, self); + } catch (...) { + __cxa_free_exception_nid_postfix(object); + throw; + } + __cxa_throw_nid_postfix(object, reinterpret_cast(&_ZTISt12future_error_nid_postfix), + reinterpret_cast(DestroyFutureError_nid_no_patch)); +} +LibcException::FutureErrorVtable _ZTVSt12future_error_nid_postfix { + 0, &_ZTISt12future_error_nid_postfix, + DestroyFutureError_nid_no_patch, DeleteFutureError_nid_no_patch, FutureErrorWhat_nid_no_patch, RaiseFutureError_nid_no_patch +}; +[[noreturn]] void APS5_VABI _ZSt19_Throw_future_errorRKSt10error_code_nid_postfix(const LibcException::ErrorCode* code) { + auto* object = static_cast(__cxa_allocate_exception_nid_postfix(sizeof(LibcException::FutureErrorObject))); + object->vtable = &_ZTVSt12future_error_nid_postfix.destroy; + object->message = {}; + object->message.capacity = 15; + object->code = *code; + __cxa_throw_nid_postfix(object, reinterpret_cast(&_ZTISt12future_error_nid_postfix), + reinterpret_cast(DestroyFutureError_nid_no_patch)); +} +[[noreturn]] void APS5_VABI _ZSt25_Rethrow_future_exceptionSt13exception_ptr_nid_postfix(GuestExceptionPointerArgument exception) { + _ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE_nid_postfix(exception); +} +void APS5_VABI _ZNSt12future_errorD1Ev_nid_postfix(LibcException::FutureErrorObject* self) { + DestroyFutureErrorString(&self->message); +} + } diff --git a/core/libs/tests/GuestFutureCategory.cpp b/core/libs/tests/GuestFutureCategory.cpp new file mode 100644 index 0000000000..35e38af6a7 --- /dev/null +++ b/core/libs/tests/GuestFutureCategory.cpp @@ -0,0 +1,192 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libc/include/GuestLocale.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using Message = GuestLocale::String* (APS5_VABI *)(GuestLocale::String*, const LibcException::CategoryObject*, int); +using Name = const char* (APS5_VABI *)(const LibcException::CategoryObject*); +using Raise = void (APS5_VABI *)(const LibcException::FutureErrorObject*); + +bool failAllocation = false; + +void* APS5_VABI TestAllocate(std::size_t size) { return failAllocation ? nullptr : std::malloc(size); } +void APS5_VABI TestFree(void* pointer) { std::free(pointer); } +void* APS5_VABI TestCalloc(std::size_t count, std::size_t size) { return std::calloc(count, size); } +void* APS5_VABI TestReallocate(void* pointer, std::size_t size) { return std::realloc(pointer, size); } +void* APS5_VABI TestAlign(std::size_t, std::size_t size) { return std::malloc(size); } +void* APS5_VABI TestRealign(void* pointer, std::size_t size, std::size_t) { return std::realloc(pointer, size); } +int APS5_VABI TestPosixAlign(void** pointer, std::size_t, std::size_t size) { + *pointer = std::malloc(size); + return *pointer ? 0 : 12; +} + +struct TypeRecord { const void* vtable; const char* name; const TypeRecord* base; }; +struct ClassTypeRecord { const void* vtable; const char* name; }; + +extern "C" { +extern TypeRecord _ZTISt12future_error_nid_postfix; +extern TypeRecord _ZTISt22_Future_error_category_nid_postfix; +extern ClassTypeRecord _ZTISt14error_category_nid_postfix; +extern std::uintptr_t _ZTVSt12future_error_nid_postfix[6]; +} + +static void Require(bool value, int line) { + if (value) return; + std::fprintf(stderr, "future category check failed at line %d\n", line); + std::exit(1); +} +#define REQUIRE(value) Require((value), __LINE__) + +static void CheckMessage(Message message, const LibcException::CategoryObject* category, int value, const char* expected) { + constexpr std::uint64_t marker = 0xA55AA55AA55AA55Aull; + struct Guarded { std::uint64_t before; GuestLocale::String result; std::uint64_t after; }; + Guarded guarded {marker, {}, marker}; + std::memset(&guarded.result, 0xcd, sizeof(guarded.result)); + *__error_nid_postfix() = 61; + REQUIRE(message(&guarded.result, category, value) == &guarded.result); + REQUIRE(*__error_nid_postfix() == 61); + REQUIRE(guarded.before == marker && guarded.after == marker); + const auto length = std::strlen(expected); + REQUIRE(guarded.result.reserved == 0 && guarded.result.size == length); + REQUIRE(guarded.result.capacity >= length && (guarded.result.capacity & 15) == 15); + const char* text = guarded.result.capacity <= 15 ? guarded.result.buffer : guarded.result.pointer; + REQUIRE(std::memcmp(text, expected, length + 1) == 0); + REQUIRE((guarded.result.capacity <= 15) == (length <= 15)); + if (length > 15) _ZdlPv_nid_postfix(guarded.result.pointer); +} + +static void CheckBaseCategory() { + REQUIRE(sizeof(ClassTypeRecord) == 0x10); + REQUIRE(std::strcmp(_ZTISt14error_category_nid_postfix.name, "St14error_category") == 0); + const auto* future = _ZSt15future_categoryv_nid_postfix(); + const auto* generic = _ZSt16generic_categoryv_nid_postfix(); + REQUIRE(future != nullptr && generic != nullptr && future != generic); + + const LibcException::ErrorCode condition {0x7fffffff, 22, future}; + REQUIRE(_ZNKSt14error_category10equivalentEiRKSt15error_condition_nid_postfix(future, 22, &condition)); + REQUIRE(!_ZNKSt14error_category10equivalentEiRKSt15error_condition_nid_postfix(future, 23, &condition)); + const LibcException::ErrorCode otherCondition {0, 22, generic}; + REQUIRE(!_ZNKSt14error_category10equivalentEiRKSt15error_condition_nid_postfix(future, 22, &otherCondition)); + + const LibcException::ErrorCode code {0x12345678, 22, future}; + REQUIRE(_ZNKSt14error_category10equivalentERKSt10error_codei_nid_postfix(future, &code, 22)); + REQUIRE(!_ZNKSt14error_category10equivalentERKSt10error_codei_nid_postfix(future, &code, 23)); + const LibcException::ErrorCode otherCode {0, 22, generic}; + REQUIRE(!_ZNKSt14error_category10equivalentERKSt10error_codei_nid_postfix(future, &otherCode, 22)); + + const auto mapped = _ZNKSt14error_category23default_error_conditionEi_nid_postfix(future, 7); + REQUIRE(mapped.reserved == 0 && mapped.value == 7 && mapped.category == future); + _ZNSt14error_categoryD2Ev_nid_postfix(nullptr); +} + +static void CheckFutureCategory() { + const auto* future = _ZSt15future_categoryv_nid_postfix(); + const auto* type = static_cast(static_cast(future->vtable)[-1]); + REQUIRE(std::strcmp(type->name, "St22_Future_error_category") == 0); + REQUIRE(type->base && std::strcmp(type->base->name, "St23_Generic_error_category") == 0); + REQUIRE(std::strcmp(_ZTISt22_Future_error_category_nid_postfix.name, "St22_Future_error_category") == 0); + REQUIRE(_ZTISt22_Future_error_category_nid_postfix.base && std::strcmp(_ZTISt22_Future_error_category_nid_postfix.base->name, "St23_Generic_error_category") == 0); + + const auto name = std::bit_cast(static_cast(future->vtable)[2]); + REQUIRE(std::strcmp(name(future), "future") == 0); + + const auto message = std::bit_cast(static_cast(future->vtable)[3]); + struct Expected { int value; const char* text; }; + static constexpr Expected errors[] { + {1, "broken promise"}, {2, "future already retrieved"}, {3, "promise already satisfied"}, {4, "no state"} + }; + for (const auto& error : errors) CheckMessage(message, future, error.value, error.text); + + for (int value : {0, 5, 22, 99}) { + const char* generic = _ZSt13_Syserror_mapi_nid_postfix(value); + CheckMessage(message, future, value, generic ? generic : "unknown error"); + } + + GuestLocale::String first {}, second {}; + REQUIRE(message(&first, future, 2) == &first && message(&second, future, 3) == &second); + REQUIRE(first.pointer != second.pointer); + _ZdlPv_nid_postfix(second.pointer); + REQUIRE(std::strcmp(first.pointer, "future already retrieved") == 0); + _ZdlPv_nid_postfix(first.pointer); + + bool rejected = false; + try { message(nullptr, future, 2); } + catch (const std::invalid_argument&) { rejected = true; } + REQUIRE(rejected); +} + +static void CheckFutureError() { + REQUIRE(sizeof(LibcException::FutureErrorObject) == 0x40); + REQUIRE(offsetof(LibcException::FutureErrorObject, message) == 0x8); + REQUIRE(offsetof(LibcException::FutureErrorObject, code) == 0x30); + REQUIRE(std::strcmp(_ZTISt12future_error_nid_postfix.name, "St12future_error") == 0); + REQUIRE(_ZTISt12future_error_nid_postfix.base && std::strcmp(_ZTISt12future_error_nid_postfix.base->name, "St11logic_error") == 0); + + REQUIRE(_ZTVSt12future_error_nid_postfix[0] == 0); + REQUIRE(_ZTVSt12future_error_nid_postfix[1] == reinterpret_cast(&_ZTISt12future_error_nid_postfix)); + for (int slot = 2; slot < 6; ++slot) REQUIRE(_ZTVSt12future_error_nid_postfix[slot] != 0); + + const auto* future = _ZSt15future_categoryv_nid_postfix(); + LibcException::ErrorCode code {0x12345678, 2, future}; + bool caught = false; + try { _ZSt19_Throw_future_errorRKSt10error_code_nid_postfix(&code); } + catch (const std::exception& error) { + caught = true; + REQUIRE(error.what() != nullptr && std::strcmp(error.what(), "future already retrieved") == 0); + } + REQUIRE(caught); + + code.value = 99; + caught = false; + try { _ZSt19_Throw_future_errorRKSt10error_code_nid_postfix(&code); } + catch (const std::exception& error) { + caught = true; + REQUIRE(error.what() == nullptr); + } + REQUIRE(caught); + + const auto raise = std::bit_cast(_ZTVSt12future_error_nid_postfix[5]); + LibcException::FutureErrorObject source {}; + source.message.capacity = 24; + source.message.size = 24; + source.message.pointer = static_cast(_Znwm_nid_postfix(25)); + std::memcpy(source.message.pointer, "future already retrieved", 25); + source.code = {0x12345678, 2, future}; + failAllocation = true; + bool allocationFailure = false; + try { raise(&source); } + catch (const std::bad_alloc&) { allocationFailure = true; } + failAllocation = false; + REQUIRE(allocationFailure); + REQUIRE(std::strcmp(source.message.pointer, "future already retrieved") == 0); + _ZdlPv_nid_postfix(source.message.pointer); + + LibcException::FutureErrorObject object {}; + object.message.capacity = 15; + _ZNSt12future_errorD1Ev_nid_postfix(&object); + REQUIRE(object.message.reserved == 0 && object.message.size == 0 && object.message.capacity == 15); + REQUIRE(object.message.buffer[0] == 0); +} + +int main() { + const std::array api { + reinterpret_cast(&TestAllocate), reinterpret_cast(&TestFree), + reinterpret_cast(&TestCalloc), reinterpret_cast(&TestReallocate), + reinterpret_cast(&TestAlign), reinterpret_cast(&TestRealign), + reinterpret_cast(&TestPosixAlign), nullptr, nullptr, nullptr}; + ApplicationHeapRegister_nid_no_patch(api.data()); + CheckBaseCategory(); + CheckFutureCategory(); + CheckFutureError(); + std::puts("guest future category, error_category base members and future_error layout: PASS"); + return 0; +} diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 3c90a5c32c..9408e04826 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -178,6 +178,7 @@ - [_WStoul](../../../core/libs/prx/libc/src/Strings.cpp), [_Iswctype](../../../core/libs/prx/libc/src/RuneLocale.cpp), [swscanf_s and swscanf](../../../core/libs/prx/libc/src/ScanningWide.cpp) (libc) - `_WStoul` is `wcstoul` and throws for a base other than 0 or 2 to 36. `_Iswctype` takes the class numbers of Dinkumware's [xwcc.h](https://github.com/superna9999/ti-cgt6x/blob/cgt6x_7_3_4/include/xwcc.h) (1 `alnum` to 11 `xdigit`, 12 `blank`) and throws for any other value, including the FreeBSD masks `wctype` returns; the numbering is not confirmed on the console's `libc.prx`. `swscanf_s` follows C11 7.29.2.2 and K.3.9.1.5, but reads the size argument as `unsigned int` like `sscanf_s` where K.3.9.1.5 specifies `rsize_t`; the console's width is unverified. A runtime-constraint violation (null buffer, format or receiving pointer) only returns `EOF`, and a too small array gets a null first element as in `sscanf_s`. A literal at end of input returns the number of assignments, including zero, rather than `EOF` as FreeBSD does, as reported from the console scanner in #8479; `%%` at end of input still returns `EOF`. A literal mismatch is a matching failure, where FreeBSD's `vfwscanf` reports an input failure. An incomplete floating exponent such as `1e+` converts its mantissa, as reported from the console scanner in #8479; other incomplete exponent forms are not separately verified. A short `%c` field is currently a matching failure without storing its available prefix, while #9321 reports that the console stores a short `%3lc` field; its return count, partial-output extent and precedence are unverified. An integer field that is only a prefix, such as `0x`, does not convert; FreeBSD's `vfwscanf` pushes back the `x` and converts the `0`. Integers out of range are truncated from `strtoll`/`strtoull`. `%p` follows FreeBSD 11's [vfwscanf.c](https://github.com/freebsd/freebsd-src/blob/4ab2e064d7950be84256d671a7ae93f87cc6aa36/lib/libc/stdio/vfwscanf.c#L610-L615): a hexadecimal integer with an optional `0x`, converted as unsigned and stored as a pointer; a length modifier on it throws, where FreeBSD ignores it. `%S` and `%C`, with any length modifier or `*`, end the scan as a matching failure, as the console's Dinkumware scanner in PPSA21564's `libc.prx` treats them as invalid conversions; this was read from the module, not run on a console, and whether input ending before them is an input failure instead is unverified. It throws for unknown length modifiers, scanset ranges, `%n` with suppression or width, and narrow `%s`, `%c` or `%[` input that the C-locale `wcrtomb` cannot convert. `swscanf` runs the same scanner without the size arguments of `%s`, `%c` and `%[`, as FreeBSD's [swscanf.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libc/stdio/swscanf.c) passes its arguments to `vswscanf`, so everything above except the sizes applies to it; it also keeps the null buffer, format and receiving pointer checks returning `EOF`, where C leaves those undefined. Neither variant is confirmed on a PS5 title - [floating scan errno](../../../core/libs/prx/libc/src/ScanningWide.cpp) (libc) leaves narrow `sscanf` inheriting host `ERANGE` for out-of-range `%f`, `%e` and `%lf`; wide `%Lf` retains host `strtold` errno, so glibc's `ERANGE` for the representable `1e-4950` differs from the bundled library. Wide float and double conversions restore the previous `errno` after `strtof`/`strtod`, as observed in the bundled `libc.prx`; converted values and long-double range handling are retained. - [flock](../../../core/libs/prx/libc/src/FileLock.cpp) (libkernel) on Windows shares one lock state between duplicated descriptors, following FreeBSD 11.4's [flock(2)](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libc/sys/flock.2). Independent opens have separate states; closing a descriptor or reopening a stream drops only its reference. Lock transitions on one open file description are serialized. The lock is a host [LockFileEx](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex) lock on one byte at offset 2^63 - 2, which no file reaches, so it excludes other `flock` calls and leaves reads, writes and mapped views alone, like FreeBSD's advisory locks. Console behavior, signal interruption and concurrent closure of a descriptor while its lock call is pending are unverified. +- [std::error_category, std::future_category and std::future_error](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libc) - measured from PPSA02530's `libc.prx`, except that the future category's deleting destructor throws instead of freeing it, the string destructor frees the block instead of the console header path, and a `_Doraise` copy of a heap message and header size are unverified. `error_code` keeps the value in the second dword, so `default_error_condition` shifts it into the high half and `equivalent` compares that dword and the category. `_Future_error_category` derives from `_Generic_error_category`, is `future` and maps 1..4 to `broken promise`, `future already retrieved`, `promise already satisfied` and `no state`; other values use the generic map, but `future_error::what()` returns null outside 1..4. `future_error` is 0x40 bytes (vtable, `GuestLocale::String` at 0x8, `error_code` at 0x30). `_Rethrow_future_exception` delegates to `rethrow_exception`. - [std::_Throw_Cpp_error](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libc) follows Dinkumware `thread0.cpp` ([VC12](https://github.com/ganboing/crt.vc12/blob/f1d47126/src/thread0.cpp), [STL](https://github.com/microsoft/STL/blob/da52dc0f/stl/src/thread0.cpp)), without console verification. Codes outside 0..6 throw `std::out_of_range`, the deleting destructor throws, and STREAMS errnos absent from FreeBSD 11.4 [`errno.h`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/sys/errno.h) (`ENODATA`, `ENOSR`, `ENOSTR`, `ETIME`) are omitted. Codes 0..6 throw a 32-byte [VC12 `system_error`](https://github.com/ganboing/crt.vc12/blob/f1d47126/src/system_error) with FreeBSD errnos (16, 22, 3, 12, 1, 11, 35), a static `generic_category` (not `_Generic_object`), and unverified 40-byte [GuestLocale::String](../../../core/libs/prx/libc/include/GuestLocale.hpp) messages from [`syserror.cpp`](https://github.com/ganboing/crt.vc12/blob/f1d47126/src/syserror.cpp) - [Call tracing](../../../core/libs/prx/libc/src/specifics/x86_64/CallTrace.cpp) (`--trace-imports`, `APS5_TRACE_CALLS`) records only the entry of a call, not its arguments or result. One process-wide ring keeps the last 65536 calls, so a busy thread pushes the others out; the reporting thread's own calls are listed only while they are still in that ring. Calls are counted per traced target; an import bound to a target already traced under another name is counted under the first name. The thunk saves the integer argument registers and `xmm0` to `xmm7`; wider vector arguments survive only while the recording code avoids AVX, which is untested. Linux and macOS have no crash report, so they print the trace only on `SIGABRT` (guest or host `abort()`), not on `SIGSEGV`; a caller in a module loaded after the last traced import prints without a module name, and a thread aborting while another writes the report waits for it at most 2 seconds. An arm64 libc build omits the recorder - [_Dtest](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows Dinkumware double classification matching _FDtest: finite values return -1, subnormals -2, zeros 0, infinities 1, and NaNs 2.