/* * 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 #include #include #include #include #include #include #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; }