Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions core/libs/prx/libc/src/FormattingWide.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,21 @@ std::u16string FormatWide(const char16_t* format, VaList* source, bool secure =
}
}
std::string length;
if (In(*format, "hljztL")) {
if (In(*format, secure ? "hljztL" : "hljztLq")) {
length += static_cast<char>(*format++);
if ((length == "h" && *format == u'h') || (length == "l" && *format == u'l')) length += static_cast<char>(*format++);
}
const char16_t conversion = *format;
if (!conversion) throw std::invalid_argument("Incomplete format conversion");
if (length == "q") length = "ll";
char16_t conversion = *format;
if (!conversion) {
if (secure) throw std::invalid_argument("Incomplete format conversion");
break;
}
++format;
if (!secure && (conversion == u'D' || conversion == u'O' || conversion == u'U')) {
if (length.empty() || length == "h" || length == "hh") length = "l";
conversion = conversion == u'D' ? u'd' : conversion == u'O' ? u'o' : u'u';
}
const bool integerLength = length.empty() || length == "h" || length == "hh" || length == "l" || length == "ll" ||
length == "j" || length == "z" || length == "t";
if (conversion == u'd' || conversion == u'i') {
Expand Down Expand Up @@ -205,6 +213,13 @@ std::u16string FormatWide(const char16_t* format, VaList* source, bool secure =
else if (length == "h") *static_cast<short*>(pointer) = static_cast<short>(count);
else if (length.empty()) *static_cast<int*>(pointer) = count;
else *static_cast<long long*>(pointer) = count;
} else if (!secure && !In(conversion, "cCdDiaAeEfFgGnoOpsSuUxX #*-+'.0123456789Lhjlqtz$m")) {
std::u16string text(1, conversion);
if (conversion >= 0xd800 && conversion < 0xdc00 && *format >= 0xdc00 && *format < 0xe000) text.push_back(*format++);
const std::size_t pad = width > 1 ? static_cast<std::size_t>(width) - 1 : 0;
if (!left) out.append(pad, u' ');
out += text;
if (left) out.append(pad, u' ');
} else {
throw std::invalid_argument("Unsupported format conversion");
}
Expand Down
38 changes: 37 additions & 1 deletion core/libs/tests/GuestWideFormatting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,40 @@ static void CheckCount() {
Require(Format(buffer, 16, u"a%n", nullCount) < 0, "vswprintf null %n argument");
}

static void CheckFreebsdConversions() {
char16_t buffer[48];
Require(swprintf_nid_postfix(buffer, 48, u"%qd|%qu|%qx|%5qd", -5ll, 7ull, 255ull, 9ll) == 13 &&
buffer == std::u16string(u"-5|7|ff| 9"), "swprintf q length");
Require(swprintf_nid_postfix(buffer, 48, u"%D|%O|%U", 5000000000ll, 8ll, 18446744073709551615ull) == 34 &&
buffer == std::u16string(u"5000000000|10|18446744073709551615"), "swprintf %D %O %U are long conversions");
Require(swprintf_nid_postfix(buffer, 48, u"%hD|%hhU|%5D|%-4O|", 70000ll, 70000ll, -3ll, 8ll) == 23 &&
buffer == std::u16string(u"70000|70000| -3|10 |"), "swprintf %D %O %U ignore h and keep width");
Require(swprintf_nid_postfix(buffer, 48, u"%y|[%5%]|[%-5y]|%€") == 19 &&
buffer == std::u16string(u"y|[ %]|[y ]|€"), "swprintf unknown conversion prints itself");
Require(swprintf_nid_postfix(buffer, 48, u"100%") == 3 && buffer == std::u16string(u"100"), "swprintf trailing percent");
Require(swprintf_nid_postfix(buffer, 48, u"ab%5") == 2 && buffer == std::u16string(u"ab"), "swprintf trailing width");
Require(swprintf_nid_postfix(buffer, 48, u"%d%l", 7) == 1 && buffer == std::u16string(u"7"), "swprintf trailing length");
Require(swprintf_nid_postfix(buffer, 48, u"%") == 0 && buffer[0] == 0, "swprintf lone percent");
Require(Format(buffer, 48, u"%d%", 7) == 1 && buffer == std::u16string(u"7"), "vswprintf trailing percent");
Require(swprintf_nid_postfix(buffer, 48, u"%m") < 0 && buffer[0] == 0, "swprintf %m still fails");
Require(swprintf_nid_postfix(buffer, 48, u"%lqd", 1ll) < 0, "swprintf odd modifier order still fails");
Require(snwprintf_s_nid_postfix(buffer, 48, u"%y") < 0 && buffer[0] == 0, "snwprintf_s unknown conversion");
Require(snwprintf_s_nid_postfix(buffer, 48, u"100%") < 0 && buffer[0] == 0, "snwprintf_s trailing percent");
Require(snwprintf_s_nid_postfix(buffer, 48, u"%D", 1ll) < 0 && buffer[0] == 0, "snwprintf_s %D");
Require(snwprintf_s_nid_postfix(buffer, 48, u"%qd", 5ll) < 0 && buffer[0] == 0, "snwprintf_s %qd");
Require(snwprintf_s_nid_postfix(buffer, 48, u"%qX", -5ll) < 0 && buffer[0] == 0, "snwprintf_s %qX");
Require(snwprintf_s_nid_postfix(buffer, 48, u"%O", 8ll) < 0 && buffer[0] == 0, "snwprintf_s %O");
Require(snwprintf_s_nid_postfix(buffer, 48, u"%U", 8ll) < 0 && buffer[0] == 0, "snwprintf_s %U");
Require(snwprintf_s_nid_postfix(buffer, 48, u"%5%") < 0 && buffer[0] == 0, "snwprintf_s unknown conversion with a width");
Require(swprintf_nid_postfix(buffer, 48, u"%-5😀|") == 7 &&
buffer == std::u16string(u"😀 |"), "swprintf surrogate pair as an unknown conversion");
Require(swprintf_nid_postfix(buffer, 48, u"%5😀|") == 7 &&
buffer == std::u16string(u" 😀|"), "swprintf right-aligned surrogate pair");
Require(swprintf_nid_postfix(buffer, 48, u"%3\xd83d|") == 4 &&
buffer == std::u16string(u" \xd83d|"), "swprintf lone surrogate prints as itself");
}


static void CheckGroupingFlag() {
char16_t buffer[48];
Require(swprintf_nid_postfix(buffer, 48, u"%'d|%'-6u|%'.2f|%'+05d|%'g", 1234567, 42u, 1234.5, 12, 1e6) == 34 &&
Expand All @@ -148,6 +182,7 @@ static void CheckGroupingFlag() {
"snwprintf_s ' flag");
}


static void CheckSwprintf() {
char16_t buffer[8];
for (auto& unit : buffer) unit = u'x';
Expand All @@ -169,7 +204,7 @@ static void CheckSwprintf() {
char16_t wide[16];
Require(swprintf_nid_postfix(wide, 16, u"%5ls|%-3c|%lc", u"wide", 'z', 0x20ac) == 11, "swprintf padded length");
Require(wide == std::u16string(u" wide|z |€"), "swprintf padded output");
Require(swprintf_nid_postfix(wide, 16, u"%q") < 0 && wide[0] == 0, "swprintf invalid conversion");
Require(swprintf_nid_postfix(wide, 16, u"%lp") < 0 && wide[0] == 0, "swprintf invalid conversion");
}

static bool WcsftimeThrows(char16_t* buffer, const char16_t* format, const GuestTm* time) {
Expand Down Expand Up @@ -243,6 +278,7 @@ int main() {
CheckPositional();
CheckPointer();
CheckCount();
CheckFreebsdConversions();
CheckSwprintf();
CheckWcsftime();
Check(u"%.2s", "\xc3\xa9\xc3\xa8", u"\u00e9\u00e8");
Expand Down
1 change: 1 addition & 0 deletions docs/dev/debt/libc.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
- [gethostname](../../../core/libs/prx/libc/src/Hostname.cpp) (libc) reports the fixed guest name `AnyPS5` without querying the host machine name. Buffer copying, null queries and `ENAMETOOLONG` follow [FreeBSD gethostname](https://github.com/freebsd/freebsd-src/blob/main/lib/libc/gen/gethostname.c); the console's configurable hostname and its integration with `sysctl` and name resolution are unverified and not implemented.
- [wcsrtombs_s](../../../core/libs/prx/libc/src/Multibyte.cpp) (libc) follows the console's `libc.prx`, but a runtime-constraint violation only returns its error: the default handler there writes the message and a newline to `stderr` before returning, which is not done here. Conversion uses the C locale of `wcrtomb` (`MB_CUR_MAX` 1), so the path where a multibyte character does not fit the remaining space is not reached
- [The narrow printf family](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) converts 16-bit guest wide arguments to UTF-8 on both platforms, independently of the host locale. It does not use the guest's single-byte `wcrtomb`; the console's treatment of invalid UTF-16 and locale-specific encoding remains unverified. Linux retains host formatting for formats without wide arguments or `%m`, so a narrow `%p` and a NaN there still follow the host (glibc prints a null pointer as `(nil)`, where FreeBSD prints `0x0`, and a NaN with its sign, `-nan`, and with the `+` and space flags, where FreeBSD prints `nan`); argument positions above 99 (FreeBSD's `NL_ARGMAX`) throw when a format requires the guest formatter
- [The wide printf family](../../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - `swprintf`, `vswprintf` and `wprintf` format `%qd`, `%D`, `%O`, `%U`, an unknown conversion character and a trailing `%` as FreeBSD 11.4 [vfwprintf.c](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/libc/stdio/vfwprintf.c) does, except that an unknown conversion character is blank-padded where FreeBSD pads it with zeros for the `0` flag. A surrogate pair in that position counts as one character; a lone surrogate prints as itself. `%m`, a size modifier in the conversion position (`%lqd`) and a modifier on `%p` still fail. `snwprintf_s` keeps rejecting all of these because it is not a FreeBSD function and the console's handling of them is unknown
- [collate<char>::_Getcat](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) builds the facet for the C locale only and throws for any other locale name. The console's `libc.prx` stores pointers to its collation state tables at offsets 0x10 and 0x18 of the facet; ours leaves them null, as only the facet's own functions read them. `do_transform` throws for input with embedded null characters, which the console drops depending on the output buffer size. Its result capacity (`length | 15`) and the over-aligned storage of results of 4080 characters or more follow the console's `libc.prx` code and are not checked on hardware.
- [The scanf family](../../../core/libs/prx/libc/src/Formatting.cpp) (libc), the `_s` forms included, on Linux throws for the 16-bit wide conversions (`%ls`, `%lc`, `%l[`, `%S`, `%C`): glibc would store a 32-bit `wchar_t` per character into the guest's 16-bit buffer. 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`
Expand Down
Loading