feat(stdio): scanf family

This commit is contained in:
2026-09-05 21:52:22 -04:00
parent ddcf8f39be
commit 3eff4c97fd
3 changed files with 2540 additions and 0 deletions
+31
View File
@@ -429,4 +429,35 @@ fmemopen(void *buf, size_t size, const char *mode);
FILE * FILE *
open_memstream(char **ptr, size_t *sizeloc); open_memstream(char **ptr, size_t *sizeloc);
/* formatted input (todo 17) */
/*
* Read formatted input. scanf reads from stdin, fscanf from the stream,
* sscanf from the string. The conversion directives are %d %i %u %o %x %X
* %f %F %e %E %g %G %a %A %c %s %[ %p %n and %% (with assignment
* suppression '*', a decimal field width, and the length modifiers hh h l
* ll j z t, plus L for the float conversions). A conversion fails to match
* without consuming its offending character; the return value is the
* number of assigned (non-suppressed) input items, or EOF if an input
* failure occurs before the first one. Integer overflow stores a saturated
* value and sets errno = ERANGE (nothing else touches errno).
*/
int
scanf(const char *restrict format, ...);
int
fscanf(FILE *restrict stream, const char *restrict format, ...);
int
sscanf(const char *restrict s, const char *restrict format, ...);
int
vscanf(const char *restrict format, va_list ap);
int
vfscanf(FILE *restrict stream, const char *restrict format, va_list ap);
int
vsscanf(const char *restrict s, const char *restrict format, va_list ap);
#endif /* VLIBC_STDIO_H */ #endif /* VLIBC_STDIO_H */
+1536
View File
File diff suppressed because it is too large Load Diff
+973
View File
@@ -0,0 +1,973 @@
/*
* vlibc — formatted input test, scanf family (todo 17).
*
* Every expected return value and stored value below was recorded from the
* host glibc with a throwaway probe corpus (/tmp/scanprobe*.c), including
* the deliberate divergences: none on values or return codes; errno policy
* is checked only in the -f scenarios (vlibc sets ERANGE solely for integer
* overflow, glibc additionally leaks it from float range errors — the
* differential harness compares return codes and stored values, not errno).
*
* Coverage: %d %i %u %o %x %X with widths, suppression, sign, base
* detection (0x/0b/0), saturation and silent truncation; %f %e %g %a with
* decimal/hex/inf/nan forms, exponent completeness rules and token widths;
* %c %s %[ (including the ']' and empty-set corners) with NUL rules; %n %%
* and literal matching; the EOF-vs-matching-failure return discipline; and
* the stream position discipline (a failed conversion never consumes its
* offending character) through fscanf/vfscanf over tmpfile, plus scanf/
* vscanf through a rebound stdin.
*
* Diagnostics go through raw SYS_write; the -f scenarios run the cases that
* write errno and leave via the raw syscall so the host cleanup never runs
* after those writes (the test_printf/test_stdio convention).
*/
#include <errno.h>
#include <float.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../src/internal/syscall.h"
static int failures;
static void
say(int fd, const char *s)
{
long n = 0;
while (s[n] != '\0')
{
n++;
}
__syscall3(SYS_write, fd, (long)s, n);
}
static void
say_dec(int fd, unsigned long v) // NOLINT(bugprone-easily-swappable-parameters)
{
char buf[24];
int i = (int)sizeof(buf);
buf[--i] = '\0';
do
{
buf[--i] = (char)('0' + (v % 10));
v /= 10;
} while (v != 0);
__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
}
/* Copy a format string at runtime so GCC's -Wformat checker cannot flag
* the deliberate corner-case formats (zero width, empty scansets, a
* suppressed %n). */
static void
mkfmt(char *dst, const char *src)
{
while (*src != '\0')
{
*dst++ = *src++;
}
*dst = '\0';
}
static void
check(int cond, const char *what)
{
if (cond)
{
say(1, "PASS: ");
say(1, what);
say(1, "\n");
}
else
{
say(2, "FAIL: ");
say(2, what);
say(2, "\n");
failures++;
}
}
/* fscanf over a tmpfile with the given content; verifies the return value,
* the stored int, and the next byte the stream delivers (or EOF). */
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
static int
fscan_int_case(const char *content, const char *fmt, int want_ret, int want_val, int want_next)
{
FILE *fp = tmpfile();
int i = 0;
int r;
int c;
fputs(content, fp);
fseek(fp, 0, SEEK_SET);
r = fscanf(fp, fmt, &i);
c = fgetc(fp);
fclose(fp);
return r == want_ret && i == want_val && (want_next < 0 ? c == EOF : c == want_next);
}
static int
fscan_dbl_case(const char *content, const char *fmt, int want_ret, double want_val, int want_next)
{
FILE *fp = tmpfile();
double d = 0;
int r;
int c;
fputs(content, fp);
fseek(fp, 0, SEEK_SET);
r = fscanf(fp, fmt, &d);
c = fgetc(fp);
fclose(fp);
return r == want_ret && d == want_val && (want_next < 0 ? c == EOF : c == want_next);
}
// NOLINTEND(bugprone-easily-swappable-parameters)
static void
int_scenarios(void)
{
int i;
int a;
int b;
int n;
int r;
unsigned u;
unsigned long ul;
unsigned long long ull;
unsigned char uc;
short h;
signed char hh;
size_t z;
intmax_t im;
ptrdiff_t td;
i = 7;
r = sscanf("abc", "%d", &i);
check(r == 0 && i == 7, "sscanf(\"abc\",\"%d\") fails and leaves the target untouched");
i = 0;
r = sscanf(" 123", "%d", &i);
check(r == 1 && i == 123, "sscanf(\" 123\",\"%d\") skips leading whitespace");
i = 0;
r = sscanf("0x1A", "%i", &i);
check(r == 1 && i == 26, "%i detects hex from 0x");
i = 0;
r = sscanf("017", "%i", &i);
check(r == 1 && i == 15, "%i detects octal from the leading 0");
i = 0;
r = sscanf("19", "%i", &i);
check(r == 1 && i == 19, "%i falls back to decimal");
i = 0;
r = sscanf("0b101", "%i", &i);
check(r == 1 && i == 5, "%i detects binary from 0b (glibc parity)");
i = 0;
r = sscanf("0B11", "%i", &i);
check(r == 1 && i == 3, "%i accepts the uppercase 0B prefix");
i = 0;
r = sscanf(" -0x1A", "%i", &i);
check(r == 1 && i == -26, "%i handles a sign before the prefix");
i = 0;
r = sscanf(" +123", "%i", &i);
check(r == 1 && i == 123, "%i handles a plus sign");
i = 0;
r = sscanf("0x", "%i", &i);
check(r == 0, "%i fails on 0x with no hex digit");
i = 0;
r = sscanf("0", "%i", &i);
check(r == 1 && i == 0, "%i of just 0 is octal zero");
i = 0;
r = sscanf("1234", "%2d", &i);
check(r == 1 && i == 12, "%2d reads at most two digits");
i = 0;
{
char fmt0d[4];
mkfmt(fmt0d, "%0d");
r = sscanf("12", fmt0d, &i);
}
check(r == 1 && i == 12, "a field width of 0 means unlimited");
a = b = 0;
r = sscanf("12", "%1d%1d", &a, &b);
check(r == 2 && a == 1 && b == 2, "adjacent width-1 integer conversions");
a = b = 0;
r = sscanf("7 8", "%*d %d", &b);
check(r == 1 && b == 8, "assignment suppression is not counted");
a = 0;
r = sscanf("7 8", "%*d %d", &a);
check(r == 1 && a == 8, "suppression consumes the argument");
a = 0;
r = sscanf("5 6 7", "%*d %*d %d", &a);
check(r == 1 && a == 7, "two suppressed conversions");
a = b = 0;
r = sscanf("5x6", "%d,%d", &a, &b);
check(r == 1 && a == 5, "a literal mismatch stops the scan after %d");
a = b = 0;
r = sscanf("5,6", "%d,%d", &a, &b);
check(r == 2 && a == 5 && b == 6, "literal characters match literally");
i = 0;
r = sscanf("12%", "%d%%", &i);
check(r == 1 && i == 12, "%% matches a percent sign");
r = sscanf("%", "%%");
check(r == 0, "%% alone matches and assigns nothing");
i = 0;
r = sscanf("%", "%%%d", &i);
check(r == EOF, "input failure after %% with no assignments is EOF");
a = b = 0;
r = sscanf("x", "%d %d", &a, &b);
check(r == 0 && a == 0 && b == 0, "matching failure with no assignments returns 0");
a = b = 0;
r = sscanf("1x", "%d%d", &a, &b);
check(r == 1 && a == 1 && b == 0, "matching failure returns the count so far");
a = b = 0;
r = sscanf("12", "%d%d", &a, &b);
check(r == 1 && a == 12 && b == 0, "input failure returns the count so far");
i = 7;
r = sscanf("", "%d", &i);
check(r == EOF && i == 7, "input failure before any conversion is EOF");
a = b = 0;
r = sscanf("5 \t", "%d %d", &a, &b);
check(r == 1 && a == 5, "whitespace in the format runs to end of input");
a = 0;
r = sscanf("1", "%d ", &a);
check(r == 1 && a == 1, "trailing whitespace after the last assignment");
a = 0;
r = sscanf("\t\n 42", "%d", &a);
check(r == 1 && a == 42, "all C-locale whitespace is skipped");
a = 0;
r = sscanf("42\n", "%d", &a);
check(r == 1 && a == 42, "trailing newline input after the number");
a = 0;
r = sscanf(" \t\n", "%d", &a);
check(r == EOF, "whitespace-only input to %d is EOF");
a = 0;
r = sscanf("1 2", "%d", &a);
check(r == 1 && a == 1, "extra input beyond the format is ignored");
i = 0;
r = sscanf("42x", "%d", &i);
check(r == 1 && i == 42, "%d stops at the first non-digit");
i = 0;
r = sscanf("2.5", "%d", &i);
check(r == 1 && i == 2, "%d stops at a decimal point");
i = 0;
r = sscanf(".5", "%d", &i);
check(r == 0, "%d fails on a leading decimal point");
i = 0;
r = sscanf("0x1A", "%d", &i);
check(r == 1 && i == 0, "%d reads only the leading 0 of 0x1A");
i = 0;
r = sscanf("1e5", "%d", &i);
check(r == 1 && i == 1, "%d stops at the exponent marker");
i = 0;
r = sscanf("1.5", "%i", &i);
check(r == 1 && i == 1, "%i stops at a decimal point");
u = 0;
r = sscanf("0x1A", "%x", &u);
check(r == 1 && u == 26, "%x accepts the 0x prefix");
u = 0;
r = sscanf("1A", "%x", &u);
check(r == 1 && u == 26, "%x reads bare hex digits");
u = 0;
r = sscanf("1a", "%X", &u);
check(r == 1 && u == 26, "%X accepts lowercase hex digits");
u = 0;
r = sscanf("0X1A", "%x", &u);
check(r == 1 && u == 26, "%x accepts the uppercase 0X prefix");
u = 0;
r = sscanf("0x", "%x", &u);
check(r == 1 && u == 0, "%x of 0x alone is zero (the 0 is the number)");
u = 0;
r = sscanf("zzz", "%x", &u);
check(r == 0, "%x fails when no hex digit exists");
u = 0;
r = sscanf("1A2B", "%2x", &u);
check(r == 1 && u == 26, "%2x reads exactly two hex digits");
u = 0;
r = sscanf("ff", "%x", &u);
check(r == 1 && u == 255, "%x of ff is 255");
i = 0;
r = sscanf("-0x1A", "%x", &u);
check(r == 1 && u == 0xffffffe6U, "%x takes a sign");
u = 0;
r = sscanf("017", "%o", &u);
check(r == 1 && u == 15, "%o reads octal");
u = 0;
r = sscanf("17", "%o", &u);
check(r == 1 && u == 15, "%o of 17 is octal 15");
u = 0;
r = sscanf("-5", "%u", &u);
check(r == 1 && u == 4294967291U, "%u takes a sign and negates modulo 2^32");
u = 0;
r = sscanf("+5", "%u", &u);
check(r == 1 && u == 5, "%u takes a plus sign");
u = 0;
r = sscanf("42", "%u", &u);
check(r == 1 && u == 42, "%u of 42");
ull = 0;
r = sscanf("-1", "%llu", &ull);
check(r == 1 && ull == ULLONG_MAX, "%llu of -1 wraps to ULLONG_MAX");
ull = 0;
r = sscanf("-18446744073709551615", "%llu", &ull);
check(r == 1 && ull == 1, "%llu of -ULLONG_MAX wraps to 1");
i = 0;
r = sscanf("-5", "%d", &i);
check(r == 1 && i == -5, "%d of -5");
h = 0;
r = sscanf("-300", "%hd", &h);
check(r == 1 && h == -300, "%hd stores a short");
h = 0;
r = sscanf("999999999", "%hd", &h);
check(r == 1 && h == -13825, "%hd truncates silently (no ERANGE)");
hh = 0;
r = sscanf("300", "%hhd", &hh);
check(r == 1 && hh == 44, "%hhd truncates to signed char");
uc = 0;
r = sscanf("-1", "%hhu", &uc);
check(r == 1 && uc == 255, "%hhu of -1 is 255");
z = 0;
r = sscanf("18446744073709551615", "%zu", &z);
check(r == 1 && z == (size_t)UINT64_MAX, "%zu reads SIZE_MAX");
im = 0;
r = sscanf("-9223372036854775808", "%jd", &im);
check(r == 1 && im == INTMAX_MIN, "%jd reads INTMAX_MIN");
td = 0;
r = sscanf("-42", "%td", &td);
check(r == 1 && td == -42, "%td reads a ptrdiff_t");
ul = 0;
r = sscanf("4294967296", "%lu", &ul);
check(r == 1 && ul == 4294967296UL, "%lu reads a long");
i = 0;
n = 55;
r = sscanf("42", "%d%n", &i, &n);
check(r == 1 && i == 42 && n == 2, "%n stores the count (not an assignment)");
i = 0;
n = 55;
r = sscanf("42", "%n%d", &n, &i);
check(r == 1 && n == 0 && i == 42, "%n before any input stores 0");
a = 0;
n = 55;
r = sscanf("42", "%d%n%d", &a, &n, &b);
check(r == 1 && a == 42 && n == 2, "%n runs even when the next conversion fails");
i = 0;
n = 55;
r = sscanf("x", "%d%n", &i, &n);
check(r == 0 && n == 55, "a failed %d never reaches its %n");
a = 0;
n = 55;
{
char fmt_sn[6];
mkfmt(fmt_sn, "%*n%d");
r = sscanf("42", fmt_sn, &a);
}
check(r == 1 && a == 42 && n == 55, "a suppressed %n stores nothing");
n = 55;
{
char fmt_sn2[4];
mkfmt(fmt_sn2, "%*n");
r = sscanf("7", fmt_sn2, &n);
}
check(r == 0 && n == 55, "a lone suppressed %n assigns nothing");
a = 0;
{
long nl = 55;
r = sscanf("123456", "%3d%ln", &a, &nl);
check(r == 1 && a == 123 && nl == 3, "%ln stores into a long");
}
a = 0;
n = 55;
r = sscanf("12 34", "%*d %n %d", &n, &a);
check(r == 1 && n == 3 && a == 34, "%n counts consumed whitespace too");
/* fscanf: the offending character is never consumed. */
check(fscan_int_case("12x", "%d", 1, 12, 'x'), "fscanf %d leaves the offending x");
check(fscan_int_case("", "%d", EOF, 0, EOF), "fscanf on an empty stream is EOF");
check(fscan_int_case("0xz", "%i", 0, 0, 'z'), "%i consumes 0x but leaves the junk");
check(fscan_int_case("+x", "%d", 0, 0, 'x'), "%d consumes the sign but leaves the junk");
check(fscan_int_case("-", "%d", 0, 0, EOF), "a bare sign is a matching failure");
check(fscan_int_case("0x1A", "%2i", 0, 0, '1'), "%2i of 0x1A consumes only 0x");
check(fscan_int_case("0x10", "%d", 1, 0, 'x'), "%d of 0x10 reads 0 and leaves x10");
check(fscan_int_case("0x1G", "%x", 1, 1, 'G'), "%x stops at the non-hex G");
check(fscan_int_case("08", "%o", 1, 0, '8'), "%o of 08 reads 0 and leaves 8");
check(fscan_int_case("12", "%3d", 1, 12, EOF), "%3d accepts a shorter number");
check(fscan_int_case("123", "%1d", 1, 1, '2'), "%1d reads one digit");
check(fscan_int_case("1.5", "%i", 1, 1, '.'), "%i leaves the decimal point");
check(fscan_int_case("0b101", "%i", 1, 5, EOF), "%i consumes the 0b prefix");
check(fscan_int_case("1x", "%d %d", 1, 1, 'x'), "second %d fails and leaves the x");
check(fscan_int_case("5x6", "%d,%d", 1, 5, 'x'), "literal mismatch leaves the character");
check(fscan_int_case("5,", "%d,%d", 1, 5, EOF), "literal then input failure returns 1");
check(fscan_int_case("z", "%d", 0, 0, 'z'), "offending char remains after a full mismatch");
}
static void
float_scenarios(void)
{
int i;
int r;
float f;
double d;
long double ld;
i = 0;
f = 0;
r = sscanf("42 3.14", "%d %f", &i, &f);
check(r == 2 && i == 42 && f == 3.14F, "sscanf(\"42 3.14\",\"%d %f\")");
d = 0;
r = sscanf("3.14e2", "%lf", &d);
check(r == 1 && d == 314.0, "%lf of 3.14e2 is 314");
d = 0;
r = sscanf("0x1.8p1", "%lf", &d);
check(r == 1 && d == 3.0, "%lf parses a hex float");
d = 0;
r = sscanf("0x1.8p1", "%la", &d);
check(r == 1 && d == 3.0, "%la parses a hex float");
d = 0;
r = sscanf("0X1.8P1", "%la", &d);
check(r == 1 && d == 3.0, "%la accepts uppercase X and P");
d = 0;
r = sscanf("0X1.8P+1", "%la", &d);
check(r == 1 && d == 3.0, "%la accepts an exponent sign");
d = 0;
r = sscanf("0x.8", "%lf", &d);
check(r == 1 && d == 0.5, "%lf parses 0x.8");
d = 0;
r = sscanf("0x.8", "%la", &d);
check(r == 1 && d == 0.5, "%la parses 0x.8");
d = 0;
r = sscanf("0x1.8", "%lf", &d);
check(r == 1 && d == 1.5, "a hex float without the p exponent is valid");
ld = 0;
r = sscanf("3.14159265358979323846264338327950288", "%Lf", &ld);
check(r == 1 && ld == strtold("3.14159265358979323846264338327950288", NULL),
"%Lf converts through strtold");
ld = 0;
r = sscanf("3.5", "%Lg", &ld);
check(r == 1 && ld == 3.5L, "%Lg behaves like %Lf");
d = 0;
r = sscanf("3.14", "%le", &d);
check(r == 1 && d == 3.14, "%le reads decimals like %lf");
d = 0;
r = sscanf("3.14", "%lg", &d);
check(r == 1 && d == 3.14, "%lg reads decimals like %lf");
d = 0;
r = sscanf("3.14", "%lF", &d);
check(r == 1 && d == 3.14, "%lF reads decimals like %lf");
d = 0;
r = sscanf("3.14", "%lA", &d);
check(r == 1 && d == 3.14, "%lA reads decimals like %lf");
d = 0;
r = sscanf("inf", "%lf", &d);
check(r == 1 && d > DBL_MAX, "%lf reads inf");
d = 0;
r = sscanf("infinity", "%lf", &d);
check(r == 1 && d > DBL_MAX, "%lf reads infinity");
d = 0;
r = sscanf("nan", "%lf", &d);
check(r == 1 && d != d, "%lf reads nan");
d = 0;
r = sscanf("nan(12)", "%lf", &d);
check(r == 1 && d != d, "%lf reads a nan payload");
d = 0;
r = sscanf("-inf", "%lf", &d);
check(r == 1 && d < -DBL_MAX, "%lf reads -inf");
d = 0;
r = sscanf("-nan", "%lf", &d);
check(r == 1 && d != d, "%lf reads -nan");
d = 0;
r = sscanf("-2.5e-3", "%lf", &d);
check(r == 1 && d == -0.0025, "%lf of -2.5e-3");
d = 0;
r = sscanf(".5", "%lf", &d);
check(r == 1 && d == 0.5, "%lf accepts a leading decimal point");
d = 0;
r = sscanf("5.", "%lf", &d);
check(r == 1 && d == 5.0, "%lf accepts a trailing decimal point");
d = 0;
r = sscanf("3.14x", "%lf", &d);
check(r == 1 && d == 3.14, "%lf stops cleanly at junk");
d = 0;
r = sscanf("1.2.3", "%lf", &d);
check(r == 1 && d == 1.2, "%lf stops at the second decimal point");
d = 0;
r = sscanf("3.14159z", "%3lf", &d);
check(r == 1 && d == 3.1, "a width-truncated float token");
d = 0;
r = sscanf("1e2", "%3lf", &d);
check(r == 1 && d == 100.0, "a width-limited complete exponent");
d = 0;
r = sscanf("1e2", "%2lf", &d);
check(r == 0, "width truncating an exponent fails the conversion");
d = 0;
r = sscanf("3.1e", "%4lf", &d);
check(r == 0, "an incomplete exponent fails the conversion");
d = 0;
r = sscanf("1e", "%lf", &d);
check(r == 0, "1e fails (exponent without digits)");
d = 0;
r = sscanf("+", "%lf", &d);
check(r == 0, "a lone sign fails");
d = 0;
r = sscanf("-.", "%lf", &d);
check(r == 0, "a sign and dot fail");
d = 0;
r = sscanf(" + 5", "%lf", &d);
check(r == 0, "whitespace between sign and digits fails");
d = 0;
r = sscanf("0x1.8p1", "%5la", &d);
check(r == 1 && d == 1.5, "width truncation keeps the valid hex prefix");
d = 0;
r = sscanf(" - 5", "%lf", &d);
check(r == 0, "sign, whitespace, digits fails");
f = 0;
d = 0;
r = sscanf("3.5 4", "%*f %lf", &d);
check(r == 1 && d == 4.0, "suppressed %f consumes a double argument");
/* fscanf stream positions for float failures. */
check(fscan_dbl_case("1e", "%lf", 0, 0.0, EOF), "failed 1e is consumed entirely");
check(fscan_dbl_case("1e+", "%lf", 0, 0.0, EOF), "failed 1e+ is consumed entirely");
check(fscan_dbl_case("1ex", "%lf", 0, 0.0, 'x'), "failed 1e leaves the junk char");
check(fscan_dbl_case("1e x", "%lf", 0, 0.0, ' '), "failed 1e leaves the whitespace");
check(fscan_dbl_case("0x1.p", "%la", 0, 0.0, EOF), "failed 0x1.p is consumed entirely");
check(fscan_dbl_case("0x1.pz", "%la", 0, 0.0, 'z'), "failed 0x1.p leaves the junk");
check(fscan_dbl_case("0x", "%la", 0, 0.0, EOF), "failed 0x is consumed entirely");
check(fscan_dbl_case(".e5", "%lf", 0, 0.0, 'e'), "failed .e5 leaves the e");
check(fscan_dbl_case("1e2x", "%lf", 1, 100.0, 'x'), "valid 1e2 leaves the junk");
check(fscan_dbl_case("1e2x", "%2lf", 0, 0.0, '2'), "width-truncated 1e fails, leaves 2");
check(fscan_dbl_case("0x1.8p1", "%5la", 1, 1.5, 'p'), "width-truncated hex float leaves the p");
check(fscan_dbl_case("0x1.8p1z", "%la", 1, 3.0, 'z'), "hex float leaves the junk");
check(fscan_dbl_case("0x.8", "%la", 1, 0.5, EOF), "0x.8 is fully consumed");
check(fscan_dbl_case("infx", "%lf", 1, 1.0 / 0.0, 'x'), "inf stops before the junk");
check(fscan_dbl_case("infinityx", "%lf", 1, 1.0 / 0.0, 'x'), "infinity stops before the junk");
check(fscan_dbl_case("1.2.3", "%lf", 1, 1.2, '.'), "float stops at the second dot");
}
static void
char_str_scenarios(void)
{
char buf[64];
char c5[5];
char c;
int a;
int b;
int n;
int r;
buf[0] = 0;
c5[0] = 0;
r = sscanf("hello world", "%s %s", buf, c5);
check(r == 2 && strcmp(buf, "hello") == 0 && strcmp(c5, "world") == 0, "%s %s reads two words");
buf[0] = 0;
r = sscanf("hello world", "%3s", buf);
check(r == 1 && strcmp(buf, "hel") == 0, "%3s truncates at the width");
buf[0] = 0;
r = sscanf(" hello", "%s", buf);
check(r == 1 && strcmp(buf, "hello") == 0, "%s skips leading whitespace");
buf[0] = 0;
r = sscanf("ab", "%5s", buf);
check(r == 1 && strcmp(buf, "ab") == 0, "%s succeeds when the input ends mid-word");
buf[0] = 0;
r = sscanf("abc def", "%s%n", buf, &n);
check(r == 1 && strcmp(buf, "abc") == 0 && n == 3, "%s then %n counts the word only");
buf[0] = 0;
n = 55;
r = sscanf("abc", "%2s%n", buf, &n);
check(r == 1 && strcmp(buf, "ab") == 0 && n == 2, "width-limited %s and %n");
buf[0] = 0;
r = sscanf("", "%s", buf);
check(r == EOF, "%s on empty input is EOF");
c5[0] = 'X';
r = sscanf(" ", "%s", c5);
check(r == EOF && c5[0] == 'X', "%s after whitespace at EOF stores nothing");
buf[0] = 0;
r = sscanf("abcXYZ", "%[a-z]", buf);
check(r == 1 && strcmp(buf, "abc") == 0, "%[a-z] reads the class prefix");
buf[0] = 0;
r = sscanf("ABCxyz", "%[^a-z]", buf);
check(r == 1 && strcmp(buf, "ABC") == 0, "%[^a-z] negates the class");
buf[0] = 0;
r = sscanf("ab]c", "%[^]]", buf);
check(r == 1 && strcmp(buf, "ab") == 0, "a ] first member of a negated class");
buf[0] = 0;
r = sscanf("]ab", "%[]]", buf);
check(r == 1 && strcmp(buf, "]") == 0, "%[]] has ] as its first member");
buf[0] = 0;
r = sscanf("-ab", "%[-a]", buf);
check(r == 1 && strcmp(buf, "-a") == 0, "%[-a] treats the leading dash literally");
buf[0] = 0;
r = sscanf("12345x", "%5[0-9]", buf);
check(r == 1 && strcmp(buf, "12345") == 0, "%5[0-9] honors the width");
buf[0] = 0;
r = sscanf("1fX", "%[0-9a-f]", buf);
check(r == 1 && strcmp(buf, "1f") == 0, "%[0-9a-f] reads a range");
buf[0] = 0;
r = sscanf("abcdefX", "%3[a-z]", buf);
check(r == 1 && strcmp(buf, "abc") == 0, "%3[a-z] honors the width");
buf[0] = 'Q';
r = sscanf("9", "%[a-z]", buf);
check(r == 0 && buf[0] == 'Q', "a failed scanset stores nothing");
buf[0] = 'Q';
{
char fmt_empty[4];
mkfmt(fmt_empty, "%[]");
r = sscanf("ab", fmt_empty, buf);
}
check(r == 0 && buf[0] == 'Q', "an empty scanset never matches");
buf[0] = 0;
{
char fmt_nempty[5];
mkfmt(fmt_nempty, "%[^]");
r = sscanf("ab", fmt_nempty, buf);
}
check(r == 0, "a negated empty scanset never matches (glibc parity)");
buf[0] = 0;
c5[0] = 0;
r = sscanf("abc", "%[a-c]%[d-f]", buf, c5);
check(r == 1 && strcmp(buf, "abc") == 0, "the second scanset hits EOF after one assignment");
buf[0] = 0;
c5[0] = 0;
r = sscanf("a-b", "%[a-z]-%[a-z]", buf, c5);
check(r == 2 && strcmp(buf, "a") == 0 && strcmp(c5, "b") == 0, "literal dash between scansets");
buf[0] = 0;
c5[0] = 0;
r = sscanf("123", "%1[0-9]%[0-9]", buf, c5);
check(r == 2 && strcmp(buf, "1") == 0 && strcmp(c5, "23") == 0, "width-1 scanset chains");
memset(c5, 0, 5);
r = sscanf("abcde", "%5c", c5);
check(r == 1 && c5[0] == 'a' && c5[1] == 'b' && c5[2] == 'c' && c5[3] == 'd' && c5[4] == 'e',
"%5c reads five characters with no NUL");
c = 0;
r = sscanf(" x", "%c", &c);
check(r == 1 && c == ' ', "%c reads a space");
c = 0;
r = sscanf(" ", " %c", &c);
check(r == EOF, "%c after whitespace at EOF is EOF");
c = 0;
r = sscanf(" x", " %c", &c);
check(r == 1 && c == 'x', "format whitespace feeds %c");
a = 0;
c = 0;
r = sscanf(" 12x", "%d%c", &a, &c);
check(r == 2 && a == 12 && c == 'x', "%d%c splits a number and its junk");
a = b = 0;
c = 0;
r = sscanf("a b", "%c%c", &c, &c5[0]);
check(r == 2 && c == 'a' && c5[0] == ' ', "%c%c reads a and the space");
c = 0;
r = sscanf("abc", "%*2c%c", &c);
check(r == 1 && c == 'c', "suppressed %2c skips two characters");
c = '?';
r = sscanf("xy", "%*c%c", &c);
check(r == 1 && c == 'y', "suppressed %c skips one character");
c5[0] = 'Q';
{
char fmt0c[4];
mkfmt(fmt0c, "%0c");
r = sscanf("y", fmt0c, c5);
}
check(r == 1 && c5[0] == 'y', "%0c behaves like %c");
c5[0] = c5[1] = c5[2] = '?';
r = sscanf("123", "%3c", c5);
check(r == 1 && c5[0] == '1' && c5[1] == '2' && c5[2] == '3', "%3c reads three chars");
c5[0] = c5[1] = c5[2] = '?';
r = sscanf("ab", "%2c", c5);
check(r == 1 && c5[0] == 'a' && c5[1] == 'b', "%2c reads two chars");
c5[0] = c5[1] = c5[2] = '?';
r = sscanf("a", "%3c", c5);
check(r == EOF && c5[0] == 'a' && c5[1] == '?', "%3c at EOF writes what it got, returns EOF");
c5[0] = c5[1] = c5[2] = '?';
r = sscanf("", "%3c", c5);
check(r == EOF && c5[0] == '?', "%3c on empty input stores nothing");
a = b = 0;
c5[0] = c5[1] = c5[2] = '?';
r = sscanf("12ab", "%2d%3c", &a, c5);
check(r == 1 && a == 12 && c5[0] == 'a' && c5[1] == 'b' && c5[2] == '?',
"EOF inside %c returns the count so far");
c5[0] = c5[1] = c5[2] = '?';
r = sscanf("123", "%3c%c", c5, &c);
check(r == 1 && c5[0] == '1' && c5[1] == '2' && c5[2] == '3', "%c after %3c hits EOF cleanly");
buf[0] = 0;
r = sscanf("abc", "%[a-z]x", buf);
check(r == 1 && strcmp(buf, "abc") == 0, "literal after a scanset hits EOF, returns 1");
/* fscanf empty scanset: the character is not consumed. */
{
FILE *fp = tmpfile();
int c2;
char fmt_empty[4];
mkfmt(fmt_empty, "%[]");
fputs("ab", fp);
fseek(fp, 0, SEEK_SET);
r = fscanf(fp, fmt_empty, buf);
c2 = fgetc(fp);
fclose(fp);
check(r == 0 && c2 == 'a', "empty scanset leaves the first character");
}
{
FILE *fp = tmpfile();
int c2;
char fmt_nempty[5];
mkfmt(fmt_nempty, "%[^]");
fputs("ab", fp);
fseek(fp, 0, SEEK_SET);
r = fscanf(fp, fmt_nempty, buf);
c2 = fgetc(fp);
fclose(fp);
check(r == 0 && c2 == 'a', "negated empty scanset leaves the first character");
}
}
static void
pointer_scenarios(void)
{
void *p;
int r;
uintptr_t v;
p = 0;
r = sscanf("0x1234", "%p", &p);
check(r == 1 && p == (void *)0x1234, "%p reads a hex pointer");
p = 0;
r = sscanf("1234", "%p", &p);
check(r == 1 && p == (void *)0x1234, "%p reads bare hex digits");
p = 0;
r = sscanf("-0x1234", "%p", &p);
v = (uintptr_t)p;
check(r == 1 && v == (uintptr_t)0xffffffffffffedccULL, "%p negates a signed pointer");
p = 0;
r = sscanf("zzz", "%p", &p);
check(r == 0, "%p fails on non-hex input");
}
static int
call_vfscanf(FILE *fp, const char *fmt, ...)
{
va_list ap;
int r;
va_start(ap, fmt);
r = vfscanf(fp, fmt, ap);
va_end(ap);
return r;
}
static int
call_vsscanf(const char *s, const char *fmt, ...)
{
va_list ap;
int r;
va_start(ap, fmt);
r = vsscanf(s, fmt, ap);
va_end(ap);
return r;
}
static int
call_vscanf(const char *fmt, ...)
{
va_list ap;
int r;
va_start(ap, fmt);
r = vscanf(fmt, ap);
va_end(ap);
return r;
}
static void
stream_scenarios(void)
{
FILE *fp;
int a;
int b;
int r;
/* vfscanf directly over tmpfile. */
fp = tmpfile();
fputs("7 8", fp);
fseek(fp, 0, SEEK_SET);
a = b = 0;
r = call_vfscanf(fp, "%d %d", &a, &b);
fclose(fp);
check(r == 2 && a == 7 && b == 8, "vfscanf reads through a va_list");
/* vsscanf. */
a = b = 0;
r = call_vsscanf("7 8", "%d %d", &a, &b);
check(r == 2 && a == 7 && b == 8, "vsscanf reads through a va_list");
/* scanf/vscanf through a rebound stdin. */
{
FILE *w = fopen("/tmp/vlibc-scan-test-in", "w");
fputs("7 8 9\n", w);
fclose(w);
stdin = freopen("/tmp/vlibc-scan-test-in", "r", stdin);
a = b = 0;
r = call_vscanf("%d %d", &a, &b);
check(r == 2 && a == 7 && b == 8, "vscanf reads from stdin");
fseek(stdin, 0, SEEK_SET);
a = 0;
r = scanf("%d", &a);
check(r == 1 && a == 7, "scanf reads from stdin");
fclose(stdin);
remove("/tmp/vlibc-scan-test-in");
}
/* fscanf consumed exactly; the leftover belongs to the stream. */
fp = tmpfile();
fputs("12x", fp);
fseek(fp, 0, SEEK_SET);
a = 0;
r = fscanf(fp, "%d", &a);
check(r == 1 && a == 12 && fgetc(fp) == 'x', "fscanf pushback: the x is still there");
fclose(fp);
/* sscanf consumes the offending character only within its string. */
{
char ch = 0;
a = 0;
r = sscanf("5x6", "%d%c", &a, &ch);
check(r == 2 && a == 5 && ch == 'x', "sscanf sees the pushed-back character again");
}
}
/*
* -f scenarios: the ones that write errno (integer overflow) or read it.
* These leave through the raw syscall so the host cleanup never runs after
* the TCB errno slot has been touched (test_stdio convention).
*/
static int
failure_scenarios(void)
{
int i;
int r;
unsigned u;
long l;
long long ll;
unsigned long long ull;
errno = 0;
i = 0;
r = sscanf("999999999999999999999", "%d", &i);
check(r == 1 && i == -1 && errno == ERANGE,
"%d overflow saturates in intmax and truncates into int");
errno = 0;
u = 0;
r = sscanf("999999999999999999999", "%u", &u);
check(r == 1 && u == UINT_MAX && errno == ERANGE, "%u overflow saturates and sets ERANGE");
errno = 0;
ll = 0;
r = sscanf("999999999999999999999999999", "%lld", &ll);
check(r == 1 && ll == LLONG_MAX && errno == ERANGE, "%lld overflow saturates and sets ERANGE");
errno = 0;
l = 0;
r = sscanf("99999999999999999999", "%ld", &l);
check(r == 1 && l == LONG_MAX && errno == ERANGE, "%ld overflow saturates and sets ERANGE");
errno = 0;
ll = 0;
r = sscanf("-99999999999999999999", "%lld", &ll);
check(r == 1 && ll == LLONG_MIN && errno == ERANGE, "negative overflow saturates at LLONG_MIN");
errno = 0;
ull = 0;
r = sscanf("18446744073709551616", "%llu", &ull);
check(r == 1 && ull == ULLONG_MAX && errno == ERANGE,
"%llu overflow saturates and sets ERANGE");
errno = 0;
ull = 0;
r = sscanf("-18446744073709551616", "%llu", &ull);
check(r == 1 && ull == ULLONG_MAX && errno == ERANGE,
"negative unsigned overflow keeps the saturated magnitude");
errno = 0;
ull = 0;
r = sscanf("-99999999999999999999999", "%llu", &ull);
check(r == 1 && ull == ULLONG_MAX && errno == ERANGE, "unsigned overflow drops the sign");
errno = 0;
{
short hs = 0;
r = sscanf("999999999", "%hd", &hs);
check(r == 1 && hs == -13825 && errno == 0, "truncation alone never sets ERANGE");
}
errno = 0;
ull = 0;
r = sscanf("18446744073709551615", "%llu", &ull);
check(r == 1 && ull == ULLONG_MAX && errno == 0, "exact ULLONG_MAX is not an overflow");
errno = 0;
i = 0;
{
float fv = 0;
r = sscanf("42 3.14", "%d %f", &i, &fv);
}
check(r == 2 && i == 42 && errno == 0, "a clean scan never touches errno");
errno = 0;
i = 0;
{
double d = 0;
r = sscanf("1e999", "%lf", &d);
check(r == 1 && d > DBL_MAX && errno == 0,
"float overflow stores inf without ERANGE (vlibc policy)");
}
errno = 0;
i = 0;
{
int a2 = 0;
int b2 = 0;
r = sscanf("999999999999999999999 5", "%d %d", &a2, &b2);
check(r == 2 && a2 == -1 && b2 == 5,
"overflow is not a matching failure: scanning continues");
}
errno = 0;
i = 0;
{
int a2 = 0;
int b2 = 0;
r = sscanf("999999999999999999999", "%d %d", &a2, &b2);
check(r == 1 && a2 == -1, "overflow saturation then input failure returns 1");
}
errno = 0;
i = 0;
r = sscanf("-99999999999999999999", "%d", &i);
check(r == 1 && i == 0 && errno == ERANGE, "saturated intmax truncates into int");
return failures > 0 ? 1 : 0;
}
int
main(int argc, char **argv)
{
int rc;
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
{
rc = failure_scenarios();
__syscall1(SYS_exit_group, rc);
return rc; /* not reached */
}
int_scenarios();
float_scenarios();
char_str_scenarios();
pointer_scenarios();
stream_scenarios();
if (failures > 0)
{
say(2, "FAILED (");
say_dec(2, (unsigned long)failures);
say(2, " check(s))\n");
return 1;
}
say(1, "all scanf tests passed\n");
return 0;
}