feat(inttypes): PRI/SCN macros + strtoimax family
This commit is contained in:
@@ -0,0 +1,314 @@
|
||||
#ifndef VLIBC_INTTYPES_H
|
||||
#define VLIBC_INTTYPES_H
|
||||
|
||||
/*
|
||||
* vlibc — <inttypes.h>.
|
||||
*
|
||||
* 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 <stdint.h> 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 <stddef.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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 */
|
||||
@@ -0,0 +1,268 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/*
|
||||
* 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 <inttypes.h>.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
@@ -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 <stdint.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user