feat(stdlib): numeric string conversions
This commit is contained in:
@@ -105,6 +105,102 @@ __attribute__((pure)) size_t
|
||||
malloc_usable_size(void *ptr);
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
|
||||
/* numeric conversions (todo 11) */
|
||||
|
||||
/*
|
||||
* String-to-number conversions (C23 7.24.1). All are ISO C core / POSIX
|
||||
* base and present in every profile.
|
||||
*
|
||||
* The ato* wrappers carry no error reporting: their behavior is undefined
|
||||
* if the converted value cannot be represented (C23 7.24.1.1-2), so they
|
||||
* read their input and nothing else — hence pure.
|
||||
*
|
||||
* The strto* functions report range errors through errno and store the
|
||||
* scan position through endptr; both are real side effects, so they carry
|
||||
* no intent attribute. errno behavior follows C23 7.24.1.4-5: ERANGE when
|
||||
* the subject sequence is outside the representable range (the clamped
|
||||
* maximum/minimum is returned); EINVAL when base is not 0 and not in
|
||||
* [2, 36] (endptr left at nptr, 0 returned); endptr points at nptr when
|
||||
* no subject sequence is present.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Convert the initial decimal digits of nptr to int, discarding leading
|
||||
* whitespace and an optional sign. Equivalent to (int)strtol(nptr, 0, 10).
|
||||
* pure: reads memory, no side effects.
|
||||
*/
|
||||
__attribute__((pure)) int
|
||||
atoi(const char *nptr);
|
||||
|
||||
/*
|
||||
* As atoi, converted to long: strtol(nptr, 0, 10).
|
||||
* pure: reads memory, no side effects.
|
||||
*/
|
||||
__attribute__((pure)) long
|
||||
atol(const char *nptr);
|
||||
|
||||
/*
|
||||
* As atoi, converted to long long: strtoll(nptr, 0, 10).
|
||||
* pure: reads memory, no side effects.
|
||||
*/
|
||||
__attribute__((pure)) long long
|
||||
atoll(const char *nptr);
|
||||
|
||||
/*
|
||||
* As atoi, converted to double: strtod(nptr, 0).
|
||||
* pure: reads memory, no side effects.
|
||||
*/
|
||||
__attribute__((pure)) double
|
||||
atof(const char *nptr);
|
||||
|
||||
/*
|
||||
* Convert the initial portion of nptr to long, stopping at the first
|
||||
* character that is not part of the subject sequence and storing its
|
||||
* position in *endptr (if endptr is not NULL). See the family comment for
|
||||
* base, errno, and endptr semantics.
|
||||
*/
|
||||
long
|
||||
strtol(const char *restrict nptr, char **restrict endptr, int base);
|
||||
|
||||
/*
|
||||
* As strtol, converted to unsigned long. A subject sequence with a minus
|
||||
* sign yields the negated value computed in the return type (modulo
|
||||
* ULONG_MAX + 1) without a range error (C23 7.24.1.4p8).
|
||||
*/
|
||||
unsigned long
|
||||
strtoul(const char *restrict nptr, char **restrict endptr, int base);
|
||||
|
||||
/*
|
||||
* As strtol, converted to long long.
|
||||
*/
|
||||
long long
|
||||
strtoll(const char *restrict nptr, char **restrict endptr, int base);
|
||||
|
||||
/*
|
||||
* As strtol, converted to unsigned long long. Negative subject sequences
|
||||
* wrap modulo ULLONG_MAX + 1 as for strtoul.
|
||||
*/
|
||||
unsigned long long
|
||||
strtoull(const char *restrict nptr, char **restrict endptr, int base);
|
||||
|
||||
/*
|
||||
* Convert the initial portion of nptr to float, double, or long double
|
||||
* (C23 7.24.1.3): optional whitespace, optional sign, then either an
|
||||
* "inf"/"infinity" or "nan"/"nan(n-char-sequence)" subject (case-
|
||||
* insensitive), a hexadecimal floating subject ("0x1.8p1"), or a decimal
|
||||
* floating subject with optional exponent. Overflow returns ±HUGE_VAL*
|
||||
* with errno ERANGE; results too small to represent return a subnormal or
|
||||
* zero value with errno ERANGE. no-conversion stores nptr in *endptr.
|
||||
*/
|
||||
double
|
||||
strtod(const char *restrict nptr, char **restrict endptr);
|
||||
|
||||
float
|
||||
strtof(const char *restrict nptr, char **restrict endptr);
|
||||
|
||||
long double
|
||||
strtold(const char *restrict nptr, char **restrict endptr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
#ifndef VLIBC_INTERNAL_STRTOX_H
|
||||
#define VLIBC_INTERNAL_STRTOX_H
|
||||
|
||||
/*
|
||||
* vlibc — internal numeric-scan engine (todo 11).
|
||||
*
|
||||
* This header declares the shared core of the strtol/strtoul family. The
|
||||
* engine performs C23 7.24.1.4 steps 1-7 — whitespace skip, optional sign,
|
||||
* base resolution (0/2..36 with the 0x/0 prefix rules), and saturating
|
||||
* digit accumulation — and reports the subject sequence's magnitude, sign,
|
||||
* and overflow status. The public wrappers in src/stdlib/strtox_impl.c own
|
||||
* the per-type clamping and errno policy (signed clamps at LONG_MIN/LONG_MAX
|
||||
* with ERANGE; unsigned negates in the return type modulo 2^width, with
|
||||
* ERANGE only when the magnitude itself overflows the type, C23 7.24.1.4p8).
|
||||
*
|
||||
* Todo 14 (strtoimax/strtoumax, which on x86_64 are long/unsigned long)
|
||||
* consumes this header and reuses the engine unchanged; the wcsto* family
|
||||
* only needs a byte-level shim before it.
|
||||
*
|
||||
* The helpers below are static inline so the engine and every wrapper share
|
||||
* one C-locale whitespace/digit definition without a call boundary.
|
||||
*/
|
||||
|
||||
#include "libc.h"
|
||||
|
||||
/* Scan results. */
|
||||
#define STRTOX_OK 0 /* subject sequence present (digits > 0) */
|
||||
#define STRTOX_EMPTY 1 /* no subject sequence; *endptr = nptr */
|
||||
#define STRTOX_BADBASE 2 /* base not in {0} union [2, 36]; *endptr = nptr */
|
||||
|
||||
/*
|
||||
* Scan the subject sequence at nptr (C23 7.24.1.4 steps 1-7).
|
||||
*
|
||||
* On STRTOX_OK: *mag holds the magnitude (exact unless *over), *neg the
|
||||
* sign, *endptr the position past the last digit, *over whether the
|
||||
* magnitude exceeded unsigned long long (callers must clamp + ERANGE).
|
||||
* On STRTOX_EMPTY / STRTOX_BADBASE: *endptr is set to nptr, *mag and *neg
|
||||
* are zeroed; the caller sets errno = EINVAL for STRTOX_BADBASE and
|
||||
* returns 0.
|
||||
*/
|
||||
// NOLINTBEGIN(bugprone-reserved-identifier)
|
||||
hidden int
|
||||
__strtox_scan(const char *nptr, char **endptr, int base, int *neg, unsigned long long *mag,
|
||||
int *over);
|
||||
// NOLINTEND(bugprone-reserved-identifier)
|
||||
|
||||
/* C-locale whitespace: exactly ' ' and '\t'..'\r'. */
|
||||
static inline int
|
||||
strtox_isspace(int c)
|
||||
{
|
||||
return c == ' ' || (unsigned int)c - '\t' < 5U;
|
||||
}
|
||||
|
||||
/*
|
||||
* Value of c as a base-36 digit, or -1 when c is not an ASCII letter/digit.
|
||||
* c | 0x20 folds 'A'..'F' to 'a'..'f'; the unsigned cast keeps negative
|
||||
* values out of the range checks (same arithmetic-range idiom as ctype.c).
|
||||
*/
|
||||
static inline int
|
||||
strtox_digit(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;
|
||||
}
|
||||
|
||||
#endif /* VLIBC_INTERNAL_STRTOX_H */
|
||||
+1028
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,149 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include "../internal/strtox.h"
|
||||
|
||||
/*
|
||||
* Shared integer subject-sequence scan (todo 11). See src/internal/strtox.h
|
||||
* for the contract; this file is the engine the strtol/strtoul wrappers and
|
||||
* todo 14's strtoimax/strtoumax build on.
|
||||
*
|
||||
* Prefix rules (C23 7.24.1.4p3-4):
|
||||
* - base 0: "0x"/"0X" followed by a hex digit selects base 16 and the
|
||||
* prefix is consumed; a bare leading 0 selects base 8 and the 0 is the
|
||||
* subject itself ("0" is a valid integer constant, so the subject is
|
||||
* never empty after an octal prefix); anything else is base 10.
|
||||
* - base 16: "0x"/"0X" followed by a hex digit is consumed; without a
|
||||
* hex digit the "0" is itself a valid hex digit and the x terminates
|
||||
* the subject.
|
||||
* - "0x" without a hex digit under base 0 is no integer-constant form,
|
||||
* so there is no subject sequence at all.
|
||||
*
|
||||
* Endptr always lands at the first character that is not part of the
|
||||
* subject sequence, or at nptr when there is no subject (C23 7.24.1.4p5,
|
||||
* even when whitespace or a sign was consumed).
|
||||
*/
|
||||
|
||||
int
|
||||
__strtox_scan(const char *nptr, char **endptr, int base, int *neg, unsigned long long *mag,
|
||||
int *over)
|
||||
{
|
||||
const char *s = nptr;
|
||||
int sg = 0;
|
||||
int ov = 0;
|
||||
int digits = 0;
|
||||
int consumed_prefix = 0;
|
||||
unsigned long long m = 0;
|
||||
|
||||
while (strtox_isspace(*s))
|
||||
{
|
||||
s++;
|
||||
}
|
||||
if (*s == '+' || *s == '-')
|
||||
{
|
||||
sg = (*s == '-');
|
||||
s++;
|
||||
}
|
||||
if (base != 0 && (base < 2 || base > 36))
|
||||
{
|
||||
if (endptr)
|
||||
{
|
||||
*endptr = (char *)nptr;
|
||||
}
|
||||
*neg = 0;
|
||||
*mag = 0;
|
||||
*over = 0;
|
||||
return STRTOX_BADBASE;
|
||||
}
|
||||
|
||||
if (base == 0 || base == 16)
|
||||
{
|
||||
if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
|
||||
{
|
||||
int d2 = strtox_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 = (char *)nptr;
|
||||
}
|
||||
*neg = 0;
|
||||
*mag = 0;
|
||||
*over = 0;
|
||||
return STRTOX_EMPTY;
|
||||
}
|
||||
/* base 16: the 0 itself is the subject; the x ends it. */
|
||||
}
|
||||
else if (base == 0 && s[0] == '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)
|
||||
{
|
||||
/* No prefix: a decimal subject (base 0 auto-detection). */
|
||||
base = 10;
|
||||
}
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
int d = strtox_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 = (char *)nptr;
|
||||
}
|
||||
*neg = 0;
|
||||
*mag = 0;
|
||||
*over = 0;
|
||||
return STRTOX_EMPTY;
|
||||
}
|
||||
if (endptr)
|
||||
{
|
||||
*endptr = (char *)s;
|
||||
}
|
||||
*neg = sg;
|
||||
*mag = m;
|
||||
*over = ov;
|
||||
return STRTOX_OK;
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "../internal/strtox.h"
|
||||
|
||||
/*
|
||||
* Public integer wrappers over the __strtox_scan engine (todo 11).
|
||||
*
|
||||
* Clamping policy (C23 7.24.1.4p7-8):
|
||||
* - signed: a magnitude above the type maximum (with the sign folded in:
|
||||
* -2^(N-1) is representable, -(2^(N-1)+1) is not) clamps to LONG_MAX /
|
||||
* LONG_MIN with errno ERANGE;
|
||||
* - unsigned: a magnitude above the type maximum clamps to ULONG_MAX
|
||||
* with errno ERANGE; a minus sign negates the value in the return type
|
||||
* (modulo 2^N) and is never a range error by itself.
|
||||
* - base outside {0} union [2, 36]: errno EINVAL, return 0, *endptr set
|
||||
* to nptr (POSIX.1-2008).
|
||||
*/
|
||||
|
||||
long
|
||||
strtol(const char *restrict nptr, char **restrict endptr, int base)
|
||||
{
|
||||
int neg;
|
||||
int over;
|
||||
unsigned long long mag;
|
||||
int rc = __strtox_scan(nptr, endptr, base, &neg, &mag, &over);
|
||||
|
||||
if (rc != STRTOX_OK)
|
||||
{
|
||||
if (rc == STRTOX_BADBASE)
|
||||
{
|
||||
errno = EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (over != 0 || mag > (unsigned long long)LONG_MAX + (unsigned long long)(neg != 0))
|
||||
{
|
||||
errno = ERANGE;
|
||||
return neg != 0 ? LONG_MIN : LONG_MAX;
|
||||
}
|
||||
return neg != 0 ? (long)(0ULL - mag) : (long)mag;
|
||||
}
|
||||
|
||||
unsigned long
|
||||
strtoul(const char *restrict nptr, char **restrict endptr, int base)
|
||||
{
|
||||
int neg;
|
||||
int over;
|
||||
unsigned long long mag;
|
||||
int rc = __strtox_scan(nptr, endptr, base, &neg, &mag, &over);
|
||||
|
||||
if (rc != STRTOX_OK)
|
||||
{
|
||||
if (rc == STRTOX_BADBASE)
|
||||
{
|
||||
errno = EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (over != 0 || mag > (unsigned long long)ULONG_MAX)
|
||||
{
|
||||
errno = ERANGE;
|
||||
return ULONG_MAX;
|
||||
}
|
||||
return neg != 0 ? (unsigned long)(0ULL - mag) : (unsigned long)mag;
|
||||
}
|
||||
|
||||
long long
|
||||
strtoll(const char *restrict nptr, char **restrict endptr, int base)
|
||||
{
|
||||
int neg;
|
||||
int over;
|
||||
unsigned long long mag;
|
||||
int rc = __strtox_scan(nptr, endptr, base, &neg, &mag, &over);
|
||||
|
||||
if (rc != STRTOX_OK)
|
||||
{
|
||||
if (rc == STRTOX_BADBASE)
|
||||
{
|
||||
errno = EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (over != 0 || mag > (unsigned long long)LLONG_MAX + (unsigned long long)(neg != 0))
|
||||
{
|
||||
errno = ERANGE;
|
||||
return neg != 0 ? LLONG_MIN : LLONG_MAX;
|
||||
}
|
||||
return neg != 0 ? (long long)(0ULL - mag) : (long long)mag;
|
||||
}
|
||||
|
||||
unsigned long long
|
||||
strtoull(const char *restrict nptr, char **restrict endptr, int base)
|
||||
{
|
||||
int neg;
|
||||
int over;
|
||||
unsigned long long mag;
|
||||
int rc = __strtox_scan(nptr, endptr, base, &neg, &mag, &over);
|
||||
|
||||
if (rc != STRTOX_OK)
|
||||
{
|
||||
if (rc == STRTOX_BADBASE)
|
||||
{
|
||||
errno = EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (over != 0 || mag > ULLONG_MAX)
|
||||
{
|
||||
errno = ERANGE;
|
||||
return ULLONG_MAX;
|
||||
}
|
||||
return neg != 0 ? 0ULL - mag : mag;
|
||||
}
|
||||
|
||||
/*
|
||||
* atoi/atol/atoll: strto* with no endptr and base 10 (C23 7.24.1.2). The
|
||||
* result truncation is that of the target type; out-of-range input is
|
||||
* undefined behavior, so no additional checking is performed.
|
||||
*/
|
||||
int
|
||||
atoi(const char *nptr)
|
||||
{
|
||||
return (int)strtol(nptr, NULL, 10);
|
||||
}
|
||||
|
||||
long
|
||||
atol(const char *nptr)
|
||||
{
|
||||
return strtol(nptr, NULL, 10);
|
||||
}
|
||||
|
||||
long long
|
||||
atoll(const char *nptr)
|
||||
{
|
||||
return strtoll(nptr, NULL, 10);
|
||||
}
|
||||
@@ -0,0 +1,547 @@
|
||||
/*
|
||||
* vlibc — numeric string conversions test (todo 11).
|
||||
*
|
||||
* Exercises atoi/atol/atoll/atof, strtol/strtoul/strtoll/strtoull, and
|
||||
* strtof/strtod/strtold against glibc-verified golden expectations (the
|
||||
* corpus was diffed value+endptr+errno against glibc during development;
|
||||
* the literals below are that diff's results):
|
||||
*
|
||||
* 1. base resolution: 0 (decimal/octal/0x-hex auto-detect), 2, 8, 10,
|
||||
* 16, including the "0" / "0x" prefix edge cases ("0" base 0 is an
|
||||
* octal subject of value 0 with the 0 consumed; "0x" base 0 with no
|
||||
* hex digit has no subject at all; base 16 keeps "0" and stops at x);
|
||||
* 2. leading C-locale whitespace and sign handling;
|
||||
* 3. endptr correctness: past the last digit, at the first invalid
|
||||
* character, or at nptr when no subject sequence exists;
|
||||
* 4. exact boundaries without range errors: LONG_MAX / LONG_MIN /
|
||||
* ULONG_MAX / ULLONG_MAX / DBL_MAX;
|
||||
* 5. strtoul negative wrap (value-only, modulo 2^64, C23 7.24.1.4p8);
|
||||
* 6. strtod: "inf"/"infinity"/"nan"/"nan(chars)" (case-insensitive),
|
||||
* hex floats ("0x1.8p1"), "0.1" exact bits, "3.14159";
|
||||
* 7. ato* wrappers.
|
||||
*
|
||||
* The `-f` failure mode runs the errno-writing scenarios (overflow clamps,
|
||||
* EINVAL bases, HUGE_VAL, underflow-to-zero, hex subnormal). Because this
|
||||
* standalone test is host-linked, vlibc's errno macro addresses glibc's
|
||||
* TCB slot (%fs:0+8) — writing it corrupts the host. errno behavior is
|
||||
* therefore verified only through the C23-mandated return-value clamps
|
||||
* (LONG_MAX/LONG_MIN/HUGE_VAL presence proves ERANGE happened; EINVAL is
|
||||
* proven by the zero return with endptr == nptr), never by reading errno,
|
||||
* and the -f mode exits via a raw SYS_exit_group to skip host cleanup.
|
||||
*
|
||||
* 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 (the make check wiring
|
||||
* is owned by a later todo).
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <float.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "../include/stdlib.h"
|
||||
|
||||
#include "../src/internal/syscall.h"
|
||||
|
||||
static int failures;
|
||||
|
||||
static void
|
||||
say(int fd, const char *s)
|
||||
{
|
||||
long n = 0;
|
||||
|
||||
while (s[n] != '\0')
|
||||
{
|
||||
n++;
|
||||
}
|
||||
__syscall3(SYS_write, fd, (long)s, n);
|
||||
}
|
||||
|
||||
static void
|
||||
say_dec(int fd, unsigned long v) // NOLINT(bugprone-easily-swappable-parameters)
|
||||
{
|
||||
char buf[24];
|
||||
int i = (int)sizeof(buf);
|
||||
|
||||
buf[--i] = '\0';
|
||||
do
|
||||
{
|
||||
buf[--i] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
} while (v != 0);
|
||||
__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
|
||||
}
|
||||
|
||||
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) // NOLINT(bugprone-easily-swappable-parameters)
|
||||
{
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
/* 1. Base resolution and prefix rules. */
|
||||
static void
|
||||
base_resolution(void)
|
||||
{
|
||||
const char *s;
|
||||
char *e;
|
||||
long v;
|
||||
|
||||
s = "0x1f";
|
||||
v = strtol(s, &e, 0);
|
||||
check(v == 31 && e == s + 4, "strtol(\"0x1f\",0)==31, endptr past digits");
|
||||
s = "0x1f";
|
||||
v = strtol(s, &e, 16);
|
||||
check(v == 31 && e == s + 4, "strtol(\"0x1f\",16)==31");
|
||||
s = "0x1F";
|
||||
check(strtol(s, &e, 0) == 31 && e == s + 4, "strtol(\"0x1F\",0)==31 (upper hex)");
|
||||
s = "101010";
|
||||
check(strtol(s, &e, 2) == 42 && e == s + 6, "strtol(\"101010\",2)==42");
|
||||
s = "777";
|
||||
check(strtol(s, &e, 8) == 511 && e == s + 3, "strtol(\"777\",8)==511");
|
||||
s = "42";
|
||||
check(strtol(s, &e, 10) == 42 && e == s + 2, "strtol(\"42\",10)==42");
|
||||
s = "ff";
|
||||
check(strtol(s, &e, 16) == 255 && e == s + 2, "strtol(\"ff\",16)==255");
|
||||
s = "ff";
|
||||
v = strtol(s, &e, 10);
|
||||
check(v == 0 && e == s, "strtol(\"ff\",10)==0, endptr at start (no digits)");
|
||||
s = "0";
|
||||
v = strtol(s, &e, 0);
|
||||
check(v == 0 && e == s + 1, "strtol(\"0\",0)==0, octal-prefix 0 consumed");
|
||||
s = "08";
|
||||
v = strtol(s, &e, 0);
|
||||
check(v == 0 && e == s + 1, "strtol(\"08\",0)==0, subject is the octal 0");
|
||||
s = "0x";
|
||||
v = strtol(s, &e, 0);
|
||||
check(v == 0 && e == s, "strtol(\"0x\",0)==0, no subject sequence at all");
|
||||
s = "0x";
|
||||
v = strtol(s, &e, 16);
|
||||
check(v == 0 && e == s + 1, "strtol(\"0x\",16)==0, the 0 is the subject");
|
||||
s = "0xg";
|
||||
v = strtol(s, &e, 0);
|
||||
check(v == 0 && e == s, "strtol(\"0xg\",0): no hex digit, no subject");
|
||||
s = "0b101";
|
||||
v = strtol(s, &e, 0);
|
||||
check(v == 0 && e == s + 1, "strtol(\"0b101\",0)==0, b is not octal");
|
||||
s = "010";
|
||||
check(strtol(s, &e, 0) == 8 && e == s + 3, "strtol(\"010\",0)==8 (octal)");
|
||||
s = "1234";
|
||||
check(strtol(s, &e, 36) == 49360 && e == s + 4, "strtol(\"1234\",36)==49360");
|
||||
}
|
||||
|
||||
/* 2. Whitespace and signs. */
|
||||
static void
|
||||
whitespace_signs(void)
|
||||
{
|
||||
const char *s = " -42";
|
||||
char *e;
|
||||
|
||||
check(strtol(s, &e, 0) == -42 && e == s + 5, "strtol(\" -42\",0)==-42");
|
||||
s = "\t\n\v\f\r+42x";
|
||||
check(strtol(s, &e, 10) == 42 && e == s + 8, "strtol(ws \"+42x\")==42, endptr at x");
|
||||
s = " ";
|
||||
check(strtol(s, &e, 10) == 0 && e == s, "strtol(whitespace only)==0, endptr at start");
|
||||
s = "+";
|
||||
check(strtol(s, &e, 10) == 0 && e == s, "strtol(\"+\")==0, endptr at start");
|
||||
s = "-";
|
||||
check(strtol(s, &e, 10) == 0 && e == s, "strtol(\"-\")==0, endptr at start");
|
||||
s = "";
|
||||
check(strtol(s, &e, 10) == 0 && e == s, "strtol(\"\")==0, endptr at start");
|
||||
s = "-0x10";
|
||||
check(strtol(s, &e, 0) == -16 && e == s + 5, "strtol(\"-0x10\",0)==-16");
|
||||
s = " 0x10";
|
||||
check(strtol(s, &e, 0) == 16 && e == s + 5, "strtol(\" 0x10\",0)==16");
|
||||
}
|
||||
|
||||
/* 3. Endptr lands at the first invalid character. */
|
||||
static void
|
||||
endptr_positions(void)
|
||||
{
|
||||
const char *s = "12abc";
|
||||
char *e;
|
||||
|
||||
check(strtol(s, &e, 10) == 12, "strtol(\"12abc\") value");
|
||||
check_ptr(e, s + 2, "strtol(\"12abc\") endptr at 'a'");
|
||||
s = "0x1fg";
|
||||
check(strtol(s, &e, 0) == 31, "strtol(\"0x1fg\") value");
|
||||
check_ptr(e, s + 4, "strtol(\"0x1fg\") endptr at 'g'");
|
||||
s = "3.5";
|
||||
check(strtol(s, &e, 10) == 3, "strtol(\"3.5\") value");
|
||||
check_ptr(e, s + 1, "strtol(\"3.5\") endptr at '.'");
|
||||
}
|
||||
|
||||
/* 4. Exact representable boundaries set no range error (proven by the
|
||||
* exact return values; errno itself is never read here). */
|
||||
static void
|
||||
exact_boundaries(void)
|
||||
{
|
||||
check(strtol("9223372036854775807", NULL, 10) == LONG_MAX, "strtol max == LONG_MAX");
|
||||
check(strtol("-9223372036854775808", NULL, 10) == LONG_MIN, "strtol min == LONG_MIN");
|
||||
check(strtoll("9223372036854775807", NULL, 10) == LLONG_MAX, "strtoll max == LLONG_MAX");
|
||||
check(strtoll("-9223372036854775808", NULL, 10) == LLONG_MIN, "strtoll min == LLONG_MIN");
|
||||
check(strtoul("18446744073709551615", NULL, 10) == ULONG_MAX, "strtoul max == ULONG_MAX");
|
||||
check(strtoull("18446744073709551615", NULL, 10) == ULLONG_MAX, "strtoull max == ULLONG_MAX");
|
||||
check(strtol("9223372036854775807z", NULL, 10) == LONG_MAX, "strtol max with tail");
|
||||
check(strtol("0x7fffffffffffffff", NULL, 0) == LONG_MAX, "strtol hex LONG_MAX");
|
||||
check(strtol("-0x8000000000000000", NULL, 0) == LONG_MIN, "strtol hex LONG_MIN");
|
||||
check(strtoul("0xffffffffffffffff", NULL, 0) == ULONG_MAX, "strtoul hex ULONG_MAX");
|
||||
}
|
||||
|
||||
/* 5. strtoul negative wrap: value-only negation modulo 2^64 (C23
|
||||
* 7.24.1.4p8) — never a range error, so this runs in default mode. */
|
||||
static void
|
||||
unsigned_negative_wrap(void)
|
||||
{
|
||||
check(strtoul("-1", NULL, 10) == ULONG_MAX, "strtoul(\"-1\") == ULONG_MAX (wrap)");
|
||||
check(strtoull("-1", NULL, 10) == ULLONG_MAX, "strtoull(\"-1\") == ULLONG_MAX (wrap)");
|
||||
check(strtoul("-4294967296", NULL, 10) == ULONG_MAX - 4294967295UL,
|
||||
"strtoul(-2^32) == 2^64-2^32");
|
||||
check(strtoul("-9223372036854775808", NULL, 10) == 9223372036854775808UL,
|
||||
"strtoul(-2^63) == 2^63 (no range error)");
|
||||
}
|
||||
|
||||
/* 6a. strtod special forms and hex floats. */
|
||||
static void
|
||||
strtod_special(void)
|
||||
{
|
||||
const char *s = "inf";
|
||||
char *e;
|
||||
double r;
|
||||
|
||||
r = strtod(s, &e);
|
||||
check(r == __builtin_inf() && e == s + 3, "strtod(\"inf\") == +infinity");
|
||||
s = "Infinity";
|
||||
r = strtod(s, &e);
|
||||
check(r == __builtin_inf() && e == s + 8, "strtod(\"Infinity\") == +infinity");
|
||||
s = "-INF";
|
||||
r = strtod(s, &e);
|
||||
check(r == -__builtin_inf() && e == s + 4, "strtod(\"-INF\") == -infinity");
|
||||
s = "infinityx";
|
||||
r = strtod(s, &e);
|
||||
check(r == __builtin_inf(), "strtod(\"infinityx\") value");
|
||||
check_ptr(e, s + 8, "strtod(\"infinityx\") endptr at 'x'");
|
||||
s = "nan";
|
||||
r = strtod(s, &e);
|
||||
check(r != r && e == s + 3, "strtod(\"nan\") is NaN, endptr past nan");
|
||||
s = "NAN(xyz)";
|
||||
r = strtod(s, &e);
|
||||
check(r != r, "strtod(\"NAN(xyz)\") is NaN");
|
||||
check_ptr(e, s + 8, "strtod(\"NAN(xyz)\") endptr past ')'");
|
||||
s = "nan(x";
|
||||
r = strtod(s, &e);
|
||||
check(r != r, "strtod(\"nan(x\") is NaN");
|
||||
check_ptr(e, s + 3, "strtod(\"nan(x\") endptr right after nan");
|
||||
s = "in";
|
||||
r = strtod(s, &e);
|
||||
check(r == 0.0 && e == s, "strtod(\"in\")==0, endptr at start");
|
||||
s = "0x1.8p1";
|
||||
r = strtod(s, &e);
|
||||
check(r == 3.0 && e == s + 7, "strtod(\"0x1.8p1\")==3.0");
|
||||
s = "0x1p-2";
|
||||
check(strtod(s, &e) == 0.25 && e == s + 6, "strtod(\"0x1p-2\")==0.25");
|
||||
s = "0x1.8p1f";
|
||||
r = strtod(s, &e);
|
||||
check(r == 3.0, "strtod(\"0x1.8p1f\") value");
|
||||
check_ptr(e, s + 7, "strtod(\"0x1.8p1f\") endptr at 'f'");
|
||||
s = "0x8.8";
|
||||
check(strtod(s, &e) == 8.5 && e == s + 5, "strtod(\"0x8.8\")==8.5");
|
||||
s = "0x.8p1";
|
||||
check(strtod(s, &e) == 1.0 && e == s + 6, "strtod(\"0x.8p1\")==1.0");
|
||||
s = "0x1.fffffffffffffp+1023";
|
||||
check(strtod(s, &e) == DBL_MAX && e == s + 23, "strtod(hex max normal) == DBL_MAX");
|
||||
s = "0x";
|
||||
r = strtod(s, &e);
|
||||
check(r == 0.0, "strtod(\"0x\") value");
|
||||
check_ptr(e, s + 1, "strtod(\"0x\") endptr after the 0 (glibc)");
|
||||
}
|
||||
|
||||
/* 6b. strtod decimal conversions. */
|
||||
static void
|
||||
strtod_decimal(void)
|
||||
{
|
||||
const char *s = "0.1";
|
||||
char *e;
|
||||
double r;
|
||||
|
||||
r = strtod(s, &e);
|
||||
check(r == 0x1.999999999999ap-4 && e == s + 3, "strtod(\"0.1\") has exact 0.1 bits");
|
||||
s = "3.14159";
|
||||
check(strtod(s, &e) == 3.14159 && e == s + 7, "strtod(\"3.14159\") == 3.14159");
|
||||
s = "1.5e2";
|
||||
check(strtod(s, &e) == 150.0 && e == s + 5, "strtod(\"1.5e2\")==150");
|
||||
s = " -0.5";
|
||||
check(strtod(s, &e) == -0.5 && e == s + 6, "strtod(\" -0.5\")==-0.5");
|
||||
s = ".5";
|
||||
check(strtod(s, &e) == 0.5 && e == s + 2, "strtod(\".5\")==0.5");
|
||||
s = "1.";
|
||||
check(strtod(s, &e) == 1.0 && e == s + 2, "strtod(\"1.\")==1.0");
|
||||
s = ".";
|
||||
r = strtod(s, &e);
|
||||
check(r == 0.0 && e == s, "strtod(\".\")==0, endptr at start");
|
||||
s = "1e";
|
||||
r = strtod(s, &e);
|
||||
check(r == 1.0 && e == s + 1, "strtod(\"1e\")==1.0, bare e not part of subject");
|
||||
s = "0";
|
||||
check(strtod(s, &e) == 0.0 && e == s + 1, "strtod(\"0\")==0.0");
|
||||
s = "-0";
|
||||
r = strtod(s, &e);
|
||||
check(r == 0.0 && 1.0 / r == -__builtin_inf(), "strtod(\"-0\") == -0.0");
|
||||
s = "123456789012345678901234567890";
|
||||
r = strtod(s, &e);
|
||||
check(r > 0.0 && e == s + 30, "strtod(30-digit integer) positive, endptr at end");
|
||||
s = "9.999999999999999e22";
|
||||
check(strtod(s, &e) == 9.999999999999999e22, "strtod(19-digit) exact");
|
||||
}
|
||||
|
||||
/* 6c. strtof and strtold basics. */
|
||||
static void
|
||||
strtof_strtold(void)
|
||||
{
|
||||
const char *s = "0.1";
|
||||
char *e;
|
||||
|
||||
check(strtof(s, &e) == 0.1f && e == s + 3, "strtof(\"0.1\") == 0.1f");
|
||||
s = "inf";
|
||||
check(strtof(s, &e) == __builtin_inff() && e == s + 3, "strtof(\"inf\") == +inf");
|
||||
s = "nan";
|
||||
{
|
||||
float nf = strtof(s, &e);
|
||||
|
||||
check(nf != nf && e == s + 3, "strtof(\"nan\") is NaN");
|
||||
}
|
||||
s = "0x1.8p1";
|
||||
check(strtof(s, &e) == 3.0f && e == s + 7, "strtof(\"0x1.8p1\")==3.0f");
|
||||
s = "0.1";
|
||||
check(strtold(s, &e) == 0.1L && e == s + 3, "strtold(\"0.1\") == 0.1L");
|
||||
s = "3.14159";
|
||||
check(strtold(s, &e) == 3.14159L && e == s + 7, "strtold(\"3.14159\") == 3.14159L");
|
||||
s = "1e100";
|
||||
check(strtold(s, &e) == 1e100L && e == s + 5, "strtold(\"1e100\") == 1e100L (exact)");
|
||||
s = "0x1.8p1";
|
||||
check(strtold(s, &e) == 3.0L && e == s + 7, "strtold(\"0x1.8p1\")==3.0L");
|
||||
s = "nan";
|
||||
{
|
||||
long double nl = strtold(s, &e);
|
||||
|
||||
check(nl != nl && e == s + 3, "strtold(\"nan\") is NaN");
|
||||
}
|
||||
s = "1e999";
|
||||
{
|
||||
long double big = strtold(s, &e);
|
||||
long double rel = big / 1e999L;
|
||||
|
||||
check(e == s + 5 && rel > 0.999999999999999L && rel < 1.000000000000001L,
|
||||
"strtold(\"1e999\") within 1e-15 of 1e999 (no range error)");
|
||||
}
|
||||
}
|
||||
|
||||
/* 7. ato* wrappers. */
|
||||
static void
|
||||
ato_wrappers(void)
|
||||
{
|
||||
check(atoi("42") == 42, "atoi(\"42\")==42");
|
||||
check(atoi(" -17") == -17, "atoi(\" -17\")==-17");
|
||||
check(atol("2147483648") == 2147483648L, "atol(\"2147483648\")==2147483648");
|
||||
check(atoll("9223372036854775807") == LLONG_MAX, "atoll max == LLONG_MAX");
|
||||
check(atof("1.5") == 1.5, "atof(\"1.5\")==1.5");
|
||||
check(atof(" +2.25e2") == 225.0, "atof(\" +2.25e2\")==225.0");
|
||||
check(atof("0x1.8p1") == 3.0, "atof(\"0x1.8p1\")==3.0");
|
||||
check(atof("x") == 0.0, "atof(\"x\")==0.0");
|
||||
}
|
||||
|
||||
/*
|
||||
* Failure scenarios (-f): every path that writes errno inside the library.
|
||||
* Because the test is host-linked, those writes land in glibc's TCB slot,
|
||||
* so errno is never read back — the C23 return-value clamps prove the
|
||||
* ERANGE/EINVAL behavior — and the process exits through a raw
|
||||
* SYS_exit_group without running host cleanup.
|
||||
*/
|
||||
static int
|
||||
failure_scenarios(void)
|
||||
{
|
||||
const char *s;
|
||||
char *e;
|
||||
int bad = 0;
|
||||
|
||||
s = "9223372036854775808";
|
||||
if (strtol(s, &e, 10) != LONG_MAX || e != s + 19)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtol overflow clamp\n");
|
||||
}
|
||||
s = "-9223372036854775809";
|
||||
if (strtol(s, &e, 10) != LONG_MIN || e != s + 20)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtol negative overflow clamp\n");
|
||||
}
|
||||
s = "9223372036854775808";
|
||||
if (strtoll(s, &e, 10) != LLONG_MAX || e != s + 19)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtoll overflow clamp\n");
|
||||
}
|
||||
s = "-9223372036854775809";
|
||||
if (strtoll(s, &e, 10) != LLONG_MIN || e != s + 20)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtoll negative overflow clamp\n");
|
||||
}
|
||||
s = "18446744073709551616";
|
||||
if (strtoul(s, &e, 10) != ULONG_MAX || e != s + 20)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtoul overflow clamp\n");
|
||||
}
|
||||
s = "18446744073709551617";
|
||||
if (strtoull(s, &e, 10) != ULLONG_MAX || e != s + 20)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtoull overflow clamp\n");
|
||||
}
|
||||
s = "-18446744073709551616";
|
||||
if (strtoul(s, &e, 10) != ULONG_MAX || e != s + 21)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtoul magnitude overflow (2^64) clamp\n");
|
||||
}
|
||||
s = "123";
|
||||
if (strtol(s, &e, 1) != 0 || e != s)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtol base 1 (EINVAL): value/endptr\n");
|
||||
}
|
||||
s = "123";
|
||||
if (strtol(s, &e, 37) != 0 || e != s)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtol base 37 (EINVAL): value/endptr\n");
|
||||
}
|
||||
s = "123";
|
||||
if (strtoul(s, &e, -1) != 0 || e != s)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtoul base -1 (EINVAL): value/endptr\n");
|
||||
}
|
||||
s = "1e999";
|
||||
if (strtod(s, &e) != __builtin_inf() || e != s + 5)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtod(\"1e999\") != +inf (HUGE_VAL)\n");
|
||||
}
|
||||
s = "-1e999";
|
||||
if (strtod(s, &e) != -__builtin_inf() || e != s + 6)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtod(\"-1e999\") != -inf\n");
|
||||
}
|
||||
s = "1e-999";
|
||||
if (strtod(s, &e) != 0.0 || e != s + 6)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtod(\"1e-999\") != 0.0 (underflow)\n");
|
||||
}
|
||||
s = "0x1.fffffffffffffp1024";
|
||||
if (strtod(s, &e) != __builtin_inf() || e != s + 22)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtod(hex overflow) != +inf\n");
|
||||
}
|
||||
s = "0x0.0000000000001p-1022";
|
||||
if (strtod(s, &e) != DBL_TRUE_MIN || e != s + 23)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtod(hex subnormal) != DBL_TRUE_MIN\n");
|
||||
}
|
||||
s = "1e999";
|
||||
if (strtof(s, &e) != __builtin_inff() || e != s + 5)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtof(\"1e999\") != +inf\n");
|
||||
}
|
||||
s = "1e-999";
|
||||
if (strtof(s, &e) != 0.0f || e != s + 6)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtof(\"1e-999\") != 0.0f\n");
|
||||
}
|
||||
s = "1e5000";
|
||||
if (strtold(s, &e) != __builtin_infl() || e != s + 6)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtold(\"1e5000\") != +inf\n");
|
||||
}
|
||||
|
||||
if (bad == 0)
|
||||
{
|
||||
say(1, "PASS: all overflow/EINVAL/underflow 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 */
|
||||
}
|
||||
|
||||
base_resolution();
|
||||
whitespace_signs();
|
||||
endptr_positions();
|
||||
exact_boundaries();
|
||||
unsigned_negative_wrap();
|
||||
strtod_special();
|
||||
strtod_decimal();
|
||||
strtof_strtold();
|
||||
ato_wrappers();
|
||||
|
||||
if (failures > 0)
|
||||
{
|
||||
say(2, "FAILED (");
|
||||
say_dec(2, (unsigned long)failures);
|
||||
say(2, " check(s))\n");
|
||||
return 1;
|
||||
}
|
||||
say(1, "all strtol/strtod tests passed\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user