feat(stdio): printf family

This commit is contained in:
2026-09-05 16:27:09 -04:00
parent 7b11452b87
commit 87ab610a9b
4 changed files with 3760 additions and 0 deletions
+33
View File
@@ -25,6 +25,7 @@
#include <vlibc/features.h>
#include <stdarg.h>
#include <stddef.h>
#include <sys/types.h>
@@ -299,4 +300,36 @@ fopen64(const char *restrict path, const char *restrict mode);
}
#endif
/* formatted output (todo 16) */
int
printf(const char *restrict format, ...);
int
fprintf(FILE *restrict stream, const char *restrict format, ...);
int
sprintf(char *restrict s, const char *restrict format, ...);
int
snprintf(char *restrict s, size_t n, const char *restrict format, ...);
int
vprintf(const char *restrict format, va_list ap);
int
vfprintf(FILE *restrict stream, const char *restrict format, va_list ap);
int
vsprintf(char *restrict s, const char *restrict format, va_list ap);
int
vsnprintf(char *restrict s, size_t n, const char *restrict format, va_list ap);
int
dprintf(int fd, const char *restrict format, ...);
int
vdprintf(int fd, const char *restrict format, va_list ap);
void
perror(const char *s);
#if VLIBC_LEVEL_GE(2)
int
asprintf(char **restrict strp, const char *restrict format, ...);
int
vasprintf(char **restrict strp, const char *restrict format, va_list ap);
#endif
#endif /* VLIBC_STDIO_H */
+2151
View File
File diff suppressed because it is too large Load Diff
+842
View File
@@ -0,0 +1,842 @@
/* internal unsigned 2^32-limb bignum core for printf float formatting (todo 16)
* clean-room implementation; nothing here is exported */
/*
* Invariants: limbs are little-endian (value = sum d[i] * 2^(32*i)); n == 0
* means value 0; no trailing-zero-limb invariant is kept -- callers normalize
* via vfpn_bn_norm. Shifts and rounding operate in place; allocation failure
* is reported as -1 and leaves errno to __libc_malloc.
*/
#include "../internal/malloc.h"
#include <stddef.h>
#include <stdint.h>
struct vfpn_bn
{
uint32_t fixed[640];
uint32_t *d;
size_t n;
size_t cap;
int heap;
};
static __attribute__((unused)) void
vfpn_bn_init(struct vfpn_bn *b)
{
b->d = b->fixed;
b->n = 0;
b->cap = 640;
b->heap = 0;
}
static __attribute__((unused)) void
vfpn_bn_free(struct vfpn_bn *b)
{
if (b->heap)
{
__libc_free(b->d);
}
}
static __attribute__((unused)) int
vfpn_bn_reserve(struct vfpn_bn *b, size_t need)
{
if (need <= b->cap)
{
return 0;
}
uint32_t *new = __libc_malloc(need * sizeof(uint32_t));
if (!new)
{
return -1;
}
size_t copy = (b->cap < need) ? b->cap : need;
__builtin_memcpy(new, b->d, copy * sizeof(uint32_t));
if (b->heap)
{
__libc_free(b->d);
}
b->d = new;
b->cap = need;
b->heap = 1;
return 0;
}
static __attribute__((unused)) void
vfpn_bn_norm(struct vfpn_bn *b)
{
while (b->n > 0 && b->d[b->n - 1] == 0)
{
b->n--;
}
}
static __attribute__((unused)) int
vfpn_bn_set_u64(struct vfpn_bn *b, uint64_t v)
{
size_t need = 0;
for (uint64_t t = v; t != 0; t >>= 32)
{
need++;
}
if (vfpn_bn_reserve(b, need) != 0)
{
return -1;
}
b->n = 0;
while (v != 0)
{
b->d[b->n++] = (uint32_t)v;
v >>= 32;
}
return 0;
}
static __attribute__((unused)) int
vfpn_bn_mul_small(struct vfpn_bn *b, uint32_t m)
{
if (m == 0)
{
b->n = 0;
return 0;
}
if (vfpn_bn_reserve(b, b->n + 1) != 0)
{
return -1;
}
uint64_t carry = 0;
for (size_t i = 0; i < b->n; i++)
{
uint64_t cur = (uint64_t)b->d[i] * m + carry;
b->d[i] = (uint32_t)cur;
carry = cur >> 32;
}
if (carry != 0)
{
b->d[b->n++] = (uint32_t)carry;
}
return 0;
}
static __attribute__((unused)) int
vfpn_bn_shl(struct vfpn_bn *b, size_t bits)
{
if (bits == 0)
{
return 0;
}
size_t wl = bits / 32;
size_t s = bits % 32;
size_t newn = b->n + wl + ((s != 0) ? 1 : 0);
if (vfpn_bn_reserve(b, newn) != 0)
{
return -1;
}
if (s == 0)
{
for (size_t i = b->n; i-- > 0;)
{
b->d[i + wl] = b->d[i];
}
for (size_t i = 0; i < wl; i++)
{
b->d[i] = 0;
}
}
else
{
for (size_t j = newn; j-- > 0;)
{
uint64_t cur = 0;
if (j >= wl && j - wl < b->n)
{
cur |= (uint64_t)b->d[j - wl] << s;
}
if (j > wl && j - wl - 1 < b->n)
{
cur |= (uint64_t)b->d[j - wl - 1] >> (32 - s);
}
b->d[j] = (uint32_t)cur;
}
}
b->n = newn;
return 0;
}
static __attribute__((unused)) int
vfpn_bn_add_one(struct vfpn_bn *b)
{
size_t i = 0;
while (i < b->n)
{
b->d[i]++;
if (b->d[i] != 0)
{
return 0;
}
i++;
}
if (vfpn_bn_reserve(b, b->n + 1) != 0)
{
return -1;
}
b->d[b->n] = 1;
b->n++;
return 0;
}
static __attribute__((unused)) int
vfpn_bn_is_zero(const struct vfpn_bn *b)
{
return b->n == 0;
}
static __attribute__((unused)) int
vfpn_bn_cmp(const struct vfpn_bn *a, const struct vfpn_bn *b)
{
if (a->n < b->n)
{
return -1;
}
if (a->n > b->n)
{
return 1;
}
for (size_t i = a->n; i-- > 0;)
{
if (a->d[i] < b->d[i])
{
return -1;
}
if (a->d[i] > b->d[i])
{
return 1;
}
}
return 0;
}
static __attribute__((unused)) int
vfpn_bn_shr_round(struct vfpn_bn *b, size_t bits)
{
if (bits == 0)
{
return 0;
}
size_t w = bits / 32;
size_t s = bits % 32;
if (w >= b->n)
{
/* Whole value discarded: result is 0, rounded up iff the original
* value exceeds 2^(bits-1). That can only happen when bits == 32*n
* (s == 0 and w == n), so compare the top limb against half. */
int round = 0;
if (s == 0 && w == b->n && b->n > 0)
{
uint32_t top = b->d[b->n - 1];
if (top > 0x80000000u)
{
round = 1;
}
else if (top == 0x80000000u)
{
for (size_t i = b->n - 1; i-- > 0;)
{
if (b->d[i] != 0)
{
round = 1;
break;
}
}
}
}
b->n = 0;
if (round)
{
return vfpn_bn_add_one(b);
}
return 0;
}
/* Round bit and sticky are captured from the discarded low `bits` bits
* of the original value before the in-place shift. */
int round_bit;
int sticky;
if (s > 0)
{
round_bit = (int)((b->d[w] >> (s - 1)) & 1u);
sticky = (b->d[w] & ((1u << (s - 1)) - 1u)) != 0;
for (size_t i = 0; i < w; i++)
{
if (b->d[i] != 0)
{
sticky = 1;
break;
}
}
}
else
{
round_bit = (int)((b->d[w - 1] >> 31) & 1u);
sticky = (b->d[w - 1] & 0x7FFFFFFFu) != 0;
for (size_t i = 0; i + 1 < w; i++)
{
if (b->d[i] != 0)
{
sticky = 1;
break;
}
}
}
/* In-place right shift by `bits`; ascending so every source limb is
* still readable when its destination is written. */
size_t old_n = b->n;
if (s == 0)
{
for (size_t j = 0; j + w < old_n; j++)
{
b->d[j] = b->d[j + w];
}
}
else
{
for (size_t j = 0; j + w < old_n; j++)
{
uint32_t lo = b->d[j + w] >> s;
uint32_t hi = (j + w + 1 < old_n) ? (b->d[j + w + 1] << (32 - s)) : 0;
b->d[j] = lo | hi;
}
}
b->n = old_n - w;
vfpn_bn_norm(b);
int odd = (b->n > 0 && (b->d[0] & 1u)) ? 1 : 0;
if (round_bit && (sticky || odd))
{
return vfpn_bn_add_one(b);
}
return 0;
}
static __attribute__((unused)) uint32_t
vfpn_bn_divmod_small_1e9(struct vfpn_bn *b)
{
uint64_t r = 0;
for (size_t i = b->n; i-- > 0;)
{
uint64_t cur = (r << 32) | b->d[i];
b->d[i] = (uint32_t)(cur / 1000000000ULL);
r = cur % 1000000000ULL;
}
vfpn_bn_norm(b);
return (uint32_t)r;
}
static __attribute__((unused)) int
vfpn_bn_pow5(struct vfpn_bn *b, size_t k)
{
int r = vfpn_bn_set_u64(b, 1);
if (r != 0)
{
return r;
}
for (size_t i = 0; i < k; i++)
{
r = vfpn_bn_mul_small(b, 5);
if (r != 0)
{
return r;
}
}
return 0;
}
/* --- exact decimal rounding layer ------------------------------------ */
/*
* Number of bits needed to hold |b| (index of the highest set bit plus one);
* 0 for the value zero. The caller may leave a trailing zero limb in place,
* so the scan starts from the recorded length and skips down.
*/
static __attribute__((unused)) size_t
vfpn_bn_bitlen(const struct vfpn_bn *b)
{
if (b->n == 0)
{
return 0;
}
size_t i = b->n - 1;
while (i > 0 && b->d[i] == 0)
{
i--;
}
if (b->d[i] == 0)
{
return 0;
}
return i * 32 + (size_t)(32 - __builtin_clz(b->d[i]));
}
static __attribute__((unused)) int
vfpn_bn_get_bit(const struct vfpn_bn *b, size_t i)
{
return (int)((b->d[i / 32] >> (i % 32)) & 1u);
}
/*
* Set bit i of b. Zero-extends the limb array when the bit lies beyond the
* current length; ORing never clears, so the value only grows.
*/
static __attribute__((unused)) int
vfpn_bn_set_bit(struct vfpn_bn *b, size_t i, int bit)
{
if (!bit)
{
return 0;
}
if (b->n <= i / 32)
{
size_t need = i / 32 + 1;
if (vfpn_bn_reserve(b, need) != 0)
{
return -1;
}
for (size_t j = b->n; j < need; j++)
{
b->d[j] = 0;
}
b->n = need;
}
b->d[i / 32] |= (1u << (i % 32));
return 0;
}
/*
* Shift |b| left by one bit in place (limb ripple). The caller's cap must
* already make room for the possible extra top limb; the reserve here is
* only a safety net and normally returns immediately.
*/
static __attribute__((unused)) int
vfpn_bn_shl1(struct vfpn_bn *b)
{
if (b->n == 0)
{
return 0;
}
if (vfpn_bn_reserve(b, b->n + 1) != 0)
{
return -1;
}
uint32_t carry = 0;
for (size_t i = 0; i < b->n; i++)
{
uint32_t cur = b->d[i];
b->d[i] = (cur << 1) | carry;
carry = cur >> 31;
}
if (carry != 0)
{
b->d[b->n++] = carry;
}
return 0;
}
/*
* In-place limb-wise subtraction a -= b. The caller guarantees a >= b, so
* the borrow always clears before the top of a; a is renormalized.
*/
static __attribute__((unused)) void
vfpn_bn_sub_inplace(struct vfpn_bn *a, const struct vfpn_bn *b)
{
uint32_t borrow = 0;
size_t i = 0;
for (; i < b->n; i++)
{
uint64_t diff = (uint64_t)a->d[i] - (uint64_t)b->d[i] - borrow;
a->d[i] = (uint32_t)diff;
borrow = (uint32_t)((diff >> 32) & 1u);
}
while (i < a->n && borrow != 0)
{
a->d[i]--;
borrow = (a->d[i] == 0xFFFFFFFFu) ? 1u : 0u;
i++;
}
vfpn_bn_norm(a);
}
/*
* Q = N / D, R = N mod D by restoring binary long division. N may be zero;
* D must be nonzero. Q and R are written over whatever they held. The
* quotients bits are produced most-significant first; the loop runs over the
* dividend's bit positions from bitlen(N)-1 down to 0, pulling one new
* dividend bit into R and subtracting D whenever R >= D.
*/
static __attribute__((unused)) int
vfpn_divmod_binary(struct vfpn_bn *Q, struct vfpn_bn *R, const struct vfpn_bn *N,
const struct vfpn_bn *D)
{
if (vfpn_bn_is_zero(D))
{
return -1;
}
if (vfpn_bn_reserve(Q, N->n + 2) != 0)
{
return -1;
}
if (vfpn_bn_reserve(R, N->n + 2) != 0)
{
return -1;
}
Q->n = 0;
R->n = 0;
size_t nbits = vfpn_bn_bitlen(N);
for (size_t i = nbits; i-- > 0;)
{
if (vfpn_bn_shl1(R) != 0)
{
return -1;
}
if (vfpn_bn_set_bit(R, 0, vfpn_bn_get_bit(N, i)) != 0)
{
return -1;
}
if (vfpn_bn_cmp(R, D) >= 0)
{
vfpn_bn_sub_inplace(R, D);
if (vfpn_bn_set_bit(Q, i, 1) != 0)
{
return -1;
}
}
}
vfpn_bn_norm(Q);
vfpn_bn_norm(R);
return 0;
}
/*
* Compare 2*a against b (both canonical base-2^32 digit strings) without
* building the doubled value: doubling a limb's carry is the next limb's
* (d[i] >> 31), which the loop below folds in, and an extra top digit
* appears when the original top limb had its high bit set. All digits of 2*a
* stay below 2^32, so a plain digitwise comparison is exact.
*/
static __attribute__((unused)) int
vfpn_bn_cmp_doubled(const struct vfpn_bn *a, const struct vfpn_bn *b)
{
if (a->n == 0)
{
return vfpn_bn_is_zero(b) ? 0 : -1;
}
size_t extra = (a->d[a->n - 1] >> 31) ? 1u : 0u;
size_t la = a->n + extra;
if (la != b->n)
{
return (la > b->n) ? 1 : -1;
}
for (size_t i = la; i-- > 0;)
{
uint32_t da;
if (extra != 0 && i == a->n)
{
da = 1; /* carry out of the doubled top limb */
}
else
{
da = (uint32_t)(a->d[i] << 1);
if (i > 0)
{
da += (a->d[i - 1] >> 31);
}
}
if (da != b->d[i])
{
return (da < b->d[i]) ? -1 : 1;
}
}
return 0;
}
/*
* R = round_half_even(mant * 2^exp2 * 10^s), computed exactly as an integer
* bignum (mant > 0; s may be negative). s >= 0 scales R by 5^s and by a
* single power of two, rounding once at the end (nearest-even). s < 0 builds
* D = 5^(-s) and shifts either N or D so that value = N / D, divides exactly,
* and rounds the quotient half-even off the remainder: 2*rem > D rounds up,
* 2*rem == D rounds up only when the quotient is odd. Returns 0 or -1 on
* allocation failure (R is then unspecified).
*/
static __attribute__((unused)) int
vfpn_round_scale10(struct vfpn_bn *R, uint64_t mant, int exp2, long s)
{
struct vfpn_bn N;
struct vfpn_bn D;
struct vfpn_bn Q;
struct vfpn_bn rem;
vfpn_bn_init(&N);
vfpn_bn_init(&D);
vfpn_bn_init(&Q);
vfpn_bn_init(&rem);
if (s >= 0)
{
if (vfpn_bn_set_u64(R, mant) != 0)
{
goto fail;
}
for (long i = 0; i < s; i++)
{
if (vfpn_bn_mul_small(R, 5) != 0)
{
goto fail;
}
}
long long sh = (long long)exp2 + (long long)s;
if (sh >= 0)
{
if (vfpn_bn_shl(R, (size_t)sh) != 0)
{
goto fail;
}
}
else
{
if (vfpn_bn_shr_round(R, (size_t)(-sh)) != 0)
{
goto fail;
}
}
vfpn_bn_free(&N);
vfpn_bn_free(&D);
vfpn_bn_free(&Q);
vfpn_bn_free(&rem);
return 0;
}
/* value = mant * 2^exp2 / (2^t * 5^t) with t = -s: make it N / D. */
{
long long t = -(long long)s;
long long sh = (long long)exp2 - t;
if (vfpn_bn_pow5(&D, (size_t)t) != 0)
{
goto fail;
}
if (vfpn_bn_set_u64(&N, mant) != 0)
{
goto fail;
}
if (sh >= 0)
{
if (vfpn_bn_shl(&N, (size_t)sh) != 0)
{
goto fail;
}
vfpn_bn_norm(&N);
}
else
{
if (vfpn_bn_shl(&D, (size_t)(-sh)) != 0)
{
goto fail;
}
vfpn_bn_norm(&D);
}
if (vfpn_divmod_binary(&Q, &rem, &N, &D) != 0)
{
goto fail;
}
int c = vfpn_bn_cmp_doubled(&rem, &D);
if (c > 0 || (c == 0 && Q.n > 0 && (Q.d[0] & 1u) != 0))
{
if (vfpn_bn_add_one(&Q) != 0)
{
goto fail;
}
}
if (vfpn_bn_reserve(R, Q.n) != 0)
{
goto fail;
}
if (Q.n > 0)
{
__builtin_memcpy(R->d, Q.d, Q.n * sizeof(uint32_t));
}
R->n = Q.n;
}
vfpn_bn_free(&N);
vfpn_bn_free(&D);
vfpn_bn_free(&Q);
vfpn_bn_free(&rem);
return 0;
fail:
vfpn_bn_free(&N);
vfpn_bn_free(&D);
vfpn_bn_free(&Q);
vfpn_bn_free(&rem);
return -1;
}
/*
* Decimal digits of |v| into out (most significant first, no terminating
* NUL); returns the digit count or -1 when outcap is too small. The value
* zero yields a single '0'. Groups of nine decimal digits are stripped from
* the low end into a fixed stack (a 640-limb value caps at ~6165 digits, far
* inside 700 groups), then re-emitted top group unpadded, lower groups
* zero-padded to nine.
*/
static __attribute__((unused)) int
vfpn_bn_to_dec(const struct vfpn_bn *v, char *out, size_t outcap)
{
if (vfpn_bn_is_zero(v))
{
if (outcap < 1)
{
return -1;
}
out[0] = '0';
return 1;
}
struct vfpn_bn w;
vfpn_bn_init(&w);
if (vfpn_bn_reserve(&w, v->n) != 0)
{
return -1;
}
w.n = v->n;
if (v->n > 0)
{
__builtin_memcpy(w.d, v->d, v->n * sizeof(uint32_t));
}
uint32_t groups[700];
size_t ngroups = 0;
while (!vfpn_bn_is_zero(&w))
{
if (ngroups >= 700)
{
vfpn_bn_free(&w);
return -1;
}
groups[ngroups++] = vfpn_bn_divmod_small_1e9(&w);
}
vfpn_bn_free(&w);
size_t top = ngroups - 1;
uint32_t t = groups[top];
size_t tdig = 1;
for (uint32_t x = t / 10; x != 0; x /= 10)
{
tdig++;
}
size_t ndigits = tdig + (ngroups - 1) * 9;
if (outcap < ndigits)
{
return -1;
}
size_t pos = 0;
char tmp[10];
size_t ntmp = 0;
do
{
tmp[ntmp++] = (char)('0' + t % 10);
t /= 10;
} while (t != 0);
while (ntmp > 0)
{
out[pos++] = tmp[--ntmp];
}
for (size_t gi = ngroups - 1; gi-- > 0;)
{
uint32_t val = groups[gi];
for (int k = 8; k >= 0; k--)
{
out[pos + (size_t)k] = (char)('0' + val % 10);
val /= 10;
}
pos += 9;
}
return (int)ndigits;
}
+734
View File
@@ -0,0 +1,734 @@
/*
* vlibc — formatted output test, integer conversions (todo 16, task A).
*
* Compares the snprintf/sprintf/dprintf/FILE-output of vfprintf.c against
* golden string literals, all of which were verified against the host glibc
* (a throwaway /tmp/probe.c) EXCEPT the spec'd divergence that %p of NULL is
* "0x0" (glibc prints "(nil)").
*
* Coverage: %d %i %u %o %x %X over 0, +/-1, INT_MIN/MAX, UINT_MAX,
* LONG_MIN/MAX (as %ld), LLONG_MIN/MAX (%lld), ULONG_MAX; %hhd/%hd
* truncation; %zu %zd %tu %td %ju %jd; the '#' prefix rules (including
* %#.0o of 0, %#x of 0, %#o of 0); the - + space 0 flags and their
* interactions with width and precision; %c %s with precision/width and the
* NULL "(null)" spelling; %p of a local (shape check) and of NULL ("0x0");
* %n; width and precision supplied via '*'; %% escaping; snprintf truncation
* (including n == 0 with a NULL buffer); sprintf/vsprintf; fprintf/vfprintf
* to a file; dprintf/vdprintf to a raw descriptor; printf/vprintf through a
* redirected stdout; and (level 2) asprintf/vasprintf.
*
* All diagnostics go through raw SYS_write; errno is never read and never
* deliberately written in the success paths exercised here, so no host TCB
* preservation is needed. Only vlibc headers are included (-Iinclude wins
* over the host's).
*/
#include <float.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../src/internal/syscall.h"
static int failures;
/* Write a NUL-terminated string to fd via the raw syscall layer. */
static void
say(int fd, const char *s)
{
long n = 0;
while (s[n] != '\0')
{
n++;
}
__syscall3(SYS_write, fd, (long)s, n);
}
/* Write v in decimal to fd. */
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++;
}
}
/* Byte-compare two buffers (the produced content need not be NUL-ended). */
static int
mem_eq(const unsigned char *a, const unsigned char *b, unsigned long n)
{
unsigned long i;
for (i = 0; i < n; i++)
{
if (a[i] != b[i])
{
return 0;
}
}
return 1;
}
/* Read a whole small file through raw syscalls into buf; returns size. */
static long
raw_read_all(const char *path, char *buf, unsigned long cap)
{
long fd = __syscall4(SYS_openat, -100, (long)path, 0x0, 0);
long got = -1;
long total = 0;
if (fd < 0)
{
return -1;
}
while (total < (long)cap)
{
got = __syscall3(SYS_read, fd, (long)(buf + total), (long)(cap - (unsigned long)total));
if (got <= 0)
{
break;
}
total += got;
}
__syscall1(SYS_close, fd);
return got < 0 ? -1 : total;
}
/* Forwarding wrappers so the v* functions get exercised with real va_lists. */
static int
run_vprintf(const char *format, ...) // NOLINT(bugprone-easily-swappable-parameters)
{
va_list ap;
int rc;
va_start(ap, format);
rc = vprintf(format, ap);
va_end(ap);
return rc;
}
static int
run_vfprintf(FILE *f, const char *format, ...) // NOLINT(bugprone-easily-swappable-parameters)
{
va_list ap;
int rc;
va_start(ap, format);
rc = vfprintf(f, format, ap);
va_end(ap);
return rc;
}
static int
run_vsprintf(char *s, const char *format, ...) // NOLINT(bugprone-easily-swappable-parameters)
{
va_list ap;
int rc;
va_start(ap, format);
rc = vsprintf(s, format, ap);
va_end(ap);
return rc;
}
static int
run_vdprintf(int fd, const char *format, ...) // NOLINT(bugprone-easily-swappable-parameters)
{
va_list ap;
int rc;
va_start(ap, format);
rc = vdprintf(fd, format, ap);
va_end(ap);
return rc;
}
#if VLIBC_LEVEL_GE(2)
static int
run_vasprintf(char **p, const char *format, ...) // NOLINT(bugprone-easily-swappable-parameters)
{
va_list ap;
int rc;
va_start(ap, format);
rc = vasprintf(p, format, ap);
va_end(ap);
return rc;
}
#endif
/* A single snprintf result checked against a golden literal. */
static void
golden(const char *want, const char *format, ...) // NOLINT(bugprone-easily-swappable-parameters)
{
char out[64];
va_list ap;
int rc;
va_start(ap, format);
rc = vsnprintf(out, sizeof out, format, ap);
va_end(ap);
check(rc >= 0 && strcmp(out, want) == 0, want);
}
/* 1. Plain integers: d/i/u/o/x/X over the extremes and the sign cases. */
static void
int_scenario(void)
{
char out[64];
int rc;
golden("0", "%d", 0);
golden("1", "%d", 1);
golden("-1", "%d", -1);
golden("-2147483648", "%d", INT_MIN);
golden("2147483647", "%d", INT_MAX);
golden("-7", "%i", -7);
golden("4294967295", "%u", UINT_MAX);
golden("0", "%u", 0);
golden("10", "%o", 8);
golden("ff", "%x", 255);
golden("FF", "%X", 255);
golden("-9223372036854775808", "%ld", LONG_MIN);
golden("9223372036854775807", "%ld", LONG_MAX);
golden("-9223372036854775808", "%lld", LLONG_MIN);
golden("9223372036854775807", "%lld", LLONG_MAX);
golden("18446744073709551615", "%lu", ULONG_MAX);
rc = snprintf(out, sizeof out, "%d%d", 1, 2);
check(rc == 2 && strcmp(out, "12") == 0, "adjacent conversions concatenate");
rc = snprintf(out, sizeof out, "%s%d", "v", -7);
check(rc == 3 && strcmp(out, "v-7") == 0, "literal text between conversions");
}
/* 2. Length modifiers: hh/h truncation and the j/z/t widths. */
static void
length_scenario(void)
{
golden("44", "%hhd", 300);
golden("4464", "%hd", 70000);
golden("44", "%hhu", 300);
golden("-44", "%hhd", -300);
golden("4000000000", "%zu", (size_t)4000000000UL);
golden("-4000000000", "%zd", (ssize_t)-4000000000L);
golden("123456789", "%tu", (ptrdiff_t)123456789L);
golden("-987654321", "%td", (ptrdiff_t)-987654321L);
golden("18446744073709551615", "%ju", (uintmax_t)UINTMAX_MAX);
golden("-9223372036854775807", "%jd", (intmax_t)-9223372036854775807LL);
golden("ffffffffffffffff", "%lx", (unsigned long)ULONG_MAX);
}
/* 3. The '#' prefixes (octal, hex) and the zero value corner cases. */
static void
alt_scenario(void)
{
golden("0x1abc", "%#x", 0x1abc);
golden("0X1ABC", "%#X", 0x1abc);
golden("010", "%#o", 8);
golden("0", "%#.0o", 0);
golden("0", "%#x", 0);
golden("0", "%#o", 0);
golden("00000010", "%#08o", 8);
golden("010", "%#.3o", 8);
golden("", "%#.0x", 0);
golden(" 0", "%#5.0o", 0);
}
/* 4. Flags and their interactions with width and precision. */
static void
flag_scenario(void)
{
golden("42 ", "%-8d", 42);
golden("+42", "%+d", 42);
golden("-42", "%+d", -42);
golden(" 42", "% d", 42);
golden("-42", "% d", -42);
golden("00000042", "%08d", 42);
golden("42 ", "%-08d", 42);
golden("+0000042", "%+08d", 42);
golden(" 042", "%5.3d", 42);
golden("042 ", "%-5.3d", 42);
golden(" ", "%05.0d", 0);
golden(" 42", "%05.0d", 42);
golden(" 000", "%5.3d", 0);
golden(" 42", "%5d", 42);
golden("42 ", "%-5d", 42);
golden("+0042", "%+05d", 42);
golden(" 42", "%*d", 5, 42);
}
/* 5. Characters, strings, pointers, %n, and the literal percent. */
static void
charstr_scenario(void)
{
char out[64];
int marker;
int n;
int rc;
golden("A", "%c", 'A');
golden("hello", "%s", "hello");
golden("hel", "%.3s", "hello");
golden(" hel", "%5.3s", "hello");
golden("hel ", "%-5.3s", "hello");
golden("(null)", "%s", (char *)0);
golden("%", "%%");
golden("0x0", "%p", (void *)0);
n = -1;
rc = snprintf(out, sizeof out, "abc%n", &n);
check(rc == 3 && n == 3 && strcmp(out, "abc") == 0, "%n stores the count so far");
rc = snprintf(out, sizeof out, "%p", (void *)&marker);
if (rc > 2 && out[0] == '0' && out[1] == 'x')
{
int i;
int hex = 1;
for (i = 2; out[i] != '\0'; i++)
{
if (!((out[i] >= '0' && out[i] <= '9') || (out[i] >= 'a' && out[i] <= 'f')))
{
hex = 0;
}
}
check(hex && i > 2, "%p of a local prints 0x + lowercase hex");
}
else
{
check(0, "%p of a local prints 0x + lowercase hex");
}
}
/* 6. Width and precision supplied through '*'. */
static void
star_scenario(void)
{
golden("00042", "%.*d", 5, 42);
golden("042", "%.*d", 3, 42);
golden("42", "%.*d", -1, 42);
golden("hel", "%.*s", 3, "hello");
}
/* 7. snprintf truncation semantics. */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
static void
trunc_scenario(void)
{
char b[8];
int rc;
rc = snprintf(b, 5, "%s", "hello world");
check(rc == 11 && strcmp(b, "hell") == 0,
"snprintf truncates at n-1 and returns the full length");
rc = snprintf((char *)0, 0, "%s", "hello world");
check(rc == 11, "snprintf with n == 0 writes nothing and returns the length");
rc = snprintf(b, 1, "%s", "hello world");
check(rc == 11 && b[0] == '\0', "snprintf with n == 1 writes only the NUL");
}
#pragma GCC diagnostic pop
/* 8. sprintf/vsprintf (no bound). */
static void
sprintf_scenario(void)
{
char s[32];
int rc;
rc = sprintf(s, "%d-%s", 7, "x");
check(rc == 3 && strcmp(s, "7-x") == 0, "sprintf writes past the argument bound");
rc = run_vsprintf(s, "%d-%s", 7, "x");
check(rc == 3 && strcmp(s, "7-x") == 0, "vsprintf writes past the argument bound");
}
/* 9. dprintf/vdprintf to a raw descriptor. */
static void
dprintf_scenario(void)
{
const char path[] = "/tmp/vlibc-test-printf-dprintf.txt";
char buf[64];
long fd;
long got;
int r1;
int r2;
fd = __syscall4(SYS_openat, -100, (long)path, (long)(0x1 | 0x40 | 0x200), 0666);
check(fd >= 0, "dprintf openat succeeds");
if (fd < 0)
{
return;
}
r1 = (dprintf((int)fd, "%d %s", 9, "nine") == 6);
r2 = (run_vdprintf((int)fd, " %#x", 0xbeef) == 7);
__syscall1(SYS_close, fd);
check(r1, "dprintf returns the byte count");
check(r2, "vdprintf returns the byte count");
got = raw_read_all(path, buf, sizeof buf);
check(got == 13 &&
mem_eq((const unsigned char *)buf, (const unsigned char *)"9 nine 0xbeef", 13),
"dprintf content reached the file unbuffered");
check(remove(path) == 0, "remove deletes the dprintf file");
}
/* 10. fprintf/vfprintf through a buffered FILE, read back raw. */
static void
file_scenario(void)
{
const char path[] = "/tmp/vlibc-test-printf-file.txt";
char buf[64];
FILE *f;
long got;
int r1;
int r2;
f = fopen(path, "w");
check(f != NULL, "fopen for fprintf succeeds");
if (f == NULL)
{
return;
}
r1 = (fprintf(f, "%s %d", "abc", 123) == 7);
r2 = (run_vfprintf(f, " %x", 255) == 3);
check(r1, "fprintf returns the byte count");
check(r2, "vfprintf returns the byte count");
check(fclose(f) == 0, "fclose flushes the fprintf file");
got = raw_read_all(path, buf, sizeof buf);
check(got == 10 && mem_eq((const unsigned char *)buf, (const unsigned char *)"abc 123 ff", 10),
"fprintf/vfprintf content reached the file");
check(remove(path) == 0, "remove deletes the fprintf file");
}
/* 11. printf/vprintf through a redirected stdout (the flush is explicit). */
static void
stdout_scenario(void)
{
const char path[] = "/tmp/vlibc-test-printf-stdout.txt";
char buf[64];
long saved;
long fd;
long got;
int r1;
int r2;
int r3;
saved = __syscall1(SYS_dup, 1);
check(saved >= 0, "dup(1) for the printf scenario");
if (saved < 0)
{
return;
}
fd = __syscall4(SYS_openat, -100, (long)path, (long)(0x1 | 0x40 | 0x200), 0666);
if (fd < 0)
{
__syscall2(SYS_dup2, saved, 1);
__syscall1(SYS_close, saved);
check(0, "openat for the printf scenario");
return;
}
__syscall2(SYS_dup2, fd, 1);
__syscall1(SYS_close, fd);
/* No check() output while fd 1 names the file. */
r1 = (printf("%d-%s", 1, "x") == 3);
r2 = (run_vprintf("%c%d", 'q', 5) == 2);
r3 = (fflush(stdout) == 0);
__syscall2(SYS_dup2, saved, 1);
__syscall1(SYS_close, saved);
check(r1, "printf to stdout returns its byte count");
check(r2, "vprintf to stdout returns its byte count");
check(r3, "fflush(stdout) after the redirect");
got = raw_read_all(path, buf, sizeof buf);
check(got == 5 && mem_eq((const unsigned char *)buf, (const unsigned char *)"1-xq5", 5),
"printf/vprintf content reached the redirected stdout");
check(remove(path) == 0, "remove deletes the stdout file");
}
#if VLIBC_LEVEL_GE(2)
/* 12. Level 2: asprintf/vasprintf. */
static void
asprintf_scenario(void)
{
char *p = NULL;
int rc;
rc = asprintf(&p, "%d-%s", 5, "x");
check(rc == 3 && p != NULL && strcmp(p, "5-x") == 0, "asprintf allocates and fills");
free(p);
p = NULL;
rc = run_vasprintf(&p, "%u", 42U);
check(rc == 2 && p != NULL && strcmp(p, "42") == 0, "vasprintf allocates and fills");
free(p);
}
#endif /* VLIBC_LEVEL_GE(2) */
/* Bit-pattern constructors for the float scenarios (host arithmetic cannot
* name every subnormal or sign-of-NaN case portably). */
static double
mkdbl(unsigned long long bits)
{
double v;
memcpy(&v, &bits, sizeof v);
return v;
}
static long double
mkldbl(unsigned e15, unsigned neg, unsigned long long sig)
{
unsigned char raw[16];
long double v;
memset(raw, 0, sizeof raw);
memcpy(raw, &sig, 8);
raw[8] = (unsigned char)(e15 & 0xFF);
raw[9] = (unsigned char)((e15 >> 8) | (neg ? 0x80 : 0));
memcpy(&v, raw, sizeof raw);
return v;
}
/* 13. %a/%A hex floats, the long double L length, and the round-half-even
* hex-digit rounding (all goldens verified against host glibc). */
static void
hexfloat_scenario(void)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Woverlength-strings"
static const char *const ldmax_dec =
"11897314953572317650212638530309702051690633222946242004403237338917370055229707"
"22616410290336528882853545697807495577314427443153670288434198125573853743678673"
"59320070697326320191591828296152436552951064679108661431179063216977883889613478"
"65606003991487534332114549111600886798451548665128523401497730376000091254793939"
"66223151383622417838542743917838138717805889487540575168226347659235576974805113"
"72564902088485522249479139937758502601177354918009979622602685950855888360815984"
"69002356451323465944763849398592764562845796617729304078066092291027150460853880"
"87959327781622986827547830768080040150694942303411728957777100335714010559775242"
"12405734700738625166011082837911962300846927720096515350020847447079244384854591"
"28867230006190851264721119513614675276335195629275979572502780029807959041931396"
"03021470997035276467445530922022679656280991498232083329641241038509239184734786"
"12192169721054348428704835340811304257300221642134891734717423480071488075100206"
"43905172342476560047217680964861079949434157034763206435586242074435044243805661"
"36017608837478165389027809576975977286860071487028287955567141404632615832623602"
"76289631617397848425448686060994827086796804807870251185893083854658422304090880"
"59962945945862019037660484467909260022254105307759010657606713472001258464069570"
"30257138960983757998926954553052368560758683179223113639519468850880771872104705"
"20395758748001314313144425494391994017575316933939236688185618912993172910425292"
"12368351599223220509980016771027840353601408292963981151228777681357060457893435"
"35451696539561254048846447169786893211671087229088082778350518228857646062218739"
"70285165508372099234948333443522898475123275372663606621390228126470623407535207"
"17240586650795182173034637826313533937067749019501978416904418247380631628285868"
"57741432581165364040218402724913393320949219498422442730427019873044536620350262"
"38695780468200360144729199712309553005720614186697485284685618651483271597448120"
"31219467516863793430961896151073300655524214851952017628585950910518394725028638"
"71632494167613804996319791441870254302706758495192008837915169401581740046711477"
"87720145964446117520405945350476472180797576111172084627363927960033967047003761"
"33745095531841500737964126050479232516613548412918842113408230154733047540670728"
"18763503617332908005951896325207071673904547777129682265206225651439919376804400"
"29238090311243791261477625596469422198137514696707944687035800439250765945161837"
"98118593920495440361149153107822510726914869798092409467721427270124043771874092"
"16756613634938900451232351668146089322400697993176017805338191849981933008410985"
"99393876029260139091141452600372028487213241195542428210183120421610446740462163"
"53369005836646065911562987647455250681450039329414041314954006776029510059622530"
"22823003631473824681059648442441324864573137437595096416168048024129351876204668"
"13563687753281467553879887177183651289394719533506188500326760735438867336800207"
"43878496570145760903498575712430451020387304948542567024793393228091105260415385"
"28994849203991091946129912491633289917998094380337879522093131466946149705939664"
"15237594928589096048991612194498998638483702248667224914892467841020618336462741"
"69695763076324802355879752452537370354338829608627534277400163334340550835370485"
"07374544819754722228975281083020898682633020285259923084168054539687911418297629"
"98896457648276528750456285492426516521775079951625966922911497778896235667095662"
"71384820181913483216879958636526376209782850700993372943967846398790249145142227"
"42527006363942327998483976739987154418554201562244154926653014515504685489258620"
"27608576183712976335876121538256512963353814166394951655600026415918655485005705"
"26114319529199188079545223946496276356301785808966922264062353828985358675959906"
"47008385687123810329591926494846250768992258419305480763620215089022149220528069"
"84201835084058693849381549890944546197789302911357651677540623227829831403347327"
"66039522316034228247175281818188443048809213219335508698733958612760736708666523"
"75555675803171490108477320096424318780070008797346032906278943553743564448851907"
"19161645514115576193939969076741515640282654366402676009508752394550734155613586"
"79330660317447209244465135323666476497354008519670407711036405381500734868917983"
"64049570606189535005089840913826869535090066783324472578712196604415284924840041"
"85093281190896363417573989716659600075948780061916409485433875852065711654107226"
"09962881501231443779440087493019447443307843889957018427100048083050121771235606"
"22895076269042856800047718893158089358515593863176652948089031267747029662545110"
"86154895839508779675546413794489596052797520987481383976257859210575628440175934"
"93241621483395653501891968113890918437957347032694063428900878058469403524534793"
"98080674273236297887100867175802531561302356064878709259865288416350972529537091"
"11431720488774740553905400942537542411931794417513706468964386151771884986701034"
"15325423859110896247108853858086888377772586485641459342621210866475884892600317"
"62345960769508849149662444156604419552086811989770240";
#pragma GCC diagnostic pop
double pos_inf = mkdbl(0x7FF0000000000000ULL);
double neg_inf = mkdbl(0xFFF0000000000000ULL);
double pos_nan = mkdbl(0x7FF8000000000000ULL);
double neg_nan = mkdbl(0xFFF8000000000000ULL);
double maxsub = mkdbl(0x000FFFFFFFFFFFFFULL);
char out[6000];
int rc;
golden("0x0p+0", "%a", 0.0);
golden("0x1p+0", "%a", 1.0);
golden("0x1.8p+0", "%a", 1.5);
golden("0x1p-1", "%a", 0.5);
golden("0x1.91eb851eb851fp+1", "%a", 3.14);
golden("0x1.fffffffffffffp+1023", "%a", DBL_MAX);
golden("0x2p+1023", "%.0a", DBL_MAX);
golden("0x2.0p+1023", "%.1a", DBL_MAX);
golden("0x0.0000000000001p-1022", "%a", DBL_TRUE_MIN);
golden("0x0.0p-1022", "%.1a", DBL_TRUE_MIN);
golden("0x0p-1022", "%.0a", DBL_TRUE_MIN);
golden("0x0.fffffffffffffp-1022", "%a", maxsub);
golden("0x1.0p-1022", "%.1a", maxsub);
golden("0x1p-1022", "%.0a", maxsub);
golden("0x0.0000p-1022", "%.4a", DBL_TRUE_MIN);
golden("0x1.81cd6e631f8a1p+13", "%a", 12345.6789);
golden("0x1.8000000000000p+0", "%.13a", 1.5);
golden("0x1.8000000000000000p+0", "%.16a", 1.5);
golden("0x2.0p+0", "%.1a", 0x1.fffffffffffffp+0);
golden("0x2p+0", "%.0a", 0x1.8p+0);
golden("0x1p+0", "%.0a", 0x1.4p+0);
golden("0x2p-1", "%.0a", 0x1.fffffffffffffp-1);
golden("0x1.000p+0", "%.3a", 1.0);
golden("0x1.p+0", "%#a", 1.0);
golden("0x2.p+0", "%#.0a", 1.5);
golden("0x0.p+0", "%#.0a", 0.0);
golden("0x0.p+0", "%#a", 0.0);
golden("0x0.000p+0", "%.3a", 0.0);
golden("0x0000000000001.8p+0", "%020a", 1.5);
golden("+0x000000000001.8p+0", "%+020a", 1.5);
golden("+0x1.8p+0", "%+a", 1.5);
golden(" 0x1.8p+0", "% a", 1.5);
golden("+0x1.8p+0 ", "%-+20a", 1.5);
golden("0x1.8p+0 ", "%-20a", 1.5);
golden("0x1.800p+0", "%.3a", 1.5);
golden("+0x1.800p+0", "%+08.3a", 1.5);
golden("0X1.8P+0", "%A", 1.5);
golden("0X1.91EB851EB851FP+1", "%A", 3.14);
golden("0X1.FFFFFFFFFFFFFP+1023", "%A", DBL_MAX);
golden("0X2P+0", "%.0A", 1.5);
golden("inf", "%a", pos_inf);
golden("-inf", "%a", neg_inf);
golden("nan", "%a", pos_nan);
golden("-nan", "%a", neg_nan);
golden("INF", "%A", pos_inf);
golden("NAN", "%A", pos_nan);
golden("inf", "%La", mkldbl(0x7FFF, 0, 0x8000000000000000ULL));
golden("-inf", "%La", mkldbl(0x7FFF, 1, 0x8000000000000000ULL));
golden("-nan", "%La", mkldbl(0x7FFF, 1, 0x0000000000000001ULL));
golden("0x8p-3", "%La", 1.0L);
golden("0xcp-3", "%La", 1.5L);
golden("0xcp-3", "%.0La", 0xcp-3L);
golden("0xc.0p-3", "%.1La", 0xcp-3L);
golden("0x8p-4", "%La", 0.5L);
golden("0xc.8f5c28f5c28f5c3p-2", "%La", 3.14L);
golden("0xf.fffffffffffffffp+16380", "%La", LDBL_MAX);
golden("0x1p+16384", "%.0La", LDBL_MAX);
golden("0x1.0p+16384", "%.1La", LDBL_MAX);
golden("0x8p-16385", "%La", LDBL_MIN);
golden("0x8.0p-16385", "%.1La", LDBL_MIN);
golden("0x0.000000000000001p-16385", "%La", LDBL_TRUE_MIN);
golden("0x0.0p-16385", "%.1La", LDBL_TRUE_MIN);
golden("0x0p-16385", "%.0La", LDBL_TRUE_MIN);
golden("0xc.p-3", "%#.0La", 1.5L);
golden("0x0.p+0", "%#.0La", 0.0L);
golden("-0xcp-3", "%La", -1.5L);
golden("-0x0p+0", "%.0La", -0.0L);
golden("0x0.0p+0", "%.1La", 0.0L);
golden("0xc.000p-3", "%.3La", 1.5L);
golden("0xc.000000000000000p-3", "%.15La", 1.5L);
golden("0x00000000000000cp-3", "%020La", 1.5L);
golden("+0x1p+16384", "%+.0La", LDBL_MAX);
golden("0x1p+4", "%.0La", 0xf.fffffffffffffffp+0L);
golden("0x1.0p+4", "%.1La", 0xf.fffffffffffffffp+0L);
golden("0XCP-3", "%LA", 1.5L);
golden("0XC.8F5C28F5C28F5C3P-2", "%LA", 3.14L);
golden("0xc.0e6b7318fc50481p+10", "%La", 12345.6789L);
golden("0xc.8p-3", "%.1La", 0xc.8p-3L);
golden("0xcp-3", "%.0La", 0xc.8p-3L);
golden("0xc.000000000000001p-3", "%.15La", 0xc.000000000000001p-3L);
golden("1.500000e+00", "%Le", 1.5L);
golden("1.500000", "%Lf", 1.5L);
golden("1.5", "%Lg", 1.5L);
golden("-0.000000e+00", "%Le", -0.0L);
golden("-0.000000", "%Lf", -0.0L);
golden("3.14159", "%Lg", 3.14159L);
golden("3.141590", "%Lf", 3.14159L);
golden("3.141590e+00", "%Le", 3.14159L);
golden("0.500000", "%Lf", 0.5L);
golden("1.000000", "%Lf", 1.0L);
golden("0.0001", "%Lg", 0.0001L);
golden("1.189731e+4932", "%Le", LDBL_MAX);
golden("0.000000", "%Lf", 0.0L);
golden("3.14159", "%.6Lg", 3.14159L);
golden("1.5", "%Lg", 1.5000001L);
rc = snprintf(out, sizeof out, "%.0Lf", LDBL_MAX);
check(rc == 4933 && strcmp(out, ldmax_dec) == 0,
"%.0Lf of LDBL_MAX prints all 4933 digits (no truncation)");
rc = snprintf(out, sizeof out, "%Lf", LDBL_MAX);
check(rc == 4940 && out[0] == '1' && strncmp(out, "11897314953572317650", 20) == 0 &&
out[4933] == '.' && out[4939] == '0',
"%Lf of LDBL_MAX fills the 4933-digit integer part");
}
int
main(void)
{
int_scenario();
length_scenario();
alt_scenario();
flag_scenario();
charstr_scenario();
star_scenario();
trunc_scenario();
sprintf_scenario();
dprintf_scenario();
file_scenario();
stdout_scenario();
#if VLIBC_LEVEL_GE(2)
asprintf_scenario();
#endif
hexfloat_scenario();
if (failures > 0)
{
say(2, "FAILED (");
say_dec(2, (unsigned long)failures);
say(2, " check(s))\n");
return 1;
}
say(1, "all printf tests passed\n");
return 0;
}