fix(libc): return 0 from scanf when input fails after a suppressed conversion on Windows
C17 7.21.6.2p16 returns EOF only if input fails before the first conversion completes, and a suppressed conversion completes. FreeBSD counts it in nconversions (vfscanf.c, releng/11.4, lines 799-805). The host scanf returned EOF there, so sscanf("12", "%*d%d", &v) reported EOF instead of 0. Each suppressed conversion now records its end with a %n and a result of EOF becomes 0 when one was recorded.
This commit is contained in:
parent
24cb0d2412
commit
09921dd376
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
#include "WindowsFormatting.hpp"
|
||||
#include "General.hpp"
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
|
||||
namespace LibcDetail {
|
||||
|
||||
|
|
@ -12,6 +14,8 @@ inline int ScanWindowsArguments_nid_no_patch(const char* format, const void* sou
|
|||
if (!format || !source) { errno = 22; return EOF; }
|
||||
std::string translated;
|
||||
std::vector<void*> pointers;
|
||||
std::vector<int> suppressedEnds(static_cast<size_t>(std::count(format, format + std::strlen(format), '%')), -1);
|
||||
size_t suppressedCount = 0;
|
||||
FormatArguments args(source);
|
||||
while (*format) {
|
||||
const char value = *format++;
|
||||
|
|
@ -45,8 +49,15 @@ inline int ScanWindowsArguments_nid_no_patch(const char* format, const void* sou
|
|||
translated += *format++;
|
||||
}
|
||||
if (!suppressed) pointers.push_back(args.Next<void*>());
|
||||
else if (conversion != 'n') {
|
||||
translated += "%n";
|
||||
pointers.push_back(&suppressedEnds[suppressedCount++]);
|
||||
}
|
||||
}
|
||||
return scan(translated.c_str(), reinterpret_cast<char*>(pointers.data()));
|
||||
const int result = scan(translated.c_str(), reinterpret_cast<char*>(pointers.data()));
|
||||
if (result == EOF && std::any_of(suppressedEnds.begin(), suppressedEnds.begin() + suppressedCount, [](int end) { return end >= 0; }))
|
||||
return 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
inline int ScanWindows(const char* input, const char* format, const void* source) {
|
||||
|
|
|
|||
|
|
@ -164,6 +164,22 @@ __attribute__((noinline)) static void APS5_VABI RunChecks() {
|
|||
&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);
|
||||
#ifdef _WIN32
|
||||
int scanned = -1;
|
||||
int position = -1;
|
||||
Require(sscanf_nid_postfix("12", "%*d%d", &scanned) == 0 && scanned == -1);
|
||||
Require(sscanf_nid_postfix("12 ", "%*d %d", &scanned) == 0);
|
||||
Require(sscanf_nid_postfix("abc", "%*s%d", &scanned) == 0);
|
||||
Require(sscanf_nid_postfix("a", "%*c%d", &scanned) == 0);
|
||||
Require(sscanf_nid_postfix("abc", "%*[a-z]%d", &scanned) == 0);
|
||||
Require(sscanf_nid_postfix("12", "%*d%n%d", &position, &scanned) == 0 && position == 2);
|
||||
Require(sscanf_nid_postfix("12", "%*d%d%d", &scanned, &first) == 0);
|
||||
Require(sscanf_nid_postfix("1 2", "%d%*d%d", &scanned, &first) == 1 && scanned == 1);
|
||||
Require(sscanf_nid_postfix("1 2 3", "%*d %d %*d", &scanned) == 1 && scanned == 2);
|
||||
Require(sscanf_nid_postfix("", "%*d", &scanned) == EOF);
|
||||
Require(sscanf_nid_postfix(" ", "%*d%d", &scanned) == EOF);
|
||||
Require(sscanf_nid_postfix("", "%d", &scanned) == EOF);
|
||||
#endif
|
||||
long long count = -1;
|
||||
int smallCount = -1;
|
||||
Require(snprintf_nid_postfix(buffer, 3, "abcd%lnEF%n", &count, &smallCount) == 6);
|
||||
|
|
|
|||
|
|
@ -370,6 +370,16 @@ int main() {
|
|||
Require(feof_nid_postfix(&scanned) && (scanned.GuestState().flags & 0x20));
|
||||
scanned.Close();
|
||||
|
||||
#ifdef _WIN32
|
||||
FileStream scanSuppressed(std::tmpfile());
|
||||
Require(std::fputs("12", scanSuppressed.GetHandle()) >= 0);
|
||||
std::rewind(scanSuppressed.GetHandle());
|
||||
int suppressedTarget = 321;
|
||||
Require(fscanf_nid_postfix(&scanSuppressed, "%*d%d", &suppressedTarget) == 0 && suppressedTarget == 321);
|
||||
Require(fscanf_nid_postfix(&scanSuppressed, "%*d") == EOF);
|
||||
scanSuppressed.Close();
|
||||
#endif
|
||||
|
||||
FileStream scanMany(std::tmpfile());
|
||||
Require(std::fputs("7 1 2 3 4 5 6 7 8 4294967297 -4294967298 4294967299 abc %!", scanMany.GetHandle()) >= 0);
|
||||
std::rewind(scanMany.GetHandle());
|
||||
|
|
|
|||
|
|
@ -537,6 +537,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [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, so a narrow `%p` there still follows the host (glibc prints a null pointer as `(nil)`, where FreeBSD prints `0x0`); positional arguments, the `'` flag and `%m` throw when a format requires the guest formatter
|
||||
- [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, and for results of 4095 characters or more, whose string storage uses the guest's over-aligned large allocation.
|
||||
- [The scanf family](../../core/libs/prx/libc/src/Formatting.cpp) (libc) hands `%i` to glibc on Linux, which since 2.38 reads the C23 `0b`/`0B` binary prefix when the library is built as GNU C++: `sscanf("0b101", "%i", &value)` stores 5 where C17 stores 0 and stops at the `b`. The strtol family stops at the prefix as C17 does
|
||||
- [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), where C17 and FreeBSD stop before the `x`
|
||||
- [socketpair](../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) - the pair is the host's `AF_UNIX` pair on Linux and, on Windows, a loopback TCP connection for `SOCK_STREAM` or two connected UDP sockets for `SOCK_DGRAM`; `SOCK_SEQPACKET` throws on Windows. A datagram sent to a full receiver gets `ENOBUFS` on the console, waits or gets `EAGAIN` on Linux and is dropped on Windows, and on Windows a datagram `recv` after the peer closed reports `ECONNRESET` instead of the console's `ENOTCONN`, and a stream `send` after the peer closed reports `ECONNABORTED` instead of the console's `EPIPE`. `read` and `write` on a pair end go to the host like on every guest socket. `getpeername` on a pair end succeeds after the peer closed, where the console returns `ENOTCONN`, and `connect`, `bind` and `sendto` with an address on a pair end report an address-family error instead of the console's `EISCONN`. On Windows `poll` on a stream end reports `POLLHUP` without `POLLIN` after the peer's `shutdown(SHUT_WR)` or `close`, where the console reports `POLLIN`, with `POLLHUP` only after `close`
|
||||
- [read, write and vector I/O](../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) on a guest socket use `recv` and `send` without flags; `sceKernelReadv` and `sceKernelWritev` gather or scatter through one socket operation, preserving stream short reads and datagram boundaries. The positioned `pread`, `pwrite`, `preadv` and `pwritev` forms, and their `sceKernel` forms, return `ESPIPE` for an open guest socket and `EBADF` for a closed one. Guest socket descriptors start at `0x10000000` and are allocated upward, so the descriptor range selects the guest socket table or the host descriptor table. `EPIPE` from a write raises the host's `SIGPIPE` on Linux, which the guest can neither ignore nor suppress: `signal(SIGPIPE, SIG_IGN)` and `sigaction` record the disposition, as they do for every number from 1 to 128, but only the six signals with host equivalents (2, 4, 6, 8, 11 and 15) take effect on the host or are delivered, so the host `SIGPIPE` is not stopped; `SO_NOSIGPIPE` is not an option here, and raises nothing on Windows; a `write` longer than `INT_MAX` bytes is `EMSGSIZE` where the console sends it. AIO on a guest socket still goes to the host, which has no such descriptor
|
||||
- [poll](../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) waits only on guest sockets, through `WSAPoll` on Windows and `poll` on Linux; the `pollfd` layout and flag values are FreeBSD's. A descriptor below the socket range (files, pipes, stdio) throws, as do event bits other than `POLLIN`, `POLLPRI`, `POLLOUT`, `POLLRDNORM`, `POLLRDBAND` and `POLLWRBAND` (the `POLLERR`, `POLLHUP` and `POLLNVAL` bits are ignored in `events`), `POLLPRI` on Windows, which `WSAPoll` rejects, and an infinite timeout with no socket to wait on. A closed socket descriptor reports `POLLNVAL`; the console's limit on the descriptor count is unknown and not checked
|
||||
|
|
|
|||
Loading…
Reference in New Issue