From 14b0136a8e42470dc09a5ee93465ae2e54171326 Mon Sep 17 00:00:00 2001 From: huntedbytheirs Date: Thu, 3 Sep 2026 22:16:12 -0400 Subject: [PATCH] feat(inttypes): PRI/SCN macros + strtoimax family --- include/inttypes.h | 314 +++++++++++++++++++++++++++++++++ src/stdlib/inttypes.c | 268 ++++++++++++++++++++++++++++ tests/test_inttypes.c | 398 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 980 insertions(+) create mode 100644 include/inttypes.h create mode 100644 src/stdlib/inttypes.c create mode 100644 tests/test_inttypes.c diff --git a/include/inttypes.h b/include/inttypes.h new file mode 100644 index 0000000..4196467 --- /dev/null +++ b/include/inttypes.h @@ -0,0 +1,314 @@ +#ifndef VLIBC_INTTYPES_H +#define VLIBC_INTTYPES_H + +/* + * vlibc — . + * + * Extended integer type names, the imax* conversions of the widest signed + * type, and the PRI and SCN formatted-I/O macros (C23 7.8). Every macro below + * expands to a string literal holding the complete conversion specifier + * (length modifier included) for the corresponding type on the + * x86_64 LP64 target, where the compiler predefined types are: + * + * int8_t/uint8_t signed char / unsigned char + * int16_t/uint16_t short / unsigned short + * int32_t/uint32_t int / unsigned int + * int64_t/uint64_t long / unsigned long + * int_leastN_t identical to the exact-width types + * int_fast8_t signed char; int_fast16/32/64_t are long + * intmax_t/uintmax_t long / unsigned long + * intptr_t/uintptr_t long / unsigned long + * + * Design rule (per plan): the fprintf macros name the length modifier of + * the PROMOTED argument — printf receives an int for every sub-int type, + * so PRId8 is "d", not "hhd" — while the fscanf macros name the + * exact-width modifier — scanf receives a pointer, so SCNd8 is "hhd". + * Both forms convert the corresponding type correctly under C23 7.8.1; + * glibc and musl instead use the exact-width modifier in both families, + * which is equally conformant and indistinguishable in behavior for + * correctly-typed arguments. + * + * This header is ISO C core and is present in every profile. + */ + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Quotient and remainder of the widest integer division (C23 7.8.1). */ +typedef struct imaxdiv_t +{ + intmax_t quot; + intmax_t rem; +} imaxdiv_t; + +/* + * fprintf macros for the signed exact-width types (argument promotes to + * int, or stays long for the 64-bit width). + */ +#define PRId8 "d" +#define PRId16 "d" +#define PRId32 "d" +#define PRId64 "ld" + +#define PRIi8 "i" +#define PRIi16 "i" +#define PRIi32 "i" +#define PRIi64 "li" + +/* fprintf macros for the unsigned exact-width types. */ +#define PRIo8 "o" +#define PRIo16 "o" +#define PRIo32 "o" +#define PRIo64 "lo" + +#define PRIu8 "u" +#define PRIu16 "u" +#define PRIu32 "u" +#define PRIu64 "lu" + +#define PRIx8 "x" +#define PRIx16 "x" +#define PRIx32 "x" +#define PRIx64 "lx" + +#define PRIX8 "X" +#define PRIX16 "X" +#define PRIX32 "X" +#define PRIX64 "lX" + +/* fprintf macros for the signed least-width types (same typedefs as the + * exact-width types on x86_64). */ +#define PRIdLEAST8 "d" +#define PRIdLEAST16 "d" +#define PRIdLEAST32 "d" +#define PRIdLEAST64 "ld" + +#define PRIiLEAST8 "i" +#define PRIiLEAST16 "i" +#define PRIiLEAST32 "i" +#define PRIiLEAST64 "li" + +/* fprintf macros for the unsigned least-width types. */ +#define PRIoLEAST8 "o" +#define PRIoLEAST16 "o" +#define PRIoLEAST32 "o" +#define PRIoLEAST64 "lo" + +#define PRIuLEAST8 "u" +#define PRIuLEAST16 "u" +#define PRIuLEAST32 "u" +#define PRIuLEAST64 "lu" + +#define PRIxLEAST8 "x" +#define PRIxLEAST16 "x" +#define PRIxLEAST32 "x" +#define PRIxLEAST64 "lx" + +#define PRIXLEAST8 "X" +#define PRIXLEAST16 "X" +#define PRIXLEAST32 "X" +#define PRIXLEAST64 "lX" + +/* fprintf macros for the signed fastest types (int_fast8_t is signed char, + * which promotes to int; int_fast16/32/64_t are long). */ +#define PRIdFAST8 "d" +#define PRIdFAST16 "ld" +#define PRIdFAST32 "ld" +#define PRIdFAST64 "ld" + +#define PRIiFAST8 "i" +#define PRIiFAST16 "li" +#define PRIiFAST32 "li" +#define PRIiFAST64 "li" + +/* fprintf macros for the unsigned fastest types. */ +#define PRIoFAST8 "o" +#define PRIoFAST16 "lo" +#define PRIoFAST32 "lo" +#define PRIoFAST64 "lo" + +#define PRIuFAST8 "u" +#define PRIuFAST16 "lu" +#define PRIuFAST32 "lu" +#define PRIuFAST64 "lu" + +#define PRIxFAST8 "x" +#define PRIxFAST16 "lx" +#define PRIxFAST32 "lx" +#define PRIxFAST64 "lx" + +#define PRIXFAST8 "X" +#define PRIXFAST16 "lX" +#define PRIXFAST32 "lX" +#define PRIXFAST64 "lX" + +/* fprintf macros for the widest (intmax_t/uintmax_t) and pointer-width + * (intptr_t/uintptr_t) types; both families are long/unsigned long. */ +#define PRIdMAX "ld" +#define PRIiMAX "li" +#define PRIoMAX "lo" +#define PRIuMAX "lu" +#define PRIxMAX "lx" +#define PRIXMAX "lX" + +#define PRIdPTR "ld" +#define PRIiPTR "li" +#define PRIoPTR "lo" +#define PRIuPTR "lu" +#define PRIxPTR "lx" +#define PRIXPTR "lX" + +/* + * fscanf macros for the signed exact-width types: the pointer targets the + * exact type, so the modifier is hh/h/(none)/l by width. + */ +#define SCNd8 "hhd" +#define SCNd16 "hd" +#define SCNd32 "d" +#define SCNd64 "ld" + +#define SCNi8 "hhi" +#define SCNi16 "hi" +#define SCNi32 "i" +#define SCNi64 "li" + +/* fscanf macros for the unsigned exact-width types. */ +#define SCNo8 "hho" +#define SCNo16 "ho" +#define SCNo32 "o" +#define SCNo64 "lo" + +#define SCNu8 "hhu" +#define SCNu16 "hu" +#define SCNu32 "u" +#define SCNu64 "lu" + +#define SCNx8 "hhx" +#define SCNx16 "hx" +#define SCNx32 "x" +#define SCNx64 "lx" + +/* fscanf macros for the signed least-width types (same typedefs as the + * exact-width types on x86_64). */ +#define SCNdLEAST8 "hhd" +#define SCNdLEAST16 "hd" +#define SCNdLEAST32 "d" +#define SCNdLEAST64 "ld" + +#define SCNiLEAST8 "hhi" +#define SCNiLEAST16 "hi" +#define SCNiLEAST32 "i" +#define SCNiLEAST64 "li" + +/* fscanf macros for the unsigned least-width types. */ +#define SCNoLEAST8 "hho" +#define SCNoLEAST16 "ho" +#define SCNoLEAST32 "o" +#define SCNoLEAST64 "lo" + +#define SCNuLEAST8 "hhu" +#define SCNuLEAST16 "hu" +#define SCNuLEAST32 "u" +#define SCNuLEAST64 "lu" + +#define SCNxLEAST8 "hhx" +#define SCNxLEAST16 "hx" +#define SCNxLEAST32 "x" +#define SCNxLEAST64 "lx" + +/* fscanf macros for the signed fastest types (int_fast8_t is signed char; + * int_fast16/32/64_t are long). */ +#define SCNdFAST8 "hhd" +#define SCNdFAST16 "ld" +#define SCNdFAST32 "ld" +#define SCNdFAST64 "ld" + +#define SCNiFAST8 "hhi" +#define SCNiFAST16 "li" +#define SCNiFAST32 "li" +#define SCNiFAST64 "li" + +/* fscanf macros for the unsigned fastest types. */ +#define SCNoFAST8 "hho" +#define SCNoFAST16 "lo" +#define SCNoFAST32 "lo" +#define SCNoFAST64 "lo" + +#define SCNuFAST8 "hhu" +#define SCNuFAST16 "lu" +#define SCNuFAST32 "lu" +#define SCNuFAST64 "lu" + +#define SCNxFAST8 "hhx" +#define SCNxFAST16 "lx" +#define SCNxFAST32 "lx" +#define SCNxFAST64 "lx" + +/* fscanf macros for the widest and pointer-width types. */ +#define SCNdMAX "ld" +#define SCNiMAX "li" +#define SCNoMAX "lo" +#define SCNuMAX "lu" +#define SCNxMAX "lx" + +#define SCNdPTR "ld" +#define SCNiPTR "li" +#define SCNoPTR "lo" +#define SCNuPTR "lu" +#define SCNxPTR "lx" + +/* + * Return the absolute value of n. abs(INTMAX_MIN) is not representable; + * it wraps (implementation-defined, no errno), matching GCC's two's + * complement semantics. + * const: result depends only on the argument (GCC predeclares imaxabs + * with this attribute, so no weaker one may be used). + */ +__attribute__((const)) intmax_t +imaxabs(intmax_t n); + +/* + * Return { quot, rem } of numer / denom, both truncated toward zero with + * the remainder taking the sign of numer (C division semantics). + * const: result depends only on the arguments. + */ +__attribute__((const)) imaxdiv_t +imaxdiv(intmax_t numer, intmax_t denom); + +/* + * Convert the initial portion of nptr to intmax_t, as strtol with the + * widest signed type. intmax_t is long on x86_64 LP64, so this is an + * ABI-identical delegation to strtol (same engine, same errno policy: + * ERANGE clamps, EINVAL for a base outside {0} union [2, 36]). + */ +intmax_t +strtoimax(const char *restrict nptr, char **restrict endptr, int base); + +/* As strtoimax, converted to uintmax_t (unsigned long); negative subject + * sequences wrap modulo 2^64 and are never a range error (C23 7.8.2.3). */ +uintmax_t +strtoumax(const char *restrict nptr, char **restrict endptr, int base); + +/* + * Wide-character forms of strtoimax/strtoumax (C23 7.8.2.4-5). The subject + * sequence is scanned from nptr under the same rules as strtol: C-locale + * whitespace (L' ' and L'\t'..L'\r'), optional sign, base 0/2..36 with the + * 0x/0 prefix rules, ASCII digits only. errno and endptr (of type + * wchar_t **) behave exactly as the narrow forms. + */ +intmax_t +wcstoimax(const wchar_t *restrict nptr, wchar_t **restrict endptr, int base); + +uintmax_t +wcstoumax(const wchar_t *restrict nptr, wchar_t **restrict endptr, int base); + +#ifdef __cplusplus +} +#endif + +#endif /* VLIBC_INTTYPES_H */ diff --git a/src/stdlib/inttypes.c b/src/stdlib/inttypes.c new file mode 100644 index 0000000..2ca7750 --- /dev/null +++ b/src/stdlib/inttypes.c @@ -0,0 +1,268 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include +#include +#include +#include +#include + +/* + * inttypes.h conversions (todo 14). + * + * strtoimax/strtoumax are thin delegations: on x86_64 LP64 intmax_t is + * long and uintmax_t is unsigned long (GCC __INTMAX_TYPE__), so strtol and + * strtoul are ABI-identical — same input, same endptr, same errno policy + * (ERANGE clamps, EINVAL for a base outside {0} union [2, 36]). They share + * the todo-11 engine in src/stdlib/strtox.c through those calls. + * + * wcstoimax/wcstoumax cannot reuse the byte-based engine directly, so this + * file carries a self-contained wide scan: a wchar_t mirror of the + * __strtox_scan algorithm (C-locale whitespace L' ' and L'\t'..L'\r', + * optional sign, base 0/2..36 with the 0x/0 prefix rules, saturating u64 + * magnitude, same clamping policy). wchar_t is a 32-bit code unit here + * (GCC __WCHAR_TYPE__ is int), so digits are matched by ASCII codepoint + * exactly as strtox_digit matches bytes; non-ASCII code units are never + * digits under the C locale. Todo 50 (wchar.h) may later unify this scan + * with the narrow engine. + */ + +/* Wide-scan results; mirrors the STRTOX_* contract of the narrow engine. */ +#define WCSTOX_OK 0 +#define WCSTOX_EMPTY 1 +#define WCSTOX_BADBASE 2 + +/* C-locale whitespace for wide code units: L' ' and L'\t'..L'\r'. */ +static int +wcstox_isspace(wchar_t c) +{ + return c == L' ' || (unsigned int)c - L'\t' < 5U; +} + +/* Value of c as a base-36 digit, or -1 when it is not an ASCII letter or + * digit; | 0x20 folds 'A'..'F' to 'a'..'f' (same idiom as strtox_digit). */ +static int +wcstox_digit(wchar_t c) +{ + if ((unsigned int)c - L'0' < 10U) + { + return (int)c - L'0'; + } + if ((unsigned int)(c | 0x20) - L'a' < 26U) + { + return (int)(c | 0x20) - L'a' + 10; + } + return -1; +} + +/* + * Scan the subject sequence at nptr (C23 7.8.2.4 steps 1-7, the wide + * mirror of __strtox_scan). On WCSTOX_OK: *mag holds the magnitude (exact + * unless *over), *neg the sign, *endptr the position past the last digit. + * On WCSTOX_EMPTY / WCSTOX_BADBASE: *endptr is set to nptr, *mag and *neg + * are zeroed; the caller sets errno = EINVAL for WCSTOX_BADBASE. + */ +static int +wcstox_scan(const wchar_t *nptr, wchar_t **endptr, int base, int *neg, unsigned long long *mag, + int *over) +{ + const wchar_t *s = nptr; + int sg = 0; + int ov = 0; + int digits = 0; + int consumed_prefix = 0; + unsigned long long m = 0; + + while (wcstox_isspace(*s)) + { + s++; + } + if (*s == L'+' || *s == L'-') + { + sg = (*s == L'-'); + s++; + } + if (base != 0 && (base < 2 || base > 36)) + { + if (endptr) + { + *endptr = (wchar_t *)nptr; + } + *neg = 0; + *mag = 0; + *over = 0; + return WCSTOX_BADBASE; + } + + if (base == 0 || base == 16) + { + if (s[0] == L'0' && (s[1] == L'x' || s[1] == L'X')) + { + int d2 = wcstox_digit(s[2]); + + if (d2 >= 0 && d2 < 16) + { + base = 16; + s += 2; + } + else if (base == 0) + { + /* "0x" with no hex digit is no integer-constant form. */ + if (endptr) + { + *endptr = (wchar_t *)nptr; + } + *neg = 0; + *mag = 0; + *over = 0; + return WCSTOX_EMPTY; + } + /* base 16: the 0 itself is the subject; the x ends it. */ + } + else if (base == 0 && s[0] == L'0') + { + /* Octal prefix: the 0 opens the subject even when no octal + * digit follows ("0" is itself a valid integer constant). */ + base = 8; + consumed_prefix = 1; + s++; + } + else if (base == 0) + { + base = 10; + } + } + + for (;;) + { + int d = wcstox_digit(*s); + + if (d < 0 || d >= base) + { + break; + } + if (ov == 0) + { + unsigned long long lim = + (ULLONG_MAX - (unsigned long long)d) / (unsigned long long)base; + + if (m > lim) + { + ov = 1; + } + else + { + m = (m * (unsigned long long)base) + (unsigned long long)d; + } + } + digits++; + s++; + } + + if (digits == 0 && consumed_prefix == 0) + { + if (endptr) + { + *endptr = (wchar_t *)nptr; + } + *neg = 0; + *mag = 0; + *over = 0; + return WCSTOX_EMPTY; + } + if (endptr) + { + *endptr = (wchar_t *)s; + } + *neg = sg; + *mag = m; + *over = ov; + return WCSTOX_OK; +} + +intmax_t +strtoimax(const char *restrict nptr, char **restrict endptr, int base) +{ + /* ABI identity: intmax_t is long on x86_64 LP64. */ + return strtol(nptr, endptr, base); +} + +uintmax_t +strtoumax(const char *restrict nptr, char **restrict endptr, int base) +{ + /* ABI identity: uintmax_t is unsigned long on x86_64 LP64. */ + return strtoul(nptr, endptr, base); +} + +intmax_t +wcstoimax(const wchar_t *restrict nptr, wchar_t **restrict endptr, int base) +{ + int neg; + int over; + unsigned long long mag; + int rc = wcstox_scan(nptr, endptr, base, &neg, &mag, &over); + + if (rc != WCSTOX_OK) + { + if (rc == WCSTOX_BADBASE) + { + errno = EINVAL; + } + return 0; + } + if (over != 0 || mag > (unsigned long long)INTMAX_MAX + (unsigned long long)(neg != 0)) + { + errno = ERANGE; + return neg != 0 ? INTMAX_MIN : INTMAX_MAX; + } + return neg != 0 ? (intmax_t)(0ULL - mag) : (intmax_t)mag; +} + +uintmax_t +wcstoumax(const wchar_t *restrict nptr, wchar_t **restrict endptr, int base) +{ + int neg; + int over; + unsigned long long mag; + int rc = wcstox_scan(nptr, endptr, base, &neg, &mag, &over); + + if (rc != WCSTOX_OK) + { + if (rc == WCSTOX_BADBASE) + { + errno = EINVAL; + } + return 0; + } + if (over != 0 || mag > UINTMAX_MAX) + { + errno = ERANGE; + return UINTMAX_MAX; + } + return neg != 0 ? (uintmax_t)(0ULL - mag) : (uintmax_t)mag; +} + +/* + * imaxabs: INTMAX_MIN is not representable as a positive value; the + * unsigned computation wraps it back to INTMAX_MIN (implementation-defined + * by the uintmax_t -> intmax_t conversion, no errno), matching GCC's + * two's complement semantics. The const attribute lives in . + */ +intmax_t +imaxabs(intmax_t n) +{ + return n >= 0 ? n : (intmax_t)((uintmax_t)0 - (uintmax_t)n); +} + +imaxdiv_t +imaxdiv(intmax_t numer, intmax_t denom) +{ + imaxdiv_t r; + + r.quot = numer / denom; + r.rem = numer % denom; + return r; +} diff --git a/tests/test_inttypes.c b/tests/test_inttypes.c new file mode 100644 index 0000000..17520ac --- /dev/null +++ b/tests/test_inttypes.c @@ -0,0 +1,398 @@ +/* + * vlibc — inttypes.h test (todo 14). + * + * Covers three surfaces, all without printf (which does not exist yet — + * todo 16 owns it): + * + * 1. PRI/SCN macro literals: each macro must expand to a string literal + * with the complete conversion specifier for the x86_64 LP64 target + * (long = 64-bit), compared with vlibc's own strcmp, never through a + * host printf. Design rule under test: the fprintf macros carry the + * PROMOTED argument's modifier (PRId8 == "d", PRId64 == "ld") while + * the fscanf macros carry the exact-width pointer modifier (SCNd8 == + * "hhd", SCNd64 == "ld"). + * 2. strtoimax/strtoumax: thin ABI-identical delegations to strtol/ + * strtoul (intmax_t is long on LP64), including the C23 7.8.2.3 + * unsigned-wrap contract (strtoumax("-1") == UINTMAX_MAX with no + * range error) and imaxabs/imaxdiv. + * 3. wcstoimax/wcstoumax: the self-contained wide scan, verified + * value + endptr (wchar_t *) against the narrow-engine semantics. + * + * The `-f` failure mode runs the errno-writing scenarios (overflow clamps, + * EINVAL bases). This standalone test is host-linked, so vlibc's errno + * macro would write glibc's TCB slot (%fs:0+8) — errno is never read + * back; the C23 return-value clamps (INTMAX_MAX/INTMAX_MIN/UINTMAX_MAX) + * prove the ERANGE behavior, EINVAL is proven by the zero return with + * endptr == nptr, and -f exits through a raw SYS_exit_group to skip host + * cleanup. imaxabs(INTMAX_MIN) is deliberately untested: the wrap is + * implementation-defined. + * + * No host headers: under -Iinclude the vlibc headers shadow GCC's + * internal ones, so all output goes through raw SYS_write. Not part of the + * library proper; compiled manually for this todo (make check wiring is + * owned by a later todo). + */ + +#include + +#include "../include/inttypes.h" + +#include "../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); +} + +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +static void +say_dec(int fd, unsigned long v) +{ + 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)); +} + +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++; + } +} + +static void +check_ptr(const char *got, const char *want, const char *what) +{ + if (got == want) + { + say(1, "PASS: "); + say(1, what); + say(1, "\n"); + } + else + { + say(2, "FAIL: "); + say(2, what); + say(2, " (endptr off by "); + say_dec(2, (unsigned long)(got - want)); + say(2, ")\n"); + failures++; + } +} +// NOLINTEND(bugprone-easily-swappable-parameters) + +/* noipa: keep the comparison a real strcmp call so GCC cannot fold the + * literal contents away at -O2 (test_malloc.c proxy precedent). */ +static __attribute__((noipa)) int +str_eq(const char *a, const char *b) +{ + return strcmp(a, b) == 0; +} + +/* 1. PRI/SCN macro literals (acceptance: a macro expands to a literal, + * not via printf). */ +static void +macro_literals(void) +{ + const char *s; + + s = PRIu64; + check(str_eq(s, "lu"), "PRIu64 == \"lu\" (LP64 ABI)"); + s = PRId64; + check(str_eq(s, "ld"), "PRId64 == \"ld\""); + s = PRIi64; + check(str_eq(s, "li"), "PRIi64 == \"li\""); + s = PRIx64; + check(str_eq(s, "lx"), "PRIx64 == \"lx\""); + s = PRIX64; + check(str_eq(s, "lX"), "PRIX64 == \"lX\""); + s = PRIuMAX; + check(str_eq(s, "lu"), "PRIuMAX == \"lu\" (intmax_t is long)"); + s = PRIdMAX; + check(str_eq(s, "ld"), "PRIdMAX == \"ld\""); + s = PRIdPTR; + check(str_eq(s, "ld"), "PRIdPTR == \"ld\""); + s = PRIxPTR; + check(str_eq(s, "lx"), "PRIxPTR == \"lx\""); + s = SCNu64; + check(str_eq(s, "lu"), "SCNu64 == \"lu\""); + s = SCNd64; + check(str_eq(s, "ld"), "SCNd64 == \"ld\""); + s = SCNx64; + check(str_eq(s, "lx"), "SCNx64 == \"lx\""); + + /* Sub-int widths: PRI uses the promoted argument's modifier, SCN the + * exact-width pointer modifier. */ + s = PRId8; + check(str_eq(s, "d"), "PRId8 == \"d\" (int8_t promotes to int)"); + s = SCNd8; + check(str_eq(s, "hhd"), "SCNd8 == \"hhd\" (pointer to int8_t)"); + s = PRId16; + check(str_eq(s, "d"), "PRId16 == \"d\" (int16_t promotes to int)"); + s = SCNd16; + check(str_eq(s, "hd"), "SCNd16 == \"hd\""); + s = SCNuLEAST8; + check(str_eq(s, "hhu"), "SCNuLEAST8 == \"hhu\""); + s = PRIdLEAST64; + check(str_eq(s, "ld"), "PRIdLEAST64 == \"ld\""); + + /* Fastest types: int_fast8_t is signed char, int_fast16/32/64_t are + * long on x86_64. */ + s = PRIdFAST8; + check(str_eq(s, "d"), "PRIdFAST8 == \"d\" (signed char promotes)"); + s = PRIdFAST16; + check(str_eq(s, "ld"), "PRIdFAST16 == \"ld\" (long)"); + s = SCNdFAST8; + check(str_eq(s, "hhd"), "SCNdFAST8 == \"hhd\""); + s = SCNdFAST16; + check(str_eq(s, "ld"), "SCNdFAST16 == \"ld\""); +} + +/* 2a. strtoimax/strtoumax happy paths (no errno writes). */ +static void +strtoimax_happy(void) +{ + const char *s = " -42"; + char *e; + intmax_t v = strtoimax(s, &e, 0); + + check(v == -42, "strtoimax(\" -42\",0) value"); + check_ptr(e, s + 5, "strtoimax(\" -42\",0) endptr past digits"); + + s = "42xyz"; + v = strtoimax(s, &e, 0); + check(v == 42, "strtoimax(\"42xyz\") value"); + check_ptr(e, s + 2, "strtoimax(\"42xyz\") endptr at 'x'"); + + s = "0x1f"; + check(strtoimax(s, &e, 0) == 31 && e == s + 4, "strtoimax(\"0x1f\",0)==31"); + + s = "18446744073709551615"; + check(strtoumax(s, &e, 10) == UINTMAX_MAX && e == s + 20, + "strtoumax(UINTMAX_MAX digits) exact, no range error"); + s = " 42"; + check(strtoumax(s, &e, 10) == 42 && e == s + 4, "strtoumax(\" 42\")==42"); + + /* C23 7.8.2.3: a minus sign negates in the return type modulo 2^64 and + * is never a range error by itself (errno stays 0 — proven by value + * only, never by reading errno in this host-linked test). */ + s = "-1"; + check(strtoumax(s, &e, 10) == UINTMAX_MAX, "strtoumax(\"-1\")==UINTMAX_MAX (wrap, no errno)"); + s = "-4294967296"; + check(strtoumax(s, &e, 10) == UINTMAX_MAX - 4294967295UL, "strtoumax(-2^32)==2^64-2^32"); +} + +/* 2b. imaxabs/imaxdiv. imaxabs(INTMAX_MIN) is deliberately skipped: the + * wrap is implementation-defined and UB-adjacent. */ +static void +imax_abs_div(void) +{ + check(imaxabs(-42) == 42 && imaxabs(42) == 42 && imaxabs(0) == 0, "imaxabs basics"); + check(imaxabs(INTMAX_MAX) == INTMAX_MAX, "imaxabs(INTMAX_MAX)"); + + { + imaxdiv_t r = imaxdiv(7, 3); + + check(r.quot == 2 && r.rem == 1, "imaxdiv(7,3) == {2,1}"); + } + { + imaxdiv_t r = imaxdiv(-7, 3); + + check(r.quot == -2 && r.rem == -1, "imaxdiv(-7,3) == {-2,-1} (trunc toward zero)"); + } + { + imaxdiv_t r = imaxdiv(7, -3); + + check(r.quot == -2 && r.rem == 1, "imaxdiv(7,-3) == {-2,1}"); + } +} + +/* 3. wcstoimax/wcstoumax: wide scan mirrors the narrow engine. */ +static void +wcsto_happy(void) +{ + const wchar_t *ws = L" -42"; + wchar_t *e; + intmax_t v = wcstoimax(ws, &e, 0); + + check(v == -42, "wcstoimax(L\" -42\",0) value"); + check(e == ws + 5, "wcstoimax(L\" -42\",0) endptr past digits"); + + ws = L"4x"; + v = wcstoimax(ws, &e, 10); + check(v == 4, "wcstoimax(L\"4x\") value"); + check(e == ws + 1, "wcstoimax(L\"4x\") endptr past L'4'"); + + ws = L"42abc"; + v = wcstoimax(ws, &e, 0); + check(v == 42, "wcstoimax(L\"42abc\") value"); + check(e == ws + 2, "wcstoimax(L\"42abc\") endptr at 'a'"); + + ws = L"-0x10"; + check(wcstoimax(ws, &e, 0) == -16 && e == ws + 5, "wcstoimax(L\"-0x10\",0)==-16"); + + ws = L"18446744073709551615"; + check(wcstoumax(ws, &e, 10) == UINTMAX_MAX && e == ws + 20, + "wcstoumax(UINTMAX_MAX digits) exact, no range error"); + + ws = L"-1"; + check(wcstoumax(ws, &e, 10) == UINTMAX_MAX, "wcstoumax(L\"-1\")==UINTMAX_MAX (wrap)"); + + ws = L"\t\n\v\f\r +42x"; + check(wcstoumax(ws, &e, 10) == 42 && e == ws + 9, "wcstoumax wide whitespace+sign"); + + ws = L" "; + check(wcstoimax(ws, &e, 10) == 0 && e == ws, "wcstoimax(whitespace only) endptr at start"); + + ws = L"0x"; + check(wcstoimax(ws, &e, 0) == 0 && e == ws, "wcstoimax(L\"0x\",0): no subject"); +} + +/* + * Failure scenarios (-f): every path that writes errno inside the library. + * errno is never read back (host TCB hazard) — the C23 return-value clamps + * prove ERANGE, endptr == nptr with a zero return proves EINVAL — and the + * process exits through a raw SYS_exit_group. + */ +static int +failure_scenarios(void) +{ + const char *s; + char *e; + int bad = 0; + + s = "18446744073709551616"; + if (strtoumax(s, &e, 0) != UINTMAX_MAX || e != s + 20) + { + bad++; + say(2, "FAIL: strtoumax overflow clamp\n"); + } + s = "9223372036854775808"; + if (strtoimax(s, &e, 10) != INTMAX_MAX || e != s + 19) + { + bad++; + say(2, "FAIL: strtoimax overflow clamp\n"); + } + s = "-9223372036854775809"; + if (strtoimax(s, &e, 10) != INTMAX_MIN || e != s + 20) + { + bad++; + say(2, "FAIL: strtoimax negative overflow clamp\n"); + } + s = "-18446744073709551616"; + if (strtoumax(s, &e, 10) != UINTMAX_MAX || e != s + 21) + { + bad++; + say(2, "FAIL: strtoumax magnitude 2^64 overflow clamp\n"); + } + s = "123"; + if (strtoimax(s, &e, 1) != 0 || e != s) + { + bad++; + say(2, "FAIL: strtoimax base 1 (EINVAL)\n"); + } + s = "123"; + if (strtoumax(s, &e, 37) != 0 || e != s) + { + bad++; + say(2, "FAIL: strtoumax base 37 (EINVAL)\n"); + } + s = "123"; + if (strtoumax(s, &e, -1) != 0 || e != s) + { + bad++; + say(2, "FAIL: strtoumax base -1 (EINVAL)\n"); + } + + { + const wchar_t *ws = L"18446744073709551616"; + wchar_t *we; + + if (wcstoumax(ws, &we, 0) != UINTMAX_MAX || we != ws + 20) + { + bad++; + say(2, "FAIL: wcstoumax overflow clamp\n"); + } + ws = L"9223372036854775808"; + if (wcstoimax(ws, &we, 10) != INTMAX_MAX || we != ws + 19) + { + bad++; + say(2, "FAIL: wcstoimax overflow clamp\n"); + } + ws = L"-9223372036854775809"; + if (wcstoimax(ws, &we, 10) != INTMAX_MIN || we != ws + 20) + { + bad++; + say(2, "FAIL: wcstoimax negative overflow clamp\n"); + } + ws = L"123"; + if (wcstoimax(ws, &we, 37) != 0 || we != ws) + { + bad++; + say(2, "FAIL: wcstoimax base 37 (EINVAL)\n"); + } + } + + if (bad == 0) + { + say(1, "PASS: all overflow/EINVAL failure scenarios\n"); + } + return bad > 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 */ + } + + macro_literals(); + strtoimax_happy(); + imax_abs_div(); + wcsto_happy(); + + if (failures > 0) + { + say(2, "FAILED ("); + say_dec(2, (unsigned long)failures); + say(2, " check(s))\n"); + return 1; + } + say(1, "all inttypes tests passed\n"); + return 0; +}