Files
AnyPS5/core/libs/tests/GuestWideFormatting.cpp
T
Dean 57cfda019b Merge pull request #11214 from orezraey/fix/libc-wide-lp-measured-ps5
fix(libc): print %lp in the wide formatter as measured on a PS5
2026-11-02 23:27:08 +13:00

485 lines
28 KiB
C++

#include "prx/libc/include/Api.hpp"
#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libc/include/GuestTime.hpp"
#include <climits>
#include <cerrno>
#include <cmath>
#include <cstdarg>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <stdexcept>
#include <string>
static int APS5_VABI Format(char16_t* buffer, std::size_t size, const char16_t* format, ...) {
#ifdef _WIN32
__builtin_sysv_va_list args;
__builtin_sysv_va_start(args, format);
#else
std::va_list args;
va_start(args, format);
#endif
const int result = vswprintf_nid_postfix(buffer, size, format, reinterpret_cast<VaList*>(args));
#ifdef _WIN32
__builtin_sysv_va_end(args);
#else
va_end(args);
#endif
return result;
}
static void Check(const char16_t* format, const char* input, const char16_t* expected) {
char16_t buffer[32]{};
const int result = Format(buffer, 32, format, input);
const std::u16string wanted(expected);
if (result != static_cast<int>(wanted.size()) || buffer != wanted) {
std::fprintf(stderr, "wide precision: expected %zu units, got %d\n", wanted.size(), result);
std::abort();
}
}
static void Require(bool condition, const char* message) {
if (!condition) {
std::fprintf(stderr, "snwprintf_s: %s\n", message);
std::abort();
}
}
static void CheckBounded() {
constexpr std::size_t rsizeMax = SIZE_MAX >> 1;
const char16_t* const nullWide = nullptr;
const char* const nullNarrow = nullptr;
char16_t buffer[16];
buffer[0] = u'x';
Require(snwprintf_s_nid_postfix(buffer, 16, u"%d-%ls-%s", 42, u"ab", "\xc3\xa9") == 7, "complete output length");
Require(buffer == std::u16string(u"42-ab-\u00e9"), "complete output");
buffer[5] = u'x';
Require(snwprintf_s_nid_postfix(buffer, 5, u"%d-%ls", 42, u"abcd") == 7, "truncated output length");
Require(buffer == std::u16string(u"42-a") && buffer[5] == u'x', "truncated output");
buffer[0] = u'x';
buffer[1] = u'y';
Require(snwprintf_s_nid_postfix(buffer, 1, u"%x", 0xabcu) == 3, "one unit buffer length");
Require(buffer[0] == 0 && buffer[1] == u'y', "one unit buffer");
buffer[0] = u'x';
Require(snwprintf_s_nid_postfix(buffer, 16, u"") == 0 && buffer[0] == 0, "empty format");
buffer[0] = u'x';
Require(snwprintf_s_nid_postfix(buffer, 16, nullptr) < 0 && buffer[0] == 0, "null format");
Require(snwprintf_s_nid_postfix(nullptr, 16, u"a") < 0, "null buffer");
buffer[0] = u'x';
Require(snwprintf_s_nid_postfix(buffer, 0, u"a") < 0 && buffer[0] == u'x', "zero size");
buffer[0] = u'x';
Require(snwprintf_s_nid_postfix(buffer, rsizeMax + 1, u"a") < 0 && buffer[0] == u'x', "size above RSIZE_MAX");
int written = -7;
buffer[0] = u'x';
Require(snwprintf_s_nid_postfix(buffer, 16, u"ab%n", &written) < 0 && buffer[0] == 0 && written == -7, "%n");
buffer[0] = u'x';
Require(snwprintf_s_nid_postfix(buffer, 16, u"a%ls", nullWide) < 0 && buffer[0] == 0, "null %ls argument");
buffer[0] = u'x';
Require(snwprintf_s_nid_postfix(buffer, 16, u"a%s", nullNarrow) < 0 && buffer[0] == 0, "null %s argument");
Require(Format(buffer, 16, u"%s", nullNarrow) == 6 && buffer == std::u16string(u"(null)"), "vswprintf null %s argument");
}
static void CheckPositional() {
char16_t buffer[32];
Require(swprintf_nid_postfix(buffer, 32, u"%2$d-%1$d", 1, 2) == 3 && buffer == std::u16string(u"2-1"),
"swprintf positional integers");
Require(swprintf_nid_postfix(buffer, 32, u"%2$ls %1$ls", u"world", u"hello") == 11 && buffer == std::u16string(u"hello world"),
"swprintf positional wide strings");
Require(swprintf_nid_postfix(buffer, 32, u"[%1$*2$d|%3$.*2$f]", 7, 2, 2.5) == 9 && buffer == std::u16string(u"[ 7|2.50]"),
"swprintf positional width and precision");
Require(swprintf_nid_postfix(buffer, 32, u"%1$Lg %2$d %d", 0.5L, 3, 9) == 7 && buffer == std::u16string(u"0.5 3 9"),
"swprintf positional long double and the next argument");
Require(snwprintf_s_nid_postfix(buffer, 32, u"%2$s%1$lc", 0x20ac, "\xc3\xa9") == 2 && buffer == std::u16string(u"\u00e9\u20ac"),
"snwprintf_s positional arguments");
}
static void CheckPointer() {
char16_t buffer[64];
const auto address = [](std::uintptr_t value) { return reinterpret_cast<void*>(value); };
Require(Format(buffer, 64, u"%p|%p|%p", address(0), address(0x1234), address(0xdeadbeef12)) == 17 &&
buffer == std::u16string(u"0|1234|deadbeef12"), "vswprintf %p uses the console form");
Require(Format(buffer, 64, u"[%10p][%-10p][%.8p][%08p]", address(0x1234), address(0x1234), address(0x1234), address(0x1a)) == 44 &&
buffer == std::u16string(u"[ 1234][1234 ][00001234][0000001a]"), "vswprintf %p width and precision");
Require(Format(buffer, 64, u"[%.0p][%p]", address(0), address(0)) == 5 &&
buffer == std::u16string(u"[][0]"), "vswprintf %p with a zero precision prints nothing");
Require(Format(buffer, 64, u"[%lp]", address(0x1234)) == 6 && buffer == std::u16string(u"[1234]"), "vswprintf %lp prints the pointer as %p does");
Require(Format(buffer, 64, u"[%lp]", address(0)) == 3 && buffer == std::u16string(u"[0]"), "vswprintf %lp of a null pointer");
Require(Format(buffer, 64, u"[%5.0p][%05.3p][%.0p]", address(0), address(0x1f), address(0x1f)) == 18 &&
buffer == std::u16string(u"[ ][ 01f][1f]"), "vswprintf %p width beside a zero precision, 0 beside a precision, and %.0p of a pointer");
Require(snwprintf_s_nid_postfix(buffer, 16, u"[%lp]", address(0x1f)) == 4 && buffer == std::u16string(u"[1f]"), "snwprintf_s %lp prints the pointer as %p does");
Require(snwprintf_s_nid_postfix(buffer, 16, u"[%lp]", address(0)) == 3 && buffer == std::u16string(u"[0]"), "snwprintf_s %lp of a null pointer");
}
static void CheckCount() {
char16_t buffer[16];
int count = -7;
Require(Format(buffer, 16, u"ab%ncd", &count) == 4 && buffer == std::u16string(u"abcd") && count == 2, "vswprintf %n");
count = -7;
Require(Format(buffer, 16, u"%s%n!", "\xed\xa0\xbd\xed\xb8\x80", &count) == 3 && count == 2, "vswprintf %n counts UTF-16 units");
count = -7;
Require(Format(buffer, 16, u"%n", &count) == 0 && buffer[0] == 0 && count == 0, "vswprintf %n at start");
unsigned char bytes[2] = {0xaa, 0xaa};
Require(Format(buffer, 16, u"%3d%hhn", 5, bytes) == 3 && bytes[0] == 3 && bytes[1] == 0xaa, "vswprintf %hhn");
unsigned short halves[2] = {0xaaaa, 0xaaaa};
Require(Format(buffer, 16, u"%5d%hn", 5, halves) == 5 && halves[0] == 5 && halves[1] == 0xaaaa, "vswprintf %hn");
long long wide = -1;
Require(Format(buffer, 16, u"%4d%lln", 5, &wide) == 4 && wide == 4, "vswprintf %lln");
wide = -1;
Require(Format(buffer, 16, u"%2d%ln", 5, &wide) == 2 && wide == 2, "vswprintf %ln");
count = -7;
Require(Format(buffer, 16, u"a%5n", &count) < 0 && count == -7, "vswprintf %n with a width");
Require(Format(buffer, 16, u"a%Ln", &count) == 1 && count == 1, "vswprintf %Ln writes an int");
char16_t lengths[64];
Require(Format(lengths, 64, u"%Ld|%Lx|%hf|%zg|%hp|%Lc", 0x41424344, 0x89abcdefu, 1.5, 1.5, reinterpret_cast<void*>(0x1234), 0x44) == 39 &&
lengths == std::u16string(u"1094861636|89abcdef|1.500000|1.5|1234|D"), "vswprintf ignores lengths that do not apply");
const int* const nullCount = nullptr;
Require(Format(buffer, 16, u"a%n", nullCount) < 0, "vswprintf null %n argument");
}
static void CheckUnsignedSignFlags() {
char16_t buffer[32];
Require(swprintf_nid_postfix(buffer, 32, u"[% u|%+u|% lu|% 5u|%+x|% #o]", 7u, 7u, 7ul, 7u, 7u, 7u) == 18 &&
buffer == std::u16string(u"[7|7|7| 7|7|07]"), "unsigned conversions ignore the space and plus flags");
}
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 &&
buffer == std::u16string(u"1234567|42 |1234.50|+0012|1e+06"), "swprintf ' flag");
Require(snwprintf_s_nid_postfix(buffer, 48, u"%'lld %ls", 9876543210ll, u"x") == 12 && buffer == std::u16string(u"9876543210 x"),
"snwprintf_s ' flag");
}
static void CheckSecureWideBoundary() {
constexpr std::size_t wideRsizeMax = (SIZE_MAX >> 1) / sizeof(char16_t);
char16_t buffer[4];
for (auto& unit : buffer) unit = u'x';
buffer[0] = u'x';
buffer[1] = u'y';
Require(swprintf_s_nid_postfix(buffer, wideRsizeMax, u"%n") < 0 && buffer[0] == 0, "swprintf_s accepted-boundary failure clears");
Require(buffer[1] == u'y', "swprintf_s does not clear beyond the first unit");
buffer[0] = u'x';
buffer[1] = u'y';
Require(swprintf_s_nid_postfix(buffer, wideRsizeMax + 1, u"%n") < 0 && buffer[0] == u'x' && buffer[1] == u'y',
"swprintf_s oversized size leaves the buffer untouched");
buffer[0] = u'x';
buffer[1] = u'y';
Require(vswprintf_s_nid_postfix(buffer, wideRsizeMax, u"%n", nullptr) < 0 && buffer[0] == 0, "vswprintf_s accepted-boundary failure clears");
buffer[0] = u'x';
buffer[1] = u'y';
Require(vswprintf_s_nid_postfix(buffer, wideRsizeMax + 1, u"%n", nullptr) < 0 && buffer[0] == u'x' && buffer[1] == u'y',
"vswprintf_s oversized size leaves the buffer untouched");
buffer[0] = u'x';
buffer[1] = u'y';
Require(vsnwprintf_s_nid_postfix(buffer, wideRsizeMax, u"%n", nullptr) < 0 && buffer[0] == 0, "vsnwprintf_s accepted-boundary failure clears");
buffer[0] = u'x';
buffer[1] = u'y';
Require(vsnwprintf_s_nid_postfix(buffer, wideRsizeMax + 1, u"%n", nullptr) < 0 && buffer[0] == u'x' && buffer[1] == u'y',
"vsnwprintf_s oversized size leaves the buffer untouched");
}
static void CheckFloat() {
char16_t buffer[64];
Require(swprintf_nid_postfix(buffer, 64, u"[%.0f|%.1f|%#.3g|%.20f]", 2.5, 0.25, 2025.0, 0.1) == 39 &&
buffer == std::u16string(u"[3|0.3|2.03e+03|0.10000000000000000555]"), "swprintf rounds and caps decimal digits");
Require(swprintf_nid_postfix(buffer, 64, u"[%.0f|%-8.2e]", 67108865.5, 1125.0) == 19 && buffer == std::u16string(u"[67108865|1.13e+03]"),
"swprintf rounds a large tie down");
Require(swprintf_nid_postfix(buffer, 64, u"[%a|%.3a]", 0x1p-1074, 0x0.fffffffffffffp-1022) == 25 &&
buffer == std::u16string(u"[0x1p-1074|0x2.000p-1023]"), "swprintf normalises subnormal hexadecimal floats");
Require(swprintf_nid_postfix(buffer, 64, u"[%.2Lf]", 0.125L) == 6 && buffer == std::u16string(u"[0.13]"), "swprintf long double rounding");
Require(swprintf_nid_postfix(buffer, 64, u"[%La|%.0La]", 0.1L, 1.5L) == 32 && buffer == std::u16string(u"[0x1.999999999999999ap-4|0x2p+0]"),
"swprintf long double hexadecimal");
}
static void CheckSwprintf() {
char16_t buffer[8];
for (auto& unit : buffer) unit = u'x';
Require(swprintf_nid_postfix(buffer, 8, u"%d-%ls", 42, u"ab") == 5, "swprintf length");
Require(buffer == std::u16string(u"42-ab") && buffer[6] == u'x', "swprintf output");
Require(swprintf_nid_postfix(buffer, 8, u"中%ls%s", u"文", "\xc3\xa9") == 3, "swprintf non-ASCII length");
Require(buffer == std::u16string(u"中文é"), "swprintf non-ASCII output");
Require(swprintf_nid_postfix(buffer, 8, u"%s", "\xed\xa0\xbd\xed\xb8\x80") == 2 && buffer == std::u16string(u"\U0001f600"),
"swprintf decodes each 3-byte surrogate");
errno = 0;
Require(swprintf_nid_postfix(buffer, 8, u"[%s]", "\xf0\x9f\x98\x80") == -1 && errno == 86 && buffer == std::u16string(u"["),
"swprintf rejects a 4-byte sequence and keeps the output before it");
for (const char* invalid : {"\x80", "\xc3", "\xc3x", "\xe2\x82", "\xff", "\xf8\x88\x80\x80\x80", "\xc3\xa9\xc3"}) {
errno = 0;
Require(swprintf_nid_postfix(buffer, 8, u"x%sy", invalid) == -1 && errno == 86 && buffer == std::u16string(u"x"), "swprintf rejects invalid UTF-8");
}
Require(swprintf_nid_postfix(buffer, 8, u"[%s]", "\xc0\x80" "A") == 2 && buffer == std::u16string(u"[]"), "an overlong NUL ends the string");
Require(swprintf_nid_postfix(buffer, 8, u"[%s|%s]", "\xc1\x81", "\xe0\x81\x81") == 5 && buffer == std::u16string(u"[A|A]"), "overlong forms decode");
Require(swprintf_nid_postfix(buffer, 8, u"[%.1s]", "A\x80") == 3 && buffer == std::u16string(u"[A]"), "the precision stops before an invalid byte");
errno = 0;
Require(swprintf_nid_postfix(buffer, 8, u"[%.2s]", "A\x80") == -1 && errno == 86, "the precision reaches an invalid byte");
Require(swprintf_nid_postfix(buffer, 8, u"[%.0s]", "AB") == 2 && buffer == std::u16string(u"[]"), "precision zero writes nothing");
Require(swprintf_nid_postfix(buffer, 8, u"[%3s|%-3.0s]", "", "AB") == 3 && buffer == std::u16string(u"[|]"), "an empty narrow field is not padded");
Require(swprintf_nid_postfix(buffer, 8, u"[%3s]", "A\xc0\x80") == 5 && buffer == std::u16string(u"[ A]"), "a field with characters is padded");
errno = 0;
Require(swprintf_nid_postfix(buffer, 8, u"[%.0s]", "\x80") == -1 && errno == 86, "precision zero still decodes the first character");
Require(swprintf_nid_postfix(buffer, 8, u"") == 0 && buffer[0] == 0, "swprintf empty format");
for (auto& unit : buffer) unit = u'x';
Require(swprintf_nid_postfix(buffer, 3, u"%ls", u"abcd") < 0, "swprintf truncation result");
Require(buffer == std::u16string(u"ab") && buffer[3] == u'x', "swprintf truncation output");
Require(swprintf_nid_postfix(buffer, 3, u"abc") < 0, "swprintf terminator does not fit");
buffer[0] = u'x';
Require(swprintf_nid_postfix(buffer, 0, u"a") < 0 && buffer[0] == u'x', "swprintf zero size");
Require(swprintf_nid_postfix(nullptr, 8, u"a") < 0, "swprintf null buffer");
Require(swprintf_nid_postfix(buffer, 5, u"%ls", u"abcd") == 4 && buffer == std::u16string(u"abcd"), "swprintf exact fit");
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");
for (const char16_t* format : {u"%99999999999d", u"%.99999999999d", u"%4294967296s", u"%.4294967296s", u"%2147483648ls", u"%.2147483648ls"}) {
wide[0] = u'x';
Require(swprintf_nid_postfix(wide, 16, format, 7, u"a") < 0 && wide[0] == 0, "swprintf width or precision beyond int");
}
wide[0] = u'x';
Require(swprintf_nid_postfix(wide, 16, u"%*d", INT_MIN, 7) < 0 && wide[0] == 0, "swprintf INT_MIN width");
Require(swprintf_nid_postfix(wide, 16, u"%*d", -5, 7) == 5 && wide == std::u16string(u"7 "), "swprintf negative width");
}
static bool WcsftimeThrows(char16_t* buffer, const char16_t* format, const GuestTm* time) {
try {
wcsftime_nid_postfix(reinterpret_cast<std::uint16_t*>(buffer), 16, reinterpret_cast<const std::uint16_t*>(format), time);
} catch (const std::invalid_argument&) {
return true;
}
return false;
}
static void CheckWcsftimeCapacities(const char16_t* format, const std::u16string& expected, const GuestTm& time) {
for (std::size_t capacity = 0; capacity <= expected.size() + 2; ++capacity) {
std::u16string storage(expected.size() + 5, u'\uffff');
int& error = *__error_nid_postfix();
error = 5;
const std::size_t result = wcsftime_nid_postfix(reinterpret_cast<std::uint16_t*>(storage.data() + 1), capacity, reinterpret_cast<const std::uint16_t*>(format), &time);
Require(error == 5 && result < (capacity ? capacity : 1), "wcsftime bounded result and errno");
Require(storage[0] == u'\uffff', "wcsftime leading guard");
if (capacity > expected.size()) {
Require(result == expected.size(), "wcsftime complete result");
for (std::size_t index = 0; index < expected.size(); ++index)
Require(storage[index + 1] == expected[index], "wcsftime complete output");
Require(storage[expected.size() + 1] == 0, "wcsftime terminator");
}
for (std::size_t index = capacity + 1; index < storage.size(); ++index)
Require(storage[index] == u'\uffff', "wcsftime trailing guard");
}
}
static void CheckWcsftimeFirmwarePieces() {
GuestTm time{};
time.tm_year = 124;
time.tm_mon = 1;
time.tm_mday = 29;
time.tm_wday = 4;
time.tm_yday = 59;
time.tm_hour = 13;
time.tm_min = 5;
time.tm_sec = 9;
struct Case { const char16_t* format; std::size_t capacity; std::size_t result; std::u16string units; };
const Case cases[] = {
{u"%Y-%m-%d", 0, 0, u""}, {u"%Y-%m-%d", 1, 0, u"-"},
{u"%Y-%m-%d", 2, 0, u"--"}, {u"%Y-%m-%d", 3, 0, u"-02"},
{u"%Y-%m-%d", 4, 0, u"2024"}, {u"%Y-%m-%d", 5, 0, u"2024-"},
{u"%Y-%m-%d", 6, 0, u"2024--"}, {u"%Y-%m-%d", 7, 0, u"2024-02"},
{u"%Y-%m-%d", 8, 0, u"2024-02-"},
{u"%Y-%m-%d", 9, 8, std::u16string(u"2024-02-\0", 9)},
{u"%Y-%m-%d", 10, 0, u"2024-02-29"},
{u"%Y-%m-%d", 11, 10, std::u16string(u"2024-02-29\0", 11)},
{u"%F", 9, 0, std::u16string(u"\0" u"024-02-2", 9)},
{u"%F|%S|", 5, 4, std::u16string(u"|09|\0", 5)},
{u"ab%Ycd", 5, 4, std::u16string(u"abcd\0", 5)},
{u"%OYx", 3, 1, std::u16string(u"x\0" u"2", 3)},
{u"abc", 2, 0, u"ab"}, {u"", 1, 0, std::u16string(1, 0)},
{u"%Q", 1, 0, std::u16string(1, 0)},
{u"\U0001f600%Y", 3, 2, std::u16string(u"\U0001f600\0", 3)},
{u"%Y\U0001f600", 3, 2, std::u16string(u"\U0001f600\0", 3)}
};
for (const auto& item : cases) {
std::u16string storage(item.capacity + 4, u'\ua5a5');
int& error = *__error_nid_postfix();
error = 5;
const auto result = wcsftime_nid_postfix(reinterpret_cast<std::uint16_t*>(storage.data() + 1), item.capacity,
reinterpret_cast<const std::uint16_t*>(item.format), &time);
Require(result == item.result && error == 5, "wcsftime firmware piece result and errno");
Require(storage[0] == u'\ua5a5', "wcsftime firmware leading guard");
for (std::size_t index = 0; index < item.units.size(); ++index)
Require(storage[index + 1] == item.units[index], "wcsftime firmware piece units");
for (std::size_t index = item.units.size() + 1; index < storage.size(); ++index)
Require(storage[index] == u'\ua5a5', "wcsftime firmware untouched units");
}
time.tm_mon = 8;
time.tm_wday = 5;
char16_t buffer[12];
for (auto& unit : buffer) unit = u'\ua5a5';
int& error = *__error_nid_postfix();
error = 5;
Require(wcsftime_nid_postfix(reinterpret_cast<std::uint16_t*>(buffer), 9,
reinterpret_cast<const std::uint16_t*>(u"%A %B"), &time) == 7 && error == 5
&& std::u16string(buffer, 9) == std::u16string(u"Friday \0e", 9) && buffer[9] == u'\ua5a5', "wcsftime firmware trailing partial conversion");
}
static void CheckWcsftime() {
const char* oldZone = getenv_nid_postfix("TZ");
const bool hadZone = oldZone != nullptr;
const std::string savedZone = hadZone ? oldZone : "";
Require(setenv_nid_postfix("TZ", "UTC0", 1) == 0, "wcsftime fixed guest time zone");
CheckWcsftimeFirmwarePieces();
GuestTm time{};
time.tm_sec = 9;
time.tm_min = 5;
time.tm_hour = 13;
time.tm_mday = 29;
time.tm_mon = 1;
time.tm_year = 124;
time.tm_wday = 4;
time.tm_yday = 59;
time.tm_zone = "UTC";
struct Case {
const char16_t* wide;
const char* narrow;
};
const Case cases[] = {
{u"%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M:%S"},
{u"%a %b %j %p %Z", "%a %b %j %p %Z"},
{u"%%", "%%"},
{u"%k|%l|%v|%+", "%k|%l|%v|%+"},
{u"%A|%B|%h|%C|%e|%I|%n|%t|%U|%u|%V|%G|%g|%W|%w|%y|%z", "%A|%B|%h|%C|%e|%I|%n|%t|%U|%u|%V|%G|%g|%W|%w|%y|%z"},
{u"%s", "%s"},
{u"\u00e9t\u00e9 %Y", "\xe9t\xe9 %Y"},
};
for (const auto& item : cases) {
char narrow[128];
char16_t wide[128];
const std::size_t length = strftime_nid_postfix(narrow, sizeof(narrow), item.narrow, &time);
Require(length != 0, "wcsftime reference strftime");
Require(wcsftime_nid_postfix(reinterpret_cast<std::uint16_t*>(wide), 128, reinterpret_cast<const std::uint16_t*>(item.wide), &time) == length, "wcsftime length matches strftime");
for (std::size_t index = 0; index <= length; ++index)
Require(wide[index] == static_cast<unsigned char>(narrow[index]), "wcsftime output matches strftime");
CheckWcsftimeCapacities(item.wide, std::u16string(wide, length), time);
}
CheckWcsftimeCapacities(u"", u"", time);
CheckWcsftimeCapacities(u"abc", u"abc", time);
CheckWcsftimeCapacities(u"\u4e2d\u20ac\U0001f600%F!", u"\u4e2d\u20ac\U0001f6002024-02-29!", time);
const char16_t surrogates[] = {0xd800, u'%', u'Y', 0xdc00, 0};
const char16_t surrogateOutput[] = {0xd800, u'2', u'0', u'2', u'4', 0xdc00, 0};
CheckWcsftimeCapacities(surrogates, surrogateOutput, time);
CheckWcsftimeCapacities(u"%c|%D|%F|%r|%R|%T|%x|%X", u"Thu Feb 29 13:05:09 2024|02/29/24|2024-02-29|01:05:09 PM|13:05|13:05:09|02/29/24|13:05:09", time);
CheckWcsftimeCapacities(u"%Ea|%OX|%EEY|%-d|%_m|%0e|%--d|%q|%\u4e2d|%", u"Thu|13:05:09|%EY|%-d|%_m|%0e|%--d|%q|%\u4e2d|%", time);
GuestTm early = time;
early.tm_year = -1901;
CheckWcsftimeCapacities(u"%Y/%C/%y", u"0000/00/99", early);
char16_t buffer[32];
Require(wcsftime_nid_postfix(reinterpret_cast<std::uint16_t*>(buffer), 32, reinterpret_cast<const std::uint16_t*>(u"%Y-%m-%d %H:%M:%S"), &time) == 19
&& buffer == std::u16string(u"2024-02-29 13:05:09"), "wcsftime date and time");
Require(wcsftime_nid_postfix(reinterpret_cast<std::uint16_t*>(buffer), 32, reinterpret_cast<const std::uint16_t*>(u"%a %b %j %p"), &time) == 14
&& buffer == std::u16string(u"Thu Feb 060 PM"), "wcsftime names");
Require(wcsftime_nid_postfix(reinterpret_cast<std::uint16_t*>(buffer), 32, reinterpret_cast<const std::uint16_t*>(u"%%"), &time) == 1 && buffer == std::u16string(u"%"), "wcsftime percent");
Require(wcsftime_nid_postfix(reinterpret_cast<std::uint16_t*>(buffer), 20, reinterpret_cast<const std::uint16_t*>(u"%Y-%m-%d %H:%M:%S"), &time) == 19, "wcsftime exact fit");
int& error = *__error_nid_postfix();
for (auto& unit : buffer) unit = u'x';
error = 5;
Require(wcsftime_nid_postfix(reinterpret_cast<std::uint16_t*>(buffer), 19, reinterpret_cast<const std::uint16_t*>(u"%Y-%m-%d %H:%M:%S"), &time) == 0 && error == 5
&& std::u16string(buffer, 19) == u"2024-02-29 13:05:09" && buffer[19] == u'x', "wcsftime overflow");
Require(wcsftime_nid_postfix(reinterpret_cast<std::uint16_t*>(buffer), 1, reinterpret_cast<const std::uint16_t*>(u"%Y"), &time) == 0 && error == 5 && buffer[0] == 0, "wcsftime one unit");
buffer[0] = u'x';
Require(wcsftime_nid_postfix(reinterpret_cast<std::uint16_t*>(buffer), 0, reinterpret_cast<const std::uint16_t*>(u"%Y"), &time) == 0 && error == 5 && buffer[0] == u'x', "wcsftime zero size");
Require(wcsftime_nid_postfix(reinterpret_cast<std::uint16_t*>(buffer), 32, reinterpret_cast<const std::uint16_t*>(u""), &time) == 0 && error == 5 && buffer[0] == 0, "wcsftime empty format");
Require(wcsftime_nid_postfix(nullptr, 32, reinterpret_cast<const std::uint16_t*>(u"%Y"), &time) == 0 && error == 5, "wcsftime null buffer");
Require(wcsftime_nid_postfix(reinterpret_cast<std::uint16_t*>(buffer), 32, reinterpret_cast<const std::uint16_t*>(u"%Y \u20ac"), &time) == 6 && buffer == std::u16string(u"2024 \u20ac"),
"wcsftime copies a character above 0xff");
Require(wcsftime_nid_postfix(reinterpret_cast<std::uint16_t*>(buffer), 7, reinterpret_cast<const std::uint16_t*>(u"%Y \u20ac"), &time) == 6, "wcsftime counts wide units");
for (auto& unit : buffer) unit = u'x';
error = 5;
const std::size_t oversized = SIZE_MAX / static_cast<std::size_t>(_Getmbcurmax_nid_postfix());
for (const std::size_t capacity : {oversized, oversized + 1, SIZE_MAX}) {
error = 5;
Require(wcsftime_nid_postfix(reinterpret_cast<std::uint16_t*>(buffer), capacity, reinterpret_cast<const std::uint16_t*>(u"%Y"), &time) == 0 && error == 22 && buffer[0] == u'x',
"wcsftime oversized buffer");
}
error = 5;
Require(wcsftime_nid_postfix(reinterpret_cast<std::uint16_t*>(buffer), oversized - 1, reinterpret_cast<const std::uint16_t*>(u"%Y"), &time) == 4 && error == 5
&& buffer == std::u16string(u"2024") && buffer[5] == u'x', "wcsftime largest accepted capacity");
Require(WcsftimeThrows(buffer, nullptr, &time), "wcsftime null format");
Require(WcsftimeThrows(buffer, u"%Y", nullptr), "wcsftime null time");
Require((hadZone ? setenv_nid_postfix("TZ", savedZone.c_str(), 1) : unsetenv_nid_postfix("TZ")) == 0, "wcsftime restore guest time zone");
}
static void CheckAlternateOctalZero() {
struct Case { const char16_t* format; const char16_t* expected; unsigned value; };
for (const auto& test : {
Case{u"%#.0o", u"0", 0}, Case{u"%#.o", u"0", 0}, Case{u"%#.00o", u"0", 0},
Case{u"[%#5.0o]", u"[ 0]", 0}, Case{u"[%-#5.0o]", u"[0 ]", 0},
Case{u"[%#05.0o]", u"[ 0]", 0}, Case{u"%.0o", u"", 0}, Case{u"%#.0x", u"", 0},
Case{u"%#.0o", u"01", 1}, Case{u"%#.1o", u"0", 0}, Case{u"%#.3o", u"000", 0},
Case{u"%#.0hho", u"0", 256}, Case{u"%#.0ho", u"0", 65536}
}) {
char16_t buffer[32];
const auto expected = std::u16string(test.expected);
Require(swprintf_nid_postfix(buffer, 32, test.format, test.value) == static_cast<int>(expected.size()) && buffer == expected, "swprintf alternate octal zero");
Require(Format(buffer, 32, test.format, test.value) == static_cast<int>(expected.size()) && buffer == expected, "vswprintf alternate octal zero");
Require(snwprintf_s_nid_postfix(buffer, 32, test.format, test.value) == static_cast<int>(expected.size()) && buffer == expected, "snwprintf_s alternate octal zero");
}
char16_t buffer[16];
Require(swprintf_nid_postfix(buffer, 16, u"%#.*o", 0, 0u) == 1 && buffer == std::u16string(u"0"), "octal zero dynamic precision");
Require(swprintf_nid_postfix(buffer, 16, u"%#.*o", -1, 0u) == 1 && buffer == std::u16string(u"0"), "octal zero omitted precision");
Require(swprintf_nid_postfix(buffer, 16, u"%2$#.*1$o", 0, 0u) == 1 && buffer == std::u16string(u"0"), "octal zero positional precision");
Require(swprintf_nid_postfix(buffer, 16, u"%#.0llo", 0ull) == 1 && buffer == std::u16string(u"0"), "octal zero long long");
buffer[0] = u'x';
buffer[1] = u'y';
Require(snwprintf_s_nid_postfix(buffer, 1, u"%#.0o", 0u) == 1 && buffer[0] == 0 && buffer[1] == u'y', "octal zero bounded output");
}
static void CheckNanSign() {
char16_t buffer[64];
Require(swprintf_nid_postfix(buffer, 64, u"[%f][%+f][% F][%5.1e]", -NAN, -NAN, NAN, -NAN) == 25, "NaN sign length");
Require(buffer == std::u16string(u"[-nan][-nan][ NAN][ -nan]"), "NaN sign");
Require(swprintf_nid_postfix(buffer, 64, u"[%-6g][%06a][%LG][%+f][%+G]", NAN, -NAN, -static_cast<long double>(NAN), -INFINITY, NAN) == 34, "NaN flags length");
Require(buffer == std::u16string(u"[nan ][ -nan][-NAN][-inf][+NAN]"), "NaN flags");
}
int main() {
CheckAlternateOctalZero();
CheckBounded();
CheckNanSign();
CheckPositional();
CheckPointer();
CheckCount();
CheckSwprintf();
CheckFloat();
CheckSecureWideBoundary();
CheckWcsftime();
Check(u"%.2s", "\xc3\xa9\xc3\xa8", u"\u00e9\u00e8");
Check(u"%.1s", "\xc3\xa9\xc3\xa8", u"\u00e9");
Check(u"%.0s", "\xc3\xa9", u"");
Check(u"%.3s", "\xc3\xa9", u"\u00e9");
Check(u"%s", "\xc3\xa9\xc3\xa8", u"\u00e9\u00e8");
Check(u"%.2s", "abcd", u"ab");
Check(u"%4.2s", "\xc3\xa9\xc3\xa8", u" \u00e9\u00e8");
Check(u"%-4.2s", "\xc3\xa9\xc3\xa8", u"\u00e9\u00e8 ");
Check(u"%.2s", "\xed\xa0\xbd\xed\xb8\x80x", u"\U0001f600");
Check(u"%.1s", "\xed\xa0\xbd\xed\xb8\x80x", u"\xd83d");
Check(u"%.3s", "\xed\xa0\xbd\xed\xb8\x80x", u"\U0001f600x");
CheckGroupingFlag();
CheckUnsignedSignFlags();
}