From 887a258152a32dde7913ed9ba892f073147cc3bc Mon Sep 17 00:00:00 2001 From: Raj Date: Thu, 8 Oct 2026 11:40:37 +0530 Subject: [PATCH 1/5] fix(libc): read every variadic argument of the Linux snprintf, sprintf, sscanf and libc_printf On Linux these four exports took the guest's registers as named parameters and rebuilt a va_list from them. The register save area held only the argument registers after the fixed ones, followed by zeros, with gp_offset 0, so va_arg read the zeros for the remaining integer slots before moving to the overflow area. overflow_arg_area was the seventh integer parameter, which is the value of the first stack argument, not its address. snprintf(buffer, size, "%d %d %d %d", 1, 2, 3, 4) produced "1 2 3 0", "%s/%s/%s/%s" with four strings produced "a/b/c/(null)", and eight integers dereferenced the value 4 and crashed. sprintf and sscanf lost their fifth and sixth arguments, libc_printf its sixth. They are now variadic functions that use va_start, like printf, sprintf_s, snprintf_s and printf_s on Linux already do. --- core/libs/prx/libc/src/Formatting.cpp | 70 +++++++++------------------ 1 file changed, 24 insertions(+), 46 deletions(-) diff --git a/core/libs/prx/libc/src/Formatting.cpp b/core/libs/prx/libc/src/Formatting.cpp index 875bdf156..c22ae7c4a 100644 --- a/core/libs/prx/libc/src/Formatting.cpp +++ b/core/libs/prx/libc/src/Formatting.cpp @@ -274,44 +274,28 @@ int APS5_VABI printf_nid_postfix(const char* format, ...) { return result; } -int APS5_VABI libc_printf_nid_postfix(VA_ARGS) { - (void)rcx; (void)r8; (void)r9; - LibcDetail::RegSaveArea regs; - LibcDetail::FillRegSaveArea(regs, rsi, rdx, rcx, r8, r9, 0, - xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7); - LibcDetail::VaListLayout layout; - std::va_list* va = LibcDetail::BuildVaList(layout, regs, 0u, - reinterpret_cast(overflow_arg_area)); - return std::vprintf(reinterpret_cast(rdi), *va); +int APS5_VABI libc_printf_nid_postfix(const char* format, ...) { + std::va_list args; + va_start(args, format); + const int result = std::vprintf(format, args); + va_end(args); + return result; } -int APS5_VABI snprintf_nid_postfix(VA_ARGS) { - LibcDetail::RegSaveArea regs; - LibcDetail::FillRegSaveArea(regs, rcx, r8, r9, 0, 0, 0, - xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7); - LibcDetail::VaListLayout layout; - std::va_list* va = LibcDetail::BuildVaList(layout, regs, 0u, - reinterpret_cast(overflow_arg_area)); - return std::vsnprintf( - reinterpret_cast(rdi), - static_cast(rsi), - reinterpret_cast(rdx), - *va - ); +int APS5_VABI snprintf_nid_postfix(char* buffer, size_t size, const char* format, ...) { + std::va_list args; + va_start(args, format); + const int result = std::vsnprintf(buffer, size, format, args); + va_end(args); + return result; } -int APS5_VABI sprintf_nid_postfix(VA_ARGS) { - LibcDetail::RegSaveArea regs; - LibcDetail::FillRegSaveArea(regs, rdx, rcx, r8, r9, 0, 0, - xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7); - LibcDetail::VaListLayout layout; - std::va_list* va = LibcDetail::BuildVaList(layout, regs, 0u, - reinterpret_cast(overflow_arg_area)); - return std::vsprintf( - reinterpret_cast(rdi), - reinterpret_cast(rsi), - *va - ); +int APS5_VABI sprintf_nid_postfix(char* buffer, const char* format, ...) { + std::va_list args; + va_start(args, format); + const int result = std::vsprintf(buffer, format, args); + va_end(args); + return result; } #endif @@ -333,18 +317,12 @@ int APS5_VABI sscanf_nid_postfix(const char* input, const char* format, ...) { return result; } #else -int APS5_VABI sscanf_nid_postfix(VA_ARGS) { - LibcDetail::RegSaveArea regs; - LibcDetail::FillRegSaveArea(regs, rdx, rcx, r8, r9, 0, 0, - xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7); - LibcDetail::VaListLayout layout; - std::va_list* va = LibcDetail::BuildVaList(layout, regs, 0u, - reinterpret_cast(overflow_arg_area)); - return std::vsscanf( - reinterpret_cast(rdi), - reinterpret_cast(rsi), - *va - ); +int APS5_VABI sscanf_nid_postfix(const char* input, const char* format, ...) { + std::va_list args; + va_start(args, format); + const int result = std::vsscanf(input, format, args); + va_end(args); + return result; } #endif From cbc7498b124550326e2a04a098977062fdadd435 Mon Sep 17 00:00:00 2001 From: Raj Date: Thu, 8 Oct 2026 11:41:22 +0530 Subject: [PATCH 2/5] fix(libc): format from a copy of the guest va_list on Linux The Linux vprintf, vsprintf, vsnprintf, vsprintf_s, vfprintf, vasprintf and vsscanf handed the guest's va_list to the host function, which advanced the guest's gp_offset, fp_offset and overflow_arg_area. A caller that formats the same list twice, first to size the output and then to write it, got its second call's arguments from past the end. The Windows path reads its arguments from a copy (LibcDetail::FormatArguments) and guest_formatting checks that vsnprintf leaves the list unchanged. Linux now does the same with va_copy. --- core/libs/prx/libc/src/Formatting.cpp | 42 +++++++++++++++++++++------ 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/core/libs/prx/libc/src/Formatting.cpp b/core/libs/prx/libc/src/Formatting.cpp index c22ae7c4a..02d9e2360 100644 --- a/core/libs/prx/libc/src/Formatting.cpp +++ b/core/libs/prx/libc/src/Formatting.cpp @@ -167,7 +167,10 @@ int APS5_VABI vasprintf_nid_postfix(char** destination, const char* format, VaLi const char* source = text.c_str(); #else char* text = nullptr; - const int count = ::vasprintf(&text, format, *reinterpret_cast(args)); + std::va_list copy; + va_copy(copy, *reinterpret_cast(args)); + const int count = ::vasprintf(&text, format, copy); + va_end(copy); std::unique_ptr owner(text, std::free); if (count < 0) return -1; const char* source = text; @@ -190,7 +193,10 @@ int APS5_VABI vfprintf_nid_postfix(FileStream* stream, const char* format, VaLis const int result = std::fwrite(buffer.data(), 1, static_cast(count), native) == static_cast(count) ? count : -1; #else - const int result = std::vfprintf(native, format, *reinterpret_cast(args)); + std::va_list copy; + va_copy(copy, *reinterpret_cast(args)); + const int result = std::vfprintf(native, format, copy); + va_end(copy); #endif stream->SyncStatus(); return result; @@ -304,7 +310,11 @@ int APS5_VABI vsscanf_nid_postfix(const char* input, const char* format, VaList* #ifdef _WIN32 return LibcDetail::ScanWindows(input, format, args); #else - return std::vsscanf(input, format, *reinterpret_cast(args)); + std::va_list copy; + va_copy(copy, *reinterpret_cast(args)); + const int result = std::vsscanf(input, format, copy); + va_end(copy); + return result; #endif } @@ -352,8 +362,11 @@ int APS5_VABI vprintf_nid_postfix(const char* str, VaList* c) { #ifdef _WIN32 return LibcDetail::PrintWindows(str, c); #else - std::va_list* va = reinterpret_cast(c); - return std::vprintf(str, *va); + std::va_list copy; + va_copy(copy, *reinterpret_cast(c)); + const int result = std::vprintf(str, copy); + va_end(copy); + return result; #endif } @@ -361,7 +374,11 @@ int APS5_VABI vsprintf_nid_postfix(char* str, const char* format, VaList* args) #ifdef _WIN32 return LibcDetail::FormatWindows(str, SIZE_MAX, format, args); #else - return std::vsprintf(str, format, *reinterpret_cast(args)); + std::va_list copy; + va_copy(copy, *reinterpret_cast(args)); + const int result = std::vsprintf(str, format, copy); + va_end(copy); + return result; #endif } @@ -369,8 +386,11 @@ int APS5_VABI vsnprintf_nid_postfix(char* str, size_t size, const char* format, #ifdef _WIN32 return LibcDetail::FormatWindows(str, size, format, c); #else - std::va_list* va = reinterpret_cast(c); - return std::vsnprintf(str, size, format, *va); + std::va_list copy; + va_copy(copy, *reinterpret_cast(c)); + const int result = std::vsnprintf(str, size, format, copy); + va_end(copy); + return result; #endif } @@ -395,7 +415,11 @@ int APS5_VABI printf_s_nid_postfix(const char* format, ...) { #else int APS5_VABI vsprintf_s_nid_postfix(char* buffer, size_t size, const char* format, VaList* args) { - return std::vsnprintf(buffer, size, format, *reinterpret_cast(args)); + std::va_list copy; + va_copy(copy, *reinterpret_cast(args)); + const int result = std::vsnprintf(buffer, size, format, copy); + va_end(copy); + return result; } int APS5_VABI sprintf_s_nid_postfix(char* buffer, size_t size, const char* format, ...) { From 18113907c84b85a46610f10ca34628aa201f9748 Mon Sep 17 00:00:00 2001 From: Raj Date: Thu, 8 Oct 2026 11:42:06 +0530 Subject: [PATCH 3/5] test(libc): run guest_formatting on Linux guest_formatting_tests was built on Linux but registered with ctest on Windows only, so the Linux printf and scanf exports had no coverage: on main the test fails there at its first vsnprintf check and then crashes in snprintf with eight integers. It is registered on every platform now. The %ls checks stay Windows-only, as glibc reads %ls as 32-bit wchar_t; that gap is recorded in TechnicalDebt. --- core/libs/prx/libc/CMakeLists.txt | 2 +- core/libs/tests/Formatting.cpp | 6 ++++++ docs/dev/TechnicalDebt.md | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 6c31cccef..5d20d2706 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -206,8 +206,8 @@ if(BUILD_TESTING) target_include_directories(guest_formatting_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_formatting_tests PRIVATE libc) configure_windows_unwind(guest_formatting_tests) + add_test(NAME guest_formatting COMMAND guest_formatting_tests) if(WIN32) - add_test(NAME guest_formatting COMMAND guest_formatting_tests) set_tests_properties(guest_formatting PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() diff --git a/core/libs/tests/Formatting.cpp b/core/libs/tests/Formatting.cpp index 12a89c00f..0448df21f 100644 --- a/core/libs/tests/Formatting.cpp +++ b/core/libs/tests/Formatting.cpp @@ -38,6 +38,7 @@ static int APS5_VABI PrintList(const char* format, ...) { return result; } +#ifdef _WIN32 static bool CheckWidePrecision() { const char16_t input[] = u"A\u00e9\u20ac\U0001f600Z"; const char* expected[] = {"", "A", "A", "A\xc3\xa9", "A\xc3\xa9", "A\xc3\xa9", "A\xc3\xa9\xe2\x82\xac", @@ -65,6 +66,7 @@ static bool CheckWidePrecision() { Require(snprintf_nid_postfix(output, sizeof(output), "%.1s", "\xc3\xa9") == 1 && static_cast(output[0]) == 0xc3 && output[1] == 0); return correct; } +#endif __attribute__((noinline)) static void APS5_VABI RunChecks() { char buffer[1024]; @@ -113,4 +115,8 @@ __attribute__((noinline)) static void APS5_VABI RunChecks() { std::puts("Formatting checks passed: 10000 iterations"); } +#ifdef _WIN32 int main() { RunChecks(); return CheckWidePrecision() ? 0 : 1; } +#else +int main() { RunChecks(); } +#endif diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 7b9455c33..3ecb5646e 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -294,6 +294,7 @@ Throughout the project, every function at every stage either **does exactly what - [Min/max S#](../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp) (libSceAgcDriver, shader recompiler) - throws with anisotropy, a linear mip filter, depth or color-texture compare, or bilinear filtering of an image the recompiler point-samples (sint, converted, depth-bits). - [Raw `buffer_load_dwordx2`](../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) is one 64-bit access on the hardware (with or without glc), so it never sees half of a `buffer_atomic_swap_x2`. Only a glc load that is 8-byte aligned and in bounds, in a program that has the 64-bit storage view (it uses 64-bit buffer atomics), is read with one 64-bit atomic load; every other dwordx2 load is two dword loads and can tear against a concurrent 64-bit write: loads without glc, and loads in a program without 64-bit buffer atomics while another shader writes the pair with `swap_x2`. - [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) passes `%ls` and `%S` to glibc on Linux, which reads them as strings of 32-bit `wchar_t`; the guest's `wchar_t` is 16 bits (UTF-16), so these conversions fail with -1. The Windows path converts them to UTF-8, and `guest_formatting` checks wide conversions on Windows only - [collate::_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, and for results of 4095 characters or more, whose string storage uses the guest's over-aligned large allocation. - [Thread exit destructors](../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) run when a libkernel thread finishes; on the main or a host thread only `_sceLibcInternalThreadDtors` runs them, as `exit` does, else they are dropped - [sceFontOpenFontSet](../../core/libs/prx/libSceFont/src/SystemFont.cpp) (libSceFont) opens only the primary file of a system font set. Characters the console takes from the set's other files (Hangul, Thai and Arabic in Japanese and Chinese sets, Arabic in European sets) are reported as unsupported glyphs. The console file names are those of the PS4 firmware; the names of the PS5-only JG2 sets (`0x1A......`) are unknown, so they use the SSTJpPro files. When a console file is missing, an openly licensed substitute (Noto Sans, Noto Sans Mono, Noto Sans Thai, Noto Sans CJK) is loaded instead. It is not the system font: its metrics differ, so text width, line height and wrapping differ from the console. System font faces are loaded by FreeType in host memory, not in the font memory the title passes to the library: FreeType needs about 1 MiB per CJK face, which the title's font memory is not sized for. From faad75d655ff493b18a16b0a8be0508dbfaad8f9 Mon Sep 17 00:00:00 2001 From: Raj Date: Thu, 8 Oct 2026 11:44:49 +0530 Subject: [PATCH 4/5] refactor(libc): remove the unused register save area vararg helpers The VA_ARGS parameter list and VarArgsAbi.hpp (RegSaveArea, FillRegSaveArea, BuildVaList and the Xmm alias in SimdTypes.hpp) were only used by the four Linux exports that now take their arguments with va_start. --- core/libs/SceTypes.hpp | 6 -- core/libs/prx/libc/include/VarArgsAbi.hpp | 59 ------------------- .../include/specifics/x86_64/SimdTypes.hpp | 10 ---- core/libs/prx/libc/src/Formatting.cpp | 1 - core/libs/prx/libc/src/FormattingWide.cpp | 1 - 5 files changed, 77 deletions(-) delete mode 100644 core/libs/prx/libc/include/VarArgsAbi.hpp delete mode 100644 core/libs/prx/libc/include/specifics/x86_64/SimdTypes.hpp diff --git a/core/libs/SceTypes.hpp b/core/libs/SceTypes.hpp index bf657c237..e26cd2b3f 100644 --- a/core/libs/SceTypes.hpp +++ b/core/libs/SceTypes.hpp @@ -1846,12 +1846,6 @@ struct LibcHeapInfo { using Info = LibcHeapInfo; -#define VA_ARGS \ - std::uint64_t rdi, std::uint64_t rsi, std::uint64_t rdx, std::uint64_t rcx, \ - std::uint64_t r8, std::uint64_t r9, std::uint64_t overflow_arg_area, \ - __m128 xmm0, __m128 xmm1, __m128 xmm2, __m128 xmm3, \ - __m128 xmm4, __m128 xmm5, __m128 xmm6, __m128 xmm7, ... - struct Packet { std::uint32_t* addr; std::uint32_t dw_num; diff --git a/core/libs/prx/libc/include/VarArgsAbi.hpp b/core/libs/prx/libc/include/VarArgsAbi.hpp deleted file mode 100644 index dc2d0e87b..000000000 --- a/core/libs/prx/libc/include/VarArgsAbi.hpp +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_EXCEPTIONS_VARARGSABI_HPP -#define CORE_LIBS_PRX_LIBC_INCLUDE_EXCEPTIONS_VARARGSABI_HPP - -#include -#include -#include "SceTypes.hpp" -#include "prx/libc/include/specifics/x86_64/SimdTypes.hpp" - -namespace LibcDetail { - -struct RegSaveArea { - std::uint64_t gp[6]; - X86_64::Xmm fp[8]; -}; - -struct VaListLayout { - unsigned int gpOffset; - unsigned int fpOffset; - void* overflowArgArea; - void* regSaveArea; -}; - -inline void FillRegSaveArea( - RegSaveArea& regs, - std::uint64_t gp0, std::uint64_t gp1, std::uint64_t gp2, - std::uint64_t gp3, std::uint64_t gp4, std::uint64_t gp5, - X86_64::Xmm fp0, X86_64::Xmm fp1, X86_64::Xmm fp2, X86_64::Xmm fp3, - X86_64::Xmm fp4, X86_64::Xmm fp5, X86_64::Xmm fp6, X86_64::Xmm fp7 -) { - regs.gp[0] = gp0; - regs.gp[1] = gp1; - regs.gp[2] = gp2; - regs.gp[3] = gp3; - regs.gp[4] = gp4; - regs.gp[5] = gp5; - regs.fp[0] = fp0; - regs.fp[1] = fp1; - regs.fp[2] = fp2; - regs.fp[3] = fp3; - regs.fp[4] = fp4; - regs.fp[5] = fp5; - regs.fp[6] = fp6; - regs.fp[7] = fp7; -} - -inline std::va_list* BuildVaList( - VaListLayout& layout, RegSaveArea& regs, - unsigned int consumedGpRegisters, void* overflowArgArea -) { - layout.gpOffset = consumedGpRegisters * 8u; - layout.fpOffset = 6u * 8u; - layout.overflowArgArea = overflowArgArea; - layout.regSaveArea = ®s; - return reinterpret_cast(&layout); -} - -} - -#endif diff --git a/core/libs/prx/libc/include/specifics/x86_64/SimdTypes.hpp b/core/libs/prx/libc/include/specifics/x86_64/SimdTypes.hpp deleted file mode 100644 index b17de52e8..000000000 --- a/core/libs/prx/libc/include/specifics/x86_64/SimdTypes.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_X86_64_SIMDTYPES_HPP -#define CORE_LIBS_PRX_LIBC_INCLUDE_X86_64_SIMDTYPES_HPP - -#include - -namespace X86_64 { -using Xmm = __m128; -} - -#endif diff --git a/core/libs/prx/libc/src/Formatting.cpp b/core/libs/prx/libc/src/Formatting.cpp index 02d9e2360..04308b6a8 100644 --- a/core/libs/prx/libc/src/Formatting.cpp +++ b/core/libs/prx/libc/src/Formatting.cpp @@ -12,7 +12,6 @@ #include "prx/libc/include/General.hpp" #include "SceTypes.hpp" -#include "prx/libc/include/VarArgsAbi.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/ApplicationHeap.hpp" diff --git a/core/libs/prx/libc/src/FormattingWide.cpp b/core/libs/prx/libc/src/FormattingWide.cpp index 35912693c..7808ba4ba 100644 --- a/core/libs/prx/libc/src/FormattingWide.cpp +++ b/core/libs/prx/libc/src/FormattingWide.cpp @@ -10,7 +10,6 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" -#include "prx/libc/include/VarArgsAbi.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/WindowsFormatting.hpp" From 4c60962cd0245b71be23366c5a1425934403226c Mon Sep 17 00:00:00 2001 From: Raj Date: Thu, 8 Oct 2026 12:30:40 +0530 Subject: [PATCH 5/5] test(libc): put sprintf, sscanf and libc_printf arguments on the stack guest_formatting called sprintf with four variadic arguments and libc_printf with one, all in registers, and did not call sscanf, so only snprintf had a check that fails with the old Linux register rebuild. sprintf now formats six integers, sscanf converts eight fields and libc_printf prints seven integers; on the old code the first gives "1 2 3 4 0 0" and the other two crash. Checks suggested by nauroth in #1435. --- core/libs/tests/Formatting.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core/libs/tests/Formatting.cpp b/core/libs/tests/Formatting.cpp index 0448df21f..6cccc4a31 100644 --- a/core/libs/tests/Formatting.cpp +++ b/core/libs/tests/Formatting.cpp @@ -8,6 +8,7 @@ int APS5_VABI snprintf_nid_postfix(char*, size_t, const char*, ...); int APS5_VABI sprintf_nid_postfix(char*, const char*, ...); int APS5_VABI printf_nid_postfix(const char*, ...); int APS5_VABI libc_printf_nid_postfix(const char*, ...); +int APS5_VABI sscanf_nid_postfix(const char*, const char*, ...); int APS5_VABI vsnprintf_nid_postfix(char*, size_t, const char*, VaList*); int APS5_VABI vprintf_nid_postfix(const char*, VaList*); } @@ -103,6 +104,15 @@ __attribute__((noinline)) static void APS5_VABI RunChecks() { Require(snprintf_nid_postfix(buffer, 1, "%d", 123) == 3 && buffer[0] == 0); Require(sprintf_nid_postfix(buffer, "%hhd %hhu %hd %hu %%", 255, 257, 65535, 65537) == 11); Require(std::strcmp(buffer, "-1 1 -1 1 %") == 0); + Require(sprintf_nid_postfix(buffer, "%d %d %d %d %d %d", 1, 2, 3, 4, 5, 6) == 11); + Require(std::strcmp(buffer, "1 2 3 4 5 6") == 0); + int first = 0, second = 0, third = 0, fourth = 0, fifth = 0, sixth = 0; + char word[16] = {}; + double real = 0.0; + Require(sscanf_nid_postfix("1 2 3 4 5 6 seven 8.5", "%d %d %d %d %d %d %15s %lf", + &first, &second, &third, &fourth, &fifth, &sixth, word, &real) == 8); + Require(first == 1 && second == 2 && third == 3 && fourth == 4 && fifth == 5 && sixth == 6); + Require(std::strcmp(word, "seven") == 0 && real == 8.5); long long count = -1; int smallCount = -1; Require(snprintf_nid_postfix(buffer, 3, "abcd%lnEF%n", &count, &smallCount) == 6); @@ -111,6 +121,7 @@ __attribute__((noinline)) static void APS5_VABI RunChecks() { Require(buffer[0] == 'a' && buffer[1] == 0 && buffer[2] == 'b' && buffer[3] == 0); Require(printf_nid_postfix("printf: %d %.1f\n", 7, 2.5) == 14); Require(libc_printf_nid_postfix("libc_printf: %s\n", "OK") == 16); + Require(libc_printf_nid_postfix("%d %d %d %d %d %d %d\n", 1, 22, 333, 4444, 55555, 666666, 7777777) == 35); Require(PrintList("vprintf: %d\n", 42) == 12); std::puts("Formatting checks passed: 10000 iterations"); }