diff --git a/include/stdio.h b/include/stdio.h index 18c5421..d4de4a3 100644 --- a/include/stdio.h +++ b/include/stdio.h @@ -429,4 +429,35 @@ fmemopen(void *buf, size_t size, const char *mode); FILE * 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 */ diff --git a/src/stdio/vfscanf.c b/src/stdio/vfscanf.c new file mode 100644 index 0000000..ef578c4 --- /dev/null +++ b/src/stdio/vfscanf.c @@ -0,0 +1,1536 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include "stdio_impl.h" + +/* + * vlibc — formatted input (todo 17): the scanf family. + * + * One engine (`scan_common`) behind scanf/fscanf/sscanf/vscanf/vfscanf/ + * vsscanf. Supported conversions: %d %i %u %o %x %X %f %F %e %E %g %G %a %A + * %c %s %[ %p %n %%; flags: assignment suppression '*', field width, length + * modifiers hh h l ll j z t (and L for the float conversions). Wide-char + * forms (%lc/%ls/%l[) are not provided: no wchar support exists yet. + * + * Input source abstraction: a per-call struct with get/unget callbacks. + * The FILE source mirrors fgetc's read path exactly (pushback byte first, + * write-mode flush, stdio_refill, F_EOF) and pushes characters back by + * moving rpos over the bytes that remain in the stream buffer, using the + * F_PUSHED `ungot` slot only for a byte that itself came from that slot + * (the slot_depth counter tracks which case applies). Because every byte + * read from the buffer stays there until its fill is exhausted, this makes + * unlimited pushback correct: a failed conversion never consumes its + * offending character. The string source is a plain cursor over a NUL + * terminated string. + * + * Semantics (all verified against glibc with a probe corpus; see the + * commit test): + * - %d/%i/%u/%o/%x skip leading whitespace, take an optional sign, and + * saturate on 64-bit overflow, storing the truncated target type and + * setting errno = ERANGE only in that case (matching glibc: overflow is + * detected on the wide accumulate, truncation to int/short/... is + * silent). %i detects the base from a 0x/0b/0 prefix, %x accepts an + * optional 0x prefix, %u takes a sign and negates modulo 2^width + * (the negation is skipped when the magnitude overflowed). + * - %f/%e/%g/%a build a width-limited token with a small state machine + * and hand it to strtof/strtod/strtold (todo 11). An incomplete + * exponent ("1e", "0x1.p") makes the whole conversion fail with the + * collected token consumed and the offending character pushed back; + * errno is saved and restored around the strto* call, so range errors + * from the float conversion never leak out (documented divergence: + * glibc leaves ERANGE after %f of "1e999"). + * - %c reads width characters (default 1) and does not NUL-terminate; + * hitting EOF inside a multi-character read leaves the partially read + * characters in place but is an input failure (glibc parity). + * - %s/%[ skip leading whitespace, stop at the first character outside + * the accepted set (which is not consumed), and NUL-terminate; a + * scanset that matches nothing (%[] and %[^]) always fails. + * - %n stores the net number of characters consumed so far (it is not + * counted as an assignment, suppressed or not); %% matches '%'. + * - Whitespace in the format matches any run of C-locale whitespace. + * - The return value counts successful non-suppressed assignments; it is + * EOF when an input failure happens before the first one and the count + * so far on a matching or later input failure. + */ + +/* Longest float token kept in the converter's stack buffer. A longer token + * (field width unbounded) is still consumed correctly, but the conversion + * fails rather than parse a truncated number. */ +#define SCAN_FLOAT_TOK 4096 + +/* Length modifier codes. */ +enum +{ + SCAN_MOD_NONE = 0, + SCAN_MOD_HH, + SCAN_MOD_H, + SCAN_MOD_L, + SCAN_MOD_LL, + SCAN_MOD_J, + SCAN_MOD_Z, + SCAN_MOD_T, + SCAN_MOD_LD /* 'L': long double, float conversions only */ +}; + +/* Conversion outcomes. */ +enum +{ + SCAN_OK = 0, /* matched and (possibly) stored */ + SCAN_FAIL, /* matching failure: stop, return the count so far */ + SCAN_INPUT /* input failure: return count so far, or EOF */ +}; + +struct scan_ctx; + +struct scan_src +{ + int (*get)(struct scan_src *src); + void (*unget)(struct scan_src *src, int c); + FILE *f; /* FILE source */ + const char *s; /* string source */ + int slot_depth; /* FILE source: buffer bytes read since the pushback slot */ +}; + +struct scan_ctx +{ + struct scan_src src; + size_t nread; /* net characters consumed (get - unget) */ + int nasn; /* successful non-suppressed assignments */ +}; + +/* ---- char sources -------------------------------------------------- */ + +/* FILE source: the exact fgetc read path (todo 15). */ +static int +scan_file_get(struct scan_src *src) +{ + FILE *f = src->f; + + stdio_init_if_needed(f); + if (f->flags & F_PUSHED) + { + f->flags &= ~F_PUSHED; + src->slot_depth = 0; + return f->ungot; + } + if (f->flags & F_WRITE) + { + if (!(f->flags & F_READ)) + { + /* Reading a write-only stream. */ + f->flags |= F_ERR; + return EOF; + } + if (stdio_flush(f) < 0) + { + return EOF; + } + f->flags &= ~F_WRITE; + f->flags |= F_READ; + } + f->flags |= F_READ; + if (f->rpos >= f->rstop) + { + stdio_refill(f); + } + if (f->rpos >= f->rstop) + { + return EOF; /* F_EOF or F_ERR set by refill */ + } + src->slot_depth++; + return *f->rpos++; +} + +/* + * FILE source pushback. A byte read from the stream buffer is still in + * [buf, rpos), so it is put back by moving rpos; a byte that came from the + * F_PUSHED slot goes back into that slot. Consecutive ungets walk the + * consumed region in reverse, so the slot_depth counter always knows which + * case applies and multi-byte pushback stays ordered. + */ +static void +scan_file_unget(struct scan_src *src, int c) +{ + FILE *f = src->f; + + f->flags &= ~F_EOF; + if (src->slot_depth == 0) + { + f->ungot = (unsigned char)c; + f->flags |= F_PUSHED; + return; + } + src->slot_depth--; + *--f->rpos = (unsigned char)c; +} + +static int +scan_str_get(struct scan_src *src) +{ + unsigned char c = (unsigned char)*src->s; + + if (c == '\0') + { + return EOF; + } + src->s++; + return c; +} + +static void +scan_str_unget(struct scan_src *src, int c) +{ + (void)c; + src->s--; +} + +/* ---- engine plumbing ----------------------------------------------- */ + +static int +scan_get(struct scan_ctx *ctx) +{ + int c = ctx->src.get(&ctx->src); + + if (c != EOF) + { + ctx->nread++; + } + return c; +} + +static void +scan_unget(struct scan_ctx *ctx, int c) +{ + if (c != EOF) + { + ctx->src.unget(&ctx->src, c); + ctx->nread--; + } +} + +/* C-locale whitespace: exactly ' ' and '\t'..'\r'. */ +static int +scan_isspace(int c) +{ + return c == ' ' || (unsigned int)c - '\t' < 5U; +} + +/* Value of c as a base-36 digit, or -1 (the strtox.h idiom). */ +static int +scan_digit36(int c) +{ + if ((unsigned int)c - '0' < 10U) + { + return c - '0'; + } + if ((unsigned int)(c | 0x20) - 'a' < 26U) + { + return (c | 0x20) - 'a' + 10; + } + return -1; +} + +/* 1 when c is a decimal digit (float tokens never take letters outside + * the hex mode and the inf/nan words). */ +static int +scan_isdig10(int c) +{ + return (unsigned int)c - '0' < 10U; +} + +/* 1 when c is a hex digit. */ +static int +scan_ishexdig(int c) +{ + return ((unsigned int)c - '0' < 10U) || ((unsigned int)(c | 0x20) - 'a' < 6U); +} + +/* Skip and discard whitespace; -1 on input failure (EOF reached). */ +static int +scan_skip_ws(struct scan_ctx *ctx) +{ + for (;;) + { + int c = scan_get(ctx); + + if (c == EOF) + { + return -1; + } + if (!scan_isspace(c)) + { + scan_unget(ctx, c); + return 0; + } + } +} + +/* vararg plumbing: pointer fetch for the int length family. */ +// NOLINTBEGIN(bugprone-branch-clone) +static void * +scan_int_ptr(int mod, va_list ap) +{ + switch (mod) + { + case SCAN_MOD_NONE: + case SCAN_MOD_HH: + case SCAN_MOD_H: + return va_arg(ap, int *); + case SCAN_MOD_L: + return va_arg(ap, long *); + case SCAN_MOD_LL: + return va_arg(ap, long long *); + case SCAN_MOD_J: + return va_arg(ap, intmax_t *); + case SCAN_MOD_Z: + return va_arg(ap, size_t *); + default: + return va_arg(ap, ptrdiff_t *); + } +} + +// NOLINTEND(bugprone-branch-clone) + +/* Store a parsed integer, truncating to the target width. */ +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +static void +scan_store_int(void *p, int mod, int unsig, uintmax_t mag, int neg, int over) +{ + uintmax_t u; + + if (unsig) + { + /* Overflow keeps the saturated magnitude (the sign is dropped, + * glibc parity); otherwise the sign negates modulo 2^64. */ + u = over ? UINTMAX_MAX : (neg ? (uintmax_t)0 - mag : mag); + } + else + { + intmax_t s = (over || mag > (uintmax_t)INTMAX_MAX) ? (neg ? INTMAX_MIN : INTMAX_MAX) + : (neg ? -(intmax_t)mag : (intmax_t)mag); + + u = (uintmax_t)s; + } + switch (mod) + { + case SCAN_MOD_NONE: + if (unsig) + { + *(unsigned int *)p = (unsigned int)u; + } + else + { + *(int *)p = (int)u; + } + break; + case SCAN_MOD_HH: + if (unsig) + { + *(unsigned char *)p = (unsigned char)u; + } + else + { + *(signed char *)p = (signed char)u; + } + break; + case SCAN_MOD_H: + if (unsig) + { + *(unsigned short *)p = (unsigned short)u; + } + else + { + *(short *)p = (short)u; + } + break; + case SCAN_MOD_L: + if (unsig) + { + *(unsigned long *)p = (unsigned long)u; + } + else + { + *(long *)p = (long)u; + } + break; + case SCAN_MOD_LL: + if (unsig) + { + *(unsigned long long *)p = (unsigned long long)u; + } + else + { + *(long long *)p = (long long)u; + } + break; + case SCAN_MOD_J: + if (unsig) + { + *(uintmax_t *)p = u; + } + else + { + *(intmax_t *)p = (intmax_t)u; + } + break; + case SCAN_MOD_Z: + *(size_t *)p = (size_t)u; + break; + default: + *(ptrdiff_t *)p = (ptrdiff_t)(intmax_t)u; + break; + } +} +// NOLINTEND(bugprone-easily-swappable-parameters) + +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +static void +scan_store_count(void *p, int mod, size_t n) +{ + switch (mod) + { + case SCAN_MOD_NONE: + *(int *)p = (int)n; + break; + case SCAN_MOD_HH: + *(signed char *)p = (signed char)n; + break; + case SCAN_MOD_H: + *(short *)p = (short)n; + break; + case SCAN_MOD_L: + *(long *)p = (long)n; + break; + case SCAN_MOD_LL: + *(long long *)p = (long long)n; + break; + case SCAN_MOD_J: + *(intmax_t *)p = (intmax_t)n; + break; + case SCAN_MOD_Z: + *(size_t *)p = n; + break; + default: + *(ptrdiff_t *)p = (ptrdiff_t)n; + break; + } +} +// NOLINTEND(bugprone-easily-swappable-parameters) + +/* ---- conversions --------------------------------------------------- */ + +/* + * Parse an integer subject sequence: optional sign, base resolution for %i + * (0x/0X -> 16, 0b/0B -> 2, 0 -> 8, else 10) and an optional 0x prefix for + * an explicit base 16, then digits up to the field width, saturating on + * uintmax overflow. The width counts every character of the subject (sign, + * prefix, digits). On a matching failure the offending character is pushed + * back but any sign/prefix already read stays consumed (glibc parity). + */ +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +static int +scan_number(struct scan_ctx *ctx, int width, int base, int iscan, uintmax_t *magp, int *negp, + int *overp) +{ + uintmax_t mag = 0; + int neg = 0; + int over = 0; + int ndig = 0; + int nitem = 0; + int c; + + if (iscan) + { + base = 10; /* %i: resolved from the prefix below, decimal without one */ + } + if (scan_skip_ws(ctx) < 0) + { + return SCAN_INPUT; + } + c = scan_get(ctx); + if (c == EOF) + { + return SCAN_INPUT; + } + if (c == '+' || c == '-') + { + neg = (c == '-'); + nitem++; + c = scan_get(ctx); + if (c == EOF) + { + return SCAN_FAIL; /* bare sign, consumed */ + } + } + if (iscan && c == '0') + { + int c2 = scan_get(ctx); + + if (c2 == 'x' || c2 == 'X') + { + base = 16; + nitem += 2; + c = scan_get(ctx); + if (c == EOF) + { + return SCAN_FAIL; /* "0x" with no hex digit: consumed */ + } + } + else if (c2 == 'b' || c2 == 'B') + { + base = 2; + nitem += 2; + c = scan_get(ctx); + if (c == EOF) + { + return SCAN_FAIL; + } + } + else + { + scan_unget(ctx, c2); + base = 8; + ndig = 1; + nitem++; + c = scan_get(ctx); + if (c == EOF) + { + goto number_done; + } + } + } + else if (!iscan && base == 16 && c == '0') + { + int c2 = scan_get(ctx); + + if (c2 == 'x' || c2 == 'X') + { + nitem += 2; + c = scan_get(ctx); + if (c == EOF) + { + ndig = 1; /* "0x" alone: the '0' is the number */ + goto number_done; + } + } + else + { + scan_unget(ctx, c2); + } + } + for (;;) + { + int d; + + if (width >= 0 && nitem >= width) + { + scan_unget(ctx, c); + break; + } + d = scan_digit36(c); + if (d < 0 || d >= base) + { + scan_unget(ctx, c); + break; + } + if (!over) + { + uintmax_t nm = mag * (uintmax_t)base + (uintmax_t)d; + + if (nm / (uintmax_t)base != mag) + { + over = 1; + mag = UINTMAX_MAX; + } + else + { + mag = nm; + } + } + ndig++; + nitem++; + c = scan_get(ctx); + if (c == EOF) + { + break; + } + } +number_done: + if (ndig == 0) + { + return SCAN_FAIL; + } + *magp = mag; + *negp = neg; + *overp = over; + return SCAN_OK; +} +// NOLINTEND(bugprone-easily-swappable-parameters) + +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +static int +conv_int(struct scan_ctx *ctx, int width, int base, int unsig, int mod, int suppress, va_list ap) +{ + void *p; + uintmax_t mag; + int neg; + int over; + int r; + + if (mod == SCAN_MOD_LD) + { + mod = SCAN_MOD_NONE; /* 'L' only applies to float conversions */ + } + if (!suppress) + { + p = scan_int_ptr(mod, ap); + } + else + { + /* A suppressed conversion reads no argument at all. */ + p = NULL; + } + r = scan_number(ctx, width, base, base == 0, &mag, &neg, &over); + if (r != SCAN_OK) + { + return r; + } + if (!suppress) + { + scan_store_int(p, mod, unsig, mag, neg, over); + } + if (over || (!unsig && mag > (uintmax_t)INTMAX_MAX)) + { + errno = ERANGE; /* wide overflow only; truncation is silent */ + } + return SCAN_OK; +} +// NOLINTEND(bugprone-easily-swappable-parameters) + +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +static int +conv_ptr(struct scan_ctx *ctx, int width, int suppress, va_list ap) +{ + void **pp; + uintmax_t mag; + int neg; + int over; + int r; + + if (suppress) + { + pp = NULL; /* a suppressed conversion reads no argument */ + } + else + { + pp = va_arg(ap, void **); + } + r = scan_number(ctx, width, 16, 0, &mag, &neg, &over); + if (r != SCAN_OK) + { + return r; + } + if (!suppress) + { + uintmax_t v = over ? UINTMAX_MAX : (neg ? (uintmax_t)0 - mag : mag); + + *pp = (void *)(uintptr_t)v; + } + if (over) + { + errno = ERANGE; + } + return SCAN_OK; +} +// NOLINTEND(bugprone-easily-swappable-parameters) + +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +static int +conv_char(struct scan_ctx *ctx, int width, int suppress, va_list ap) +{ + char *dst; + int i; + + if (suppress) + { + dst = NULL; /* a suppressed conversion reads no argument */ + } + else + { + dst = va_arg(ap, char *); + } + if (width < 0) + { + width = 1; + } + for (i = 0; i < width; i++) + { + int c = scan_get(ctx); + + if (c == EOF) + { + /* Input failure; partially read characters stay stored + * (glibc parity, probed). */ + return SCAN_INPUT; + } + if (dst != NULL) + { + dst[i] = (char)c; + } + } + return SCAN_OK; +} +// NOLINTEND(bugprone-easily-swappable-parameters) + +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +static int +conv_string(struct scan_ctx *ctx, int width, int suppress, va_list ap) +{ + char *dst; + int i = 0; + int c; + + if (suppress) + { + dst = NULL; /* a suppressed conversion reads no argument */ + } + else + { + dst = va_arg(ap, char *); + } + if (scan_skip_ws(ctx) < 0) + { + return SCAN_INPUT; + } + for (;;) + { + if (width >= 0 && i >= width) + { + break; + } + c = scan_get(ctx); + if (c == EOF) + { + break; + } + if (scan_isspace(c)) + { + scan_unget(ctx, c); + break; + } + if (dst != NULL) + { + dst[i] = (char)c; + } + i++; + } + if (dst != NULL) + { + dst[i] = '\0'; + } + return SCAN_OK; +} +// NOLINTEND(bugprone-easily-swappable-parameters) + +/* Parse the scanset body (after '['); sets *negp and returns 1 when the + * set is empty (matches nothing either way, glibc parity). */ +static int +scan_parse_set(const char *p, const char **endp, unsigned char *set, int *negp) +{ + int neg = 0; + + if (*p == '^') + { + neg = 1; + p++; + } + *negp = neg; + if (*p == ']') + { + if (p[1] == '\0') + { + /* "%[]" / "%[^]": ']' right after '[' or '^' ends the spec. */ + *endp = p + 1; + return 1; + } + /* ']' as the first member. */ + set[(unsigned char)']' >> 3] |= (unsigned char)(1U << (']' & 7)); + p++; + } + while (*p != '\0' && *p != ']') + { + unsigned char lo = (unsigned char)*p; + + if (p[1] == '-' && p[2] != '\0' && p[2] != ']') + { + unsigned char hi = (unsigned char)p[2]; + int c; + + if (lo > hi) + { + unsigned char t = lo; + + lo = hi; + hi = t; + } + for (c = lo; c <= hi; c++) + { + set[(unsigned int)c >> 3] |= (unsigned char)(1U << (c & 7)); + } + p += 3; + } + else + { + set[lo >> 3] |= (unsigned char)(1U << (lo & 7)); + p++; + } + } + *endp = (*p == ']') ? p + 1 : p; + return 0; +} + +static int +scan_in_set(unsigned char c, const unsigned char *set, int neg, int empty) +{ + int bit = (set[c >> 3] >> (c & 7)) & 1; + + return !empty && (bit ^ neg); +} + +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +static int +conv_scanset(struct scan_ctx *ctx, int width, int suppress, int neg, int empty, + const unsigned char *set, va_list ap) +{ + char *dst; + int i = 0; + int c; + + if (suppress) + { + dst = NULL; /* a suppressed conversion reads no argument */ + } + else + { + dst = va_arg(ap, char *); + } + c = scan_get(ctx); + if (c == EOF) + { + return SCAN_INPUT; + } + if (!scan_in_set((unsigned char)c, set, neg, empty)) + { + /* The first character must match; on failure nothing is stored. */ + scan_unget(ctx, c); + return SCAN_FAIL; + } + if (dst != NULL) + { + dst[i] = (char)c; + } + i++; + for (;;) + { + if (width >= 0 && i >= width) + { + break; + } + c = scan_get(ctx); + if (c == EOF) + { + break; + } + if (!scan_in_set((unsigned char)c, set, neg, empty)) + { + scan_unget(ctx, c); + break; + } + if (dst != NULL) + { + dst[i] = (char)c; + } + i++; + } + if (dst != NULL) + { + dst[i] = '\0'; + } + return SCAN_OK; +} +// NOLINTEND(bugprone-easily-swappable-parameters) + +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +static int +conv_count(struct scan_ctx *ctx, int mod, int suppress, va_list ap) +{ + void *p; + + if (mod == SCAN_MOD_LD) + { + mod = SCAN_MOD_NONE; + } + if (!suppress) + { + p = scan_int_ptr(mod, ap); + scan_store_count(p, mod, ctx->nread); + } + return SCAN_OK; +} +// NOLINTEND(bugprone-easily-swappable-parameters) + +/* Float tokenizer states. */ +enum +{ + FS_SIGN, /* sign read: digit, '.', or inf/nan next */ + FS_INT, /* integer digits */ + FS_DOT, /* after '.': a digit must follow */ + FS_FRAC, /* fraction digits */ + FS_EXP, /* after e/E: digit or sign must follow */ + FS_EXPD, /* exponent digits */ + FS_HI, /* after 0x: hex digit, '.', or p next */ + FS_HD, /* after the hex '.': hex digit must follow */ + FS_HF, /* hex fraction digits */ + FS_HP, /* after p/P: digit or sign must follow */ + FS_HPD /* hex exponent digits */ +}; + +/* + * Collect a float token (width limited) and convert it with + * strtof/strtod/strtold. An exponent marker not followed by at least one + * digit fails the conversion, with everything collected so far consumed + * and the offending character pushed back; a token that strto* only + * partially consumes ("0x", ".") fails the same way. errno is restored + * around the strto* call: range errors of the float conversion are not + * supposed to leak out of scanf. + */ +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +static int +conv_float(struct scan_ctx *ctx, int width, int mod, int suppress, va_list ap) +{ + char tok[SCAN_FLOAT_TOK]; + size_t n = 0; + int cap_hit = 0; + int state; + int special = 0; + int c; + int saved_errno; + + c = scan_get(ctx); + if (c == EOF) + { + return SCAN_INPUT; + } + if ((c | 0x20) == 'i' || (c | 0x20) == 'n') + { + special = 1; + } + else if (c != '+' && c != '-' && c != '.' && !scan_isdig10(c)) + { + scan_unget(ctx, c); + return SCAN_FAIL; + } + tok[n++] = (char)c; + state = (c == '+' || c == '-') ? FS_SIGN : (c == '.') ? FS_DOT : FS_INT; + + for (;;) + { + if (width >= 0 && n >= (size_t)width) + { + break; + } + c = scan_get(ctx); + if (c == EOF) + { + break; + } + if (special) + { + /* inf/nan word: collect letters only while they extend a valid + * "inf"/"infinity"/"nan" prefix; anything else ends the word. */ + size_t wbase = (tok[0] == '+' || tok[0] == '-') ? 1U : 0U; + size_t wl = n - wbase; + int word_ok = 0; + + if ((tok[wbase] | 0x20) == 'i') + { + if (wl < 3) + { + word_ok = (c | 0x20) == "inf"[wl]; + } + else if (wl < 8) + { + word_ok = (c | 0x20) == "inity"[wl - 3]; + } + } + else if (wl < 3) + { + word_ok = (c | 0x20) == "nan"[wl]; + } + if (word_ok) + { + if (n < sizeof(tok) - 1) + { + tok[n++] = (char)c; + } + else + { + cap_hit = 1; + } + continue; + } + if (c == '(' && wl == 3 && (tok[wbase] | 0x20) == 'n') + { + /* nan payload: everything up to the closing ')'. */ + if (n < sizeof(tok) - 1) + { + tok[n++] = (char)c; + } + for (;;) + { + c = scan_get(ctx); + if (c == EOF) + { + break; + } + if (n < sizeof(tok) - 1) + { + tok[n++] = (char)c; + } + else + { + cap_hit = 1; + } + if (c == ')') + { + break; + } + } + continue; + } + scan_unget(ctx, c); + break; + } + switch (state) + { + case FS_SIGN: + if (scan_isdig10(c)) + { + state = FS_INT; + goto putc_tok; + } + if (c == '.') + { + state = FS_DOT; + goto putc_tok; + } + if ((c | 0x20) == 'i' || (c | 0x20) == 'n') + { + special = 1; + goto putc_tok; + } + scan_unget(ctx, c); + return SCAN_FAIL; /* "+x": the sign stays consumed */ + case FS_INT: + if (scan_isdig10(c)) + { + goto putc_tok; + } + if (c == '.') + { + state = FS_DOT; + goto putc_tok; + } + if ((c | 0x20) == 'e') + { + state = FS_EXP; + goto putc_tok; + } + if ((c | 0x20) == 'x' && tok[n - 1] == '0' && + (n == 1 || (n == 2 && (tok[0] == '+' || tok[0] == '-')))) + { + state = FS_HI; + goto putc_tok; + } + scan_unget(ctx, c); + goto token_done; + case FS_DOT: + if (scan_isdig10(c)) + { + state = FS_FRAC; + goto putc_tok; + } + scan_unget(ctx, c); + goto token_done; /* ".": strto* consumes nothing -> FAIL */ + case FS_FRAC: + if (scan_isdig10(c)) + { + goto putc_tok; + } + if ((c | 0x20) == 'e') + { + state = FS_EXP; + goto putc_tok; + } + scan_unget(ctx, c); + goto token_done; + case FS_EXP: + if (scan_isdig10(c)) + { + state = FS_EXPD; + goto putc_tok; + } + if (c == '+' || c == '-') + { + goto putc_tok; + } + scan_unget(ctx, c); + return SCAN_FAIL; /* "1e" + junk: incomplete exponent */ + case FS_EXPD: + if (scan_isdig10(c)) + { + goto putc_tok; + } + scan_unget(ctx, c); + goto token_done; + case FS_HI: + if (scan_ishexdig(c)) + { + goto putc_tok; + } + if (c == '.') + { + state = FS_HD; + goto putc_tok; + } + if ((c | 0x20) == 'p') + { + state = FS_HP; + goto putc_tok; + } + scan_unget(ctx, c); + goto token_done; /* "0x" alone: strto* consumes "0" only */ + case FS_HD: + if (scan_ishexdig(c)) + { + state = FS_HF; + goto putc_tok; + } + if ((c | 0x20) == 'p') + { + /* "0x1.p": the marker is part of the token, and without + * exponent digits the whole conversion fails (glibc). */ + state = FS_HP; + goto putc_tok; + } + scan_unget(ctx, c); + goto token_done; /* "0x.": partial -> FAIL */ + case FS_HF: + if (scan_ishexdig(c)) + { + goto putc_tok; + } + if ((c | 0x20) == 'p') + { + state = FS_HP; + goto putc_tok; + } + scan_unget(ctx, c); + goto token_done; + case FS_HP: + if (scan_isdig10(c)) + { + state = FS_HPD; + goto putc_tok; + } + if (c == '+' || c == '-') + { + goto putc_tok; + } + scan_unget(ctx, c); + return SCAN_FAIL; /* "0x1.p" + junk: incomplete exponent */ + case FS_HPD: + if (scan_isdig10(c)) + { + goto putc_tok; + } + scan_unget(ctx, c); + goto token_done; + default: + scan_unget(ctx, c); + goto token_done; + } + continue; + putc_tok: + if (n < sizeof(tok) - 1) + { + tok[n++] = (char)c; + } + else + { + cap_hit = 1; + } + continue; + token_done: + break; + } + /* EOF right after an exponent marker: incomplete exponent. */ + if (state == FS_EXP || state == FS_HP) + { + return SCAN_FAIL; + } + if (cap_hit) + { + return SCAN_FAIL; /* token too long: consumed, conversion failed */ + } + tok[n] = '\0'; + saved_errno = errno; + if (mod == SCAN_MOD_LD) + { + char *end = NULL; + long double ld = strtold(tok, &end); + + errno = saved_errno; + if (end != tok + n) + { + return SCAN_FAIL; /* token only partially consumed */ + } + if (!suppress) + { + *(long double *)va_arg(ap, long double *) = ld; + } + return SCAN_OK; + } + if (mod == SCAN_MOD_L) + { + char *end = NULL; + double d = strtod(tok, &end); + + errno = saved_errno; + if (end != tok + n) + { + return SCAN_FAIL; /* token only partially consumed */ + } + if (!suppress) + { + *(double *)va_arg(ap, double *) = d; + } + return SCAN_OK; + } + { + char *end = NULL; + float f = strtof(tok, &end); + + errno = saved_errno; + if (end != tok + n) + { + return SCAN_FAIL; /* token only partially consumed */ + } + if (!suppress) + { + *(float *)va_arg(ap, float *) = f; + } + return SCAN_OK; + } +} +// NOLINTEND(bugprone-easily-swappable-parameters) + +/* ---- the engine ---------------------------------------------------- */ + +static int +scan_common(struct scan_ctx *ctx, const char *fmt, va_list ap) +{ + const char *p = fmt; + + while (*p != '\0') + { + int r; + + if (*p != '%') + { + if (scan_isspace((unsigned char)*p)) + { + if (scan_skip_ws(ctx) < 0) + { + return ctx->nasn == 0 ? EOF : ctx->nasn; + } + p++; + continue; + } + { + int c = scan_get(ctx); + + if (c == EOF) + { + return ctx->nasn == 0 ? EOF : ctx->nasn; + } + if (c != (unsigned char)*p) + { + scan_unget(ctx, c); + return ctx->nasn; + } + } + p++; + continue; + } + /* A conversion directive. */ + { + int suppress = 0; + int width = -1; + int mod = SCAN_MOD_NONE; + char conv; + + p++; + if (*p == '*') + { + suppress = 1; + p++; + } + if (*p >= '0' && *p <= '9') + { + width = 0; + while (*p >= '0' && *p <= '9') + { + width = width * 10 + (*p - '0'); + p++; + } + if (width == 0) + { + width = -1; /* field width 0: unlimited (glibc parity) */ + } + } + for (;;) + { + if (p[0] == 'h' && p[1] == 'h') + { + mod = SCAN_MOD_HH; + p += 2; + } + else if (p[0] == 'l' && p[1] == 'l') + { + mod = SCAN_MOD_LL; + p += 2; + } + else if (p[0] == 'h') + { + mod = SCAN_MOD_H; + p++; + } + else if (p[0] == 'l') + { + mod = SCAN_MOD_L; + p++; + } + else if (p[0] == 'j') + { + mod = SCAN_MOD_J; + p++; + } + else if (p[0] == 'z') + { + mod = SCAN_MOD_Z; + p++; + } + else if (p[0] == 't') + { + mod = SCAN_MOD_T; + p++; + } + else if (p[0] == 'L') + { + mod = SCAN_MOD_LD; + p++; + } + else + { + break; + } + } + conv = *p; + if (conv == '\0') + { + break; /* trailing '%' at the end of the format */ + } + switch (conv) + { + case 'd': + case 'i': + r = conv_int(ctx, width, conv == 'i' ? 0 : 10, 0, mod, suppress, ap); + break; + case 'u': + r = conv_int(ctx, width, 10, 1, mod, suppress, ap); + break; + case 'o': + r = conv_int(ctx, width, 8, 1, mod, suppress, ap); + break; + case 'x': + case 'X': + r = conv_int(ctx, width, 16, 1, mod, suppress, ap); + break; + case 'p': + r = conv_ptr(ctx, width, suppress, ap); + break; + case 'f': + case 'F': + case 'e': + case 'E': + case 'g': + case 'G': + case 'a': + case 'A': + r = conv_float(ctx, width, mod, suppress, ap); + break; + case 'c': + r = conv_char(ctx, width, suppress, ap); + break; + case 's': + r = conv_string(ctx, width, suppress, ap); + break; + case '[': + { + unsigned char set[32] = {0}; + int neg = 0; + int empty; + const char *end; + + p++; + empty = scan_parse_set(p, &end, set, &neg); + r = conv_scanset(ctx, width, suppress, neg, empty, set, ap); + p = end - 1; /* the loop's p++ lands on end */ + break; + } + case 'n': + r = conv_count(ctx, mod, suppress, ap); + break; + case '%': + { + int c = scan_get(ctx); + + if (c == EOF) + { + r = SCAN_INPUT; + } + else if (c != '%') + { + scan_unget(ctx, c); + r = SCAN_FAIL; + } + else + { + r = SCAN_OK; + } + break; + } + default: + { + /* Unknown specifier: glibc matches it as a literal. */ + int c = scan_get(ctx); + + if (c == EOF) + { + r = SCAN_INPUT; + } + else if (c != (unsigned char)conv) + { + scan_unget(ctx, c); + r = SCAN_FAIL; + } + else + { + r = SCAN_OK; + } + break; + } + } + if (r == SCAN_FAIL) + { + return ctx->nasn; + } + if (r == SCAN_INPUT) + { + return ctx->nasn == 0 ? EOF : ctx->nasn; + } + if (!suppress && conv != 'n' && conv != '%') + { + ctx->nasn++; + } + p++; + } + } + return ctx->nasn; +} + +/* ---- entry points -------------------------------------------------- */ + +int +vfscanf(FILE *restrict f, const char *restrict fmt, va_list ap) +{ + struct scan_ctx ctx = {0}; + + ctx.src.get = scan_file_get; + ctx.src.unget = scan_file_unget; + ctx.src.f = f; + return scan_common(&ctx, fmt, ap); +} + +int +vscanf(const char *restrict fmt, va_list ap) +{ + return vfscanf(stdin, fmt, ap); +} + +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +int +vsscanf(const char *restrict s, const char *restrict fmt, va_list ap) +{ + struct scan_ctx ctx = {0}; + + ctx.src.get = scan_str_get; + ctx.src.unget = scan_str_unget; + ctx.src.s = s; + return scan_common(&ctx, fmt, ap); +} +// NOLINTEND(bugprone-easily-swappable-parameters) + +int +scanf(const char *restrict fmt, ...) +{ + va_list ap; + int r; + + va_start(ap, fmt); + r = vfscanf(stdin, fmt, ap); + va_end(ap); + return r; +} + +int +fscanf(FILE *restrict f, const char *restrict fmt, ...) +{ + va_list ap; + int r; + + va_start(ap, fmt); + r = vfscanf(f, fmt, ap); + va_end(ap); + return r; +} + +int +sscanf(const char *restrict s, const char *restrict fmt, ...) +{ + va_list ap; + int r; + + va_start(ap, fmt); + r = vsscanf(s, fmt, ap); + va_end(ap); + return r; +} diff --git a/tests/test_scanf.c b/tests/test_scanf.c new file mode 100644 index 0000000..239792d --- /dev/null +++ b/tests/test_scanf.c @@ -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 +#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; +}