From c7b70f44b0ad9fddf2c8eb9ff85dfededc4b5dc9 Mon Sep 17 00:00:00 2001 From: Eduardo Ruiz Date: Sun, 25 Oct 2026 11:20:21 +0100 Subject: [PATCH] feat(libc): implement std::_Throw(const std::bad_array_new_length&) Follow the console libc.prx (PPSA02530) at 0xcb2d0: ignore the argument and throw the static bad_array_new_length object. Star Wars Racer (PPSA28416) imports it. --- core/libs/prx/libc/CMakeLists.txt | 9 +++++ core/libs/prx/libc/include/Api.hpp | 1 + .../libc/src/exception/StandardExceptions.cpp | 1 + .../tests/GuestThrowBadArrayNewLength.cpp | 33 +++++++++++++++++++ docs/dev/debt/libc.md | 1 + 5 files changed, 45 insertions(+) create mode 100644 core/libs/tests/GuestThrowBadArrayNewLength.cpp diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 43e8f4646b..44d617ed54 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -1079,6 +1079,15 @@ if(BUILD_TESTING) set_tests_properties(guest_underflow_error PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_throw_bad_array_new_length_tests ${LIBS_INCLUDE_DIR}/tests/GuestThrowBadArrayNewLength.cpp) + target_include_directories(guest_throw_bad_array_new_length_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_throw_bad_array_new_length_tests PRIVATE libc) + configure_windows_unwind(guest_throw_bad_array_new_length_tests) + add_test(NAME guest_throw_bad_array_new_length COMMAND guest_throw_bad_array_new_length_tests) + if(WIN32) + set_tests_properties(guest_throw_bad_array_new_length PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() if(WIN32) add_test_executable(exception_personality_tests ${LIBS_INCLUDE_DIR}/tests/ExceptionPersonality.cpp) target_include_directories(exception_personality_tests PRIVATE ${LIBS_INCLUDE_DIR}) diff --git a/core/libs/prx/libc/include/Api.hpp b/core/libs/prx/libc/include/Api.hpp index e4f852ca3b..837e089031 100644 --- a/core/libs/prx/libc/include/Api.hpp +++ b/core/libs/prx/libc/include/Api.hpp @@ -1404,6 +1404,7 @@ int APS5_VABI dl_iterate_phdr_nid_postfix(int (*callback)(dl_phdr_info*, std::si [[noreturn]] void APS5_VABI __cxa_bad_cast_nid_postfix(); [[noreturn]] void APS5_VABI __cxa_bad_typeid_nid_postfix(); [[noreturn]] void APS5_VABI __cxa_throw_bad_array_new_length_nid_postfix(); +[[noreturn]] void APS5_VABI _ZSt6_ThrowRKSt20bad_array_new_length_nid_postfix(const LibcException::ExceptionObject* error); [[noreturn]] void APS5_VABI _ZSt11_Xbad_allocv_nid_postfix(); [[noreturn]] void APS5_VABI _ZSt14_Xout_of_rangePKc_nid_postfix(const char* message); [[noreturn]] void APS5_VABI _ZSt14_Xlength_errorPKc_nid_postfix(const char* message); diff --git a/core/libs/prx/libc/src/exception/StandardExceptions.cpp b/core/libs/prx/libc/src/exception/StandardExceptions.cpp index e6bca47778..7d789bc671 100644 --- a/core/libs/prx/libc/src/exception/StandardExceptions.cpp +++ b/core/libs/prx/libc/src/exception/StandardExceptions.cpp @@ -551,6 +551,7 @@ const char* APS5_VABI _ZNKSt8ios_base7failure4whatEv_nid_postfix(const LibcExcep [[noreturn]] void APS5_VABI __cxa_bad_cast_nid_postfix() { LibcException::ThrowPlain(_ZTVSt8bad_cast_nid_postfix); } [[noreturn]] void APS5_VABI __cxa_bad_typeid_nid_postfix() { LibcException::ThrowPlain(_ZTVSt10bad_typeid_nid_postfix); } [[noreturn]] void APS5_VABI __cxa_throw_bad_array_new_length_nid_postfix() { LibcException::ThrowPlain(_ZTVSt20bad_array_new_length_nid_postfix); } +[[noreturn]] void APS5_VABI _ZSt6_ThrowRKSt20bad_array_new_length_nid_postfix(const LibcException::ExceptionObject*) { LibcException::ThrowPlain(_ZTVSt20bad_array_new_length_nid_postfix); } [[noreturn]] void APS5_VABI _ZSt11_Xbad_allocv_nid_postfix() { LibcException::ThrowPlain(_ZTVSt9bad_alloc_nid_postfix); } [[noreturn]] void APS5_VABI _ZSt14_Xout_of_rangePKc_nid_postfix(const char* message) { LibcException::ThrowMessage(_ZTVSt12out_of_range_nid_postfix, message); } [[noreturn]] void APS5_VABI _ZSt14_Xlength_errorPKc_nid_postfix(const char* message) { LibcException::ThrowMessage(_ZTVSt12length_error_nid_postfix, message); } diff --git a/core/libs/tests/GuestThrowBadArrayNewLength.cpp b/core/libs/tests/GuestThrowBadArrayNewLength.cpp new file mode 100644 index 0000000000..c7514099e7 --- /dev/null +++ b/core/libs/tests/GuestThrowBadArrayNewLength.cpp @@ -0,0 +1,33 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +struct TypeRecord { const void* const* vtable; const char* name; const TypeRecord* base; }; +struct Vtable { std::ptrdiff_t offset; const TypeRecord* type; const void* destroy; const void* deleteObject; const char* (APS5_VABI *what)(const LibcException::ExceptionObject*); }; + +extern "C" { +extern const TypeRecord _ZTISt9bad_alloc_nid_postfix; +extern const TypeRecord _ZTISt20bad_array_new_length_nid_postfix; +extern const Vtable _ZTVSt20bad_array_new_length_nid_postfix; +} + +static void Require(bool value) { if (!value) std::abort(); } + +int main() { + const auto& type = _ZTISt20bad_array_new_length_nid_postfix; + Require(std::strcmp(type.name, "St20bad_array_new_length") == 0); + Require(type.base == &_ZTISt9bad_alloc_nid_postfix); + const auto& table = _ZTVSt20bad_array_new_length_nid_postfix; + Require(table.offset == 0 && table.type == &type && table.what != nullptr); + + bool thrown = false; + try { + _ZSt6_ThrowRKSt20bad_array_new_length_nid_postfix(nullptr); + } catch (...) { + thrown = true; + } + Require(thrown); + return 0; +} diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 1552302c7a..8d5c84280b 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -191,6 +191,7 @@ - [std::clog, std::wclog and the facet ids](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc): `std::clog` and `std::wclog` are 0x400-byte zeroed objects like the rest of the `NSTDSTR` stream set (the console's `libc.prx` sizes `std::clog` at 96 bytes, measured from its dynsym `st_size`), and the `numpunct::id` and `time_put::id` objects are 8-byte zeros like the other facet ids. - [std::__throw_bad_variant_access](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libc) throws `std::bad_variant_access` with the `what()` string `bad_variant_access`, the one [LLVM libc++](https://github.com/llvm/llvm-project/blob/main/libcxx/src/variant.cpp) returns, since the export carries libc++'s `__throw_bad_variant_access` name; the console's string is unverified. Onimusha: Way of the Sword (PPSA27836 01.000.001) imports it from `libc`, and its bundled `libc.prx` does not export it. - [std::bad_optional_access and std::_Throw_bad_optional_access](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libc) provide the exception type, constructor, destructor, and `what()` exports alongside `_Throw_bad_optional_access`, returning the `what()` string `bad_optional_access` matching Dinkumware and libc++ conventions; the console libc.prx string is unverified. PPSA26578 (issue #7624) and PPSA34015 import `_ZSt26_Throw_bad_optional_accessv` (`KwzEip7m6TM`) from `libc`. +- [std::_Throw(const std::bad_array_new_length&)](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libc, NID `1cKlQv7Kvmg`) is a 52-byte function at `0xcb2d0` in the `libc.prx` bundled with PPSA02530: it ignores its argument, allocates the 8-byte exception object with the static `_ZTVSt20bad_array_new_length`, and throws it. Star Wars Racer (PPSA28416) imports it from `libc`. - [The scanf family](../../../core/libs/prx/libc/src/Formatting.cpp) (libc), the `_s` forms included, decodes the input of the 16-bit wide conversions (`%ls`, `%lc`, `%l[`, `%S`, `%C`) on Linux as UTF-8, mirroring the narrow printf family, where FreeBSD uses the locale's `mbrtowc` (one byte per character in the C locale); the console's locale encoding is unverified. Input that is not UTF-8, a character outside the BMP, a scanset member outside ASCII, positional arguments next to a wide conversion and wide conversions in `fscanf` throw. Each character is stored as one 16-bit unit, widths and `_s` sizes count characters, and a `%lc` field cut short by the end of the input keeps what it read, as FreeBSD's `convert_wchar` does. Windows hands `%ls`, `%lc` and `%l[` to the host, whose `wchar_t` is 16 bits - [The scanf family](../../../core/libs/prx/libc/include/WindowsScanning.hpp) (libc) on Windows returns 0, as C17 and FreeBSD do, when input fails after only suppressed conversions have completed (`sscanf("12", "%*d%d", &value)`). Linux hands the format to glibc, which returns `EOF` there. On Windows the host stops a `%x`, `%X`, `%i` or `%p` after the `x` of a `0x` that no hex digit follows (or that the width cuts off), and a float conversion (`%e`, `%f`, `%g`, `%a`) after the `e` of an exponent with no digits (`1e`), where C17 and FreeBSD stop before the `x` or the `e` - [The scanf family](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) on Linux reads a `q` length as `ll`, as glibc does. On Windows a `q` directive ends the scan with nothing stored, as the console's own libc does for `%qd`; FreeBSD 11.4's source reads it as `ll`.