aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDag-Erling Smørgrav <des@FreeBSD.org>2023-08-28 15:32:23 +0000
committerDag-Erling Smørgrav <des@FreeBSD.org>2023-09-07 20:28:30 +0000
commitbae932999e0fa9e9a08d84ca992332c57e5b42be (patch)
tree79606decab963220775d3683bc078f4adc8ba960
parent70fbe797c0f6806d51708a9ef96ee02cb0c6d10d (diff)
downloadsrc-bae932999e0fa9e9a08d84ca992332c57e5b42be.tar.gz
src-bae932999e0fa9e9a08d84ca992332c57e5b42be.zip
libc: Implement N2630.
This adds formatted input/output of binary integer numbers to the printf(), scanf(), and strtol() families, including their wide-character counterparts. Reviewed by: imp, emaste Differential Revision: https://reviews.freebsd.org/D41511 (cherry picked from commit d9dc1603d6e48cca84cad3ebe859129131b8387c) libc: Add unit tests for N2630 and possible collateral damage. Reviewed by: imp, emaste Differential Revision: https://reviews.freebsd.org/D41512 (cherry picked from commit b9385720f34b536ef2568a642e8b1fad0450056f) libc: Document support for binary integers. Reviewed by: debdrup, emaste Differential Revision: https://reviews.freebsd.org/D41522 (cherry picked from commit 76edfabbecdec686a570b8e009d5ea4112f943e0) libc: Fix fixed-width case in the new integer parser. Fixes: d9dc1603d6e4 Differential Revision: https://reviews.freebsd.org/D41622 (cherry picked from commit aca3bd1602577591e5cd237c4bb0bb71b3be0c75) libc: Add a wide version of snprintf_test. Reviewed by: imp, emaste Differential Revision: https://reviews.freebsd.org/D41726 (cherry picked from commit 4ec9ee9912765ac4ca57353999caa92a23283d8e) libc: Suppress format checks on printf() / scanf() tests. Reviewed by: jrtc27, markj, emaste Differential Revision: https://reviews.freebsd.org/D41727 (cherry picked from commit 294bd2827e61a78041f6613f4b82235fcc454157) Approved by: re (gjb)
-rw-r--r--contrib/netbsd-tests/lib/libc/stdlib/t_strtol.c6
-rw-r--r--lib/libc/iconv/_strtol.h7
-rw-r--r--lib/libc/iconv/_strtoul.h7
-rw-r--r--lib/libc/locale/wcstoimax.c7
-rw-r--r--lib/libc/locale/wcstol.c7
-rw-r--r--lib/libc/locale/wcstoll.c7
-rw-r--r--lib/libc/locale/wcstoul.c7
-rw-r--r--lib/libc/locale/wcstoull.c7
-rw-r--r--lib/libc/locale/wcstoumax.c7
-rw-r--r--lib/libc/stdio/printf.334
-rw-r--r--lib/libc/stdio/printfcommon.h14
-rw-r--r--lib/libc/stdio/scanf.329
-rw-r--r--lib/libc/stdio/vfprintf.c13
-rw-r--r--lib/libc/stdio/vfscanf.c267
-rw-r--r--lib/libc/stdio/vfwprintf.c13
-rw-r--r--lib/libc/stdio/vfwscanf.c263
-rw-r--r--lib/libc/stdlib/strtoimax.c7
-rw-r--r--lib/libc/stdlib/strtol.34
-rw-r--r--lib/libc/stdlib/strtol.c7
-rw-r--r--lib/libc/stdlib/strtoll.c12
-rw-r--r--lib/libc/stdlib/strtoul.34
-rw-r--r--lib/libc/stdlib/strtoul.c7
-rw-r--r--lib/libc/stdlib/strtoull.c7
-rw-r--r--lib/libc/stdlib/strtoumax.c7
-rw-r--r--lib/libc/tests/stdio/Makefile12
-rw-r--r--lib/libc/tests/stdio/snprintf_test.c139
-rw-r--r--lib/libc/tests/stdio/sscanf_test.c266
-rw-r--r--lib/libc/tests/stdio/swprintf_test.c140
-rw-r--r--lib/libc/tests/stdio/swscanf_test.c267
29 files changed, 1315 insertions, 259 deletions
diff --git a/contrib/netbsd-tests/lib/libc/stdlib/t_strtol.c b/contrib/netbsd-tests/lib/libc/stdlib/t_strtol.c
index 54e190760656..d1027fcc7bb1 100644
--- a/contrib/netbsd-tests/lib/libc/stdlib/t_strtol.c
+++ b/contrib/netbsd-tests/lib/libc/stdlib/t_strtol.c
@@ -94,6 +94,12 @@ ATF_TC_BODY(strtol_base, tc)
{ "01234567", 342391, 0, NULL },
{ "0123456789", 123456789, 10, NULL },
{ "0x75bcd15", 123456789, 0, NULL },
+#ifdef __FreeBSD__
+ { "0x", 0, 0, "x" },
+ { "0b111010110111100110100010101", 123456789, 0, NULL },
+ { "0b0123", 1, 0, "23" },
+ { "0b", 0, 0, "b" },
+#endif
};
long long int lli;
diff --git a/lib/libc/iconv/_strtol.h b/lib/libc/iconv/_strtol.h
index d183edbe8c3a..94a13c56db98 100644
--- a/lib/libc/iconv/_strtol.h
+++ b/lib/libc/iconv/_strtol.h
@@ -91,6 +91,13 @@ _FUNCNAME(const char *nptr, char **endptr, int base)
s += 2;
base = 16;
}
+ if ((base == 0 || base == 2) &&
+ c == '0' && (*s == 'b' || *s == 'B') &&
+ (s[1] >= '0' && s[1] <= '1')) {
+ c = s[1];
+ s += 2;
+ base = 2;
+ }
if (base == 0)
base = (c == '0' ? 8 : 10);
diff --git a/lib/libc/iconv/_strtoul.h b/lib/libc/iconv/_strtoul.h
index eade72e9c2e6..4944e1fb06e0 100644
--- a/lib/libc/iconv/_strtoul.h
+++ b/lib/libc/iconv/_strtoul.h
@@ -87,6 +87,13 @@ _FUNCNAME(const char *nptr, char **endptr, int base)
s += 2;
base = 16;
}
+ if ((base == 0 || base == 2) &&
+ c == '0' && (*s == 'b' || *s == 'B') &&
+ (s[1] >= '0' && s[1] <= '1')) {
+ c = s[1];
+ s += 2;
+ base = 2;
+ }
if (base == 0)
base = (c == '0' ? 8 : 10);
diff --git a/lib/libc/locale/wcstoimax.c b/lib/libc/locale/wcstoimax.c
index 259faa2b011c..5ed949cd0531 100644
--- a/lib/libc/locale/wcstoimax.c
+++ b/lib/libc/locale/wcstoimax.c
@@ -86,6 +86,13 @@ wcstoimax_l(const wchar_t * __restrict nptr, wchar_t ** __restrict endptr,
s += 2;
base = 16;
}
+ if ((base == 0 || base == 2) &&
+ c == L'0' && (*s == L'b' || *s == L'B') &&
+ (s[1] >= L'0' && s[1] <= L'1')) {
+ c = s[1];
+ s += 2;
+ base = 2;
+ }
if (base == 0)
base = c == L'0' ? 8 : 10;
acc = any = 0;
diff --git a/lib/libc/locale/wcstol.c b/lib/libc/locale/wcstol.c
index b0b787384f39..1678b615ca1c 100644
--- a/lib/libc/locale/wcstol.c
+++ b/lib/libc/locale/wcstol.c
@@ -80,6 +80,13 @@ wcstol_l(const wchar_t * __restrict nptr, wchar_t ** __restrict endptr, int
s += 2;
base = 16;
}
+ if ((base == 0 || base == 2) &&
+ c == L'0' && (*s == L'b' || *s == L'B') &&
+ (s[1] >= L'0' && s[1] <= L'1')) {
+ c = s[1];
+ s += 2;
+ base = 2;
+ }
if (base == 0)
base = c == L'0' ? 8 : 10;
acc = any = 0;
diff --git a/lib/libc/locale/wcstoll.c b/lib/libc/locale/wcstoll.c
index ac07d6c6adbf..ef1e6ef58861 100644
--- a/lib/libc/locale/wcstoll.c
+++ b/lib/libc/locale/wcstoll.c
@@ -86,6 +86,13 @@ wcstoll_l(const wchar_t * __restrict nptr, wchar_t ** __restrict endptr,
s += 2;
base = 16;
}
+ if ((base == 0 || base == 2) &&
+ c == L'0' && (*s == L'b' || *s == L'B') &&
+ (s[1] >= L'0' && s[1] <= L'1')) {
+ c = s[1];
+ s += 2;
+ base = 2;
+ }
if (base == 0)
base = c == L'0' ? 8 : 10;
acc = any = 0;
diff --git a/lib/libc/locale/wcstoul.c b/lib/libc/locale/wcstoul.c
index 9f58db799c0e..2c9c8820b1f6 100644
--- a/lib/libc/locale/wcstoul.c
+++ b/lib/libc/locale/wcstoul.c
@@ -80,6 +80,13 @@ wcstoul_l(const wchar_t * __restrict nptr, wchar_t ** __restrict endptr,
s += 2;
base = 16;
}
+ if ((base == 0 || base == 2) &&
+ c == L'0' && (*s == L'b' || *s == L'B') &&
+ (s[1] >= L'0' && s[1] <= L'1')) {
+ c = s[1];
+ s += 2;
+ base = 2;
+ }
if (base == 0)
base = c == L'0' ? 8 : 10;
acc = any = 0;
diff --git a/lib/libc/locale/wcstoull.c b/lib/libc/locale/wcstoull.c
index cbc7253f884d..692eb90eef6b 100644
--- a/lib/libc/locale/wcstoull.c
+++ b/lib/libc/locale/wcstoull.c
@@ -86,6 +86,13 @@ wcstoull_l(const wchar_t * __restrict nptr, wchar_t ** __restrict endptr,
s += 2;
base = 16;
}
+ if ((base == 0 || base == 2) &&
+ c == L'0' && (*s == L'b' || *s == L'B') &&
+ (s[1] >= L'0' && s[1] <= L'1')) {
+ c = s[1];
+ s += 2;
+ base = 2;
+ }
if (base == 0)
base = c == L'0' ? 8 : 10;
acc = any = 0;
diff --git a/lib/libc/locale/wcstoumax.c b/lib/libc/locale/wcstoumax.c
index 4380cccf2424..c4f2ec3aaf41 100644
--- a/lib/libc/locale/wcstoumax.c
+++ b/lib/libc/locale/wcstoumax.c
@@ -86,6 +86,13 @@ wcstoumax_l(const wchar_t * __restrict nptr, wchar_t ** __restrict endptr,
s += 2;
base = 16;
}
+ if ((base == 0 || base == 2) &&
+ c == L'0' && (*s == L'b' || *s == L'B') &&
+ (s[1] >= L'0' && s[1] <= L'1')) {
+ c = s[1];
+ s += 2;
+ base = 2;
+ }
if (base == 0)
base = c == L'0' ? 8 : 10;
acc = any = 0;
diff --git a/lib/libc/stdio/printf.3 b/lib/libc/stdio/printf.3
index 3e5c6ca23511..110851e2a421 100644
--- a/lib/libc/stdio/printf.3
+++ b/lib/libc/stdio/printf.3
@@ -31,7 +31,7 @@
.\"
.\" @(#)printf.3 8.1 (Berkeley) 6/4/93
.\"
-.Dd May 22, 2018
+.Dd August 21, 2023
.Dt PRINTF 3
.Os
.Sh NAME
@@ -212,6 +212,17 @@ and
.Cm u
conversions, this option has no effect.
For
+.Cm b
+and
+.Cm B
+conversions, a non-zero result has the string
+.Ql 0b
+(or
+.Ql 0B
+for
+.Cm B
+conversions) prepended to it.
+For
.Cm o
conversions, the precision of the number is increased to force the first
character of the output string to a zero.
@@ -245,7 +256,7 @@ For all conversions except
.Cm n ,
the converted value is padded on the left with zeros rather than blanks.
If a precision is given with a numeric conversion
-.Cm ( d , i , o , u , i , x ,
+.Cm ( b , B , d , i , o , u , i , x ,
and
.Cm X ) ,
the
@@ -301,7 +312,7 @@ followed by an
optional digit string.
If the digit string is omitted, the precision is taken as zero.
This gives the minimum number of digits to appear for
-.Cm d , i , o , u , x ,
+.Cm b , B , d , i , o , u , x ,
and
.Cm X
conversions, the number of digits to appear after the decimal-point for
@@ -319,12 +330,12 @@ conversions.
.It
An optional length modifier, that specifies the size of the argument.
The following length modifiers are valid for the
-.Cm d , i , n , o , u , x ,
+.Cm b , B , d , i , n , o , u , x ,
or
.Cm X
conversion:
.Bl -column ".Cm q Em (deprecated)" ".Vt signed char" ".Vt unsigned long long" ".Vt long long *"
-.It Sy Modifier Ta Cm d , i Ta Cm o , u , x , X Ta Cm n
+.It Sy Modifier Ta Cm d , i Ta Cm b , B , o , u , x , X Ta Cm n
.It Cm hh Ta Vt "signed char" Ta Vt "unsigned char" Ta Vt "signed char *"
.It Cm h Ta Vt short Ta Vt "unsigned short" Ta Vt "short *"
.It Cm l No (ell) Ta Vt long Ta Vt "unsigned long" Ta Vt "long *"
@@ -339,7 +350,7 @@ Note:
the
.Cm t
modifier, when applied to a
-.Cm o , u , x ,
+.Cm b , B , o , u , x ,
or
.Cm X
conversion, indicates that the argument is of an unsigned type
@@ -403,11 +414,16 @@ If a single format directive mixes positional
and non-positional arguments, the results are undefined.
.Pp
The conversion specifiers and their meanings are:
-.Bl -tag -width ".Cm diouxX"
-.It Cm diouxX
+.Bl -tag -width ".Cm bBdiouxX"
+.It Cm bBdiouxX
The
.Vt int
-(or appropriate variant) argument is converted to signed decimal
+(or appropriate variant) argument is converted to
+unsigned binary
+.Cm ( b
+and
+.Cm B ) ,
+signed decimal
.Cm ( d
and
.Cm i ) ,
diff --git a/lib/libc/stdio/printfcommon.h b/lib/libc/stdio/printfcommon.h
index ac5aed0a5fcd..411b778dc234 100644
--- a/lib/libc/stdio/printfcommon.h
+++ b/lib/libc/stdio/printfcommon.h
@@ -194,6 +194,13 @@ __ultoa(u_long val, CHAR *endp, int base, int octzero, const char *xdigs)
} while (sval != 0);
break;
+ case 2:
+ do {
+ *--cp = to_char(val & 1);
+ val >>= 1;
+ } while (val);
+ break;
+
case 8:
do {
*--cp = to_char(val & 7);
@@ -244,6 +251,13 @@ __ujtoa(uintmax_t val, CHAR *endp, int base, int octzero, const char *xdigs)
} while (sval != 0);
break;
+ case 2:
+ do {
+ *--cp = to_char(val & 1);
+ val >>= 1;
+ } while (val);
+ break;
+
case 8:
do {
*--cp = to_char(val & 7);
diff --git a/lib/libc/stdio/scanf.3 b/lib/libc/stdio/scanf.3
index b1c50e10a795..6cefdb133983 100644
--- a/lib/libc/stdio/scanf.3
+++ b/lib/libc/stdio/scanf.3
@@ -31,7 +31,7 @@
.\"
.\" @(#)scanf.3 8.2 (Berkeley) 12/11/93
.\"
-.Dd April 2, 2022
+.Dd August 21, 2023
.Dt SCANF 3
.Os
.Sh NAME
@@ -141,7 +141,7 @@ The conversion that follows occurs as usual, but no pointer is used;
the result of the conversion is simply discarded.
.It Cm hh
Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
or
.Cm n
and the next pointer is a pointer to a
@@ -150,7 +150,7 @@ and the next pointer is a pointer to a
.Vt int ) .
.It Cm h
Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
or
.Cm n
and the next pointer is a pointer to a
@@ -159,7 +159,7 @@ and the next pointer is a pointer to a
.Vt int ) .
.It Cm l No (ell)
Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
or
.Cm n
and the next pointer is a pointer to a
@@ -185,7 +185,7 @@ and the next pointer is a pointer to an array of
.Vt char ) .
.It Cm ll No (ell ell)
Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
or
.Cm n
and the next pointer is a pointer to a
@@ -201,7 +201,7 @@ and the next pointer is a pointer to
.Vt "long double" .
.It Cm j
Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
or
.Cm n
and the next pointer is a pointer to a
@@ -210,7 +210,7 @@ and the next pointer is a pointer to a
.Vt int ) .
.It Cm t
Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
or
.Cm n
and the next pointer is a pointer to a
@@ -219,7 +219,7 @@ and the next pointer is a pointer to a
.Vt int ) .
.It Cm z
Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
or
.Cm n
and the next pointer is a pointer to a
@@ -229,7 +229,7 @@ and the next pointer is a pointer to a
.It Cm q
(deprecated.)
Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
or
.Cm n
and the next pointer is a pointer to a
@@ -273,6 +273,10 @@ matches a single input
.Ql %
character.
No conversion is done, and assignment does not occur.
+.It Cm b , B
+Matches an optionally signed binary integer;
+the next pointer must be a pointer to
+.Vt "unsigned int" .
.It Cm d
Matches an optionally signed decimal integer;
the next pointer must be a pointer to
@@ -281,7 +285,12 @@ the next pointer must be a pointer to
Matches an optionally signed integer;
the next pointer must be a pointer to
.Vt int .
-The integer is read in base 16 if it begins
+The integer is read
+in base 2 if it begins with
+.Ql 0b
+or
+.Ql 0B ,
+in base 16 if it begins
with
.Ql 0x
or
diff --git a/lib/libc/stdio/vfprintf.c b/lib/libc/stdio/vfprintf.c
index ad655c5d78d4..5e5a9b5e31c1 100644
--- a/lib/libc/stdio/vfprintf.c
+++ b/lib/libc/stdio/vfprintf.c
@@ -613,6 +613,19 @@ reswitch: switch (ch) {
case 'z':
flags |= SIZET;
goto rflag;
+ case 'B':
+ case 'b':
+ if (flags & INTMAX_SIZE)
+ ujval = UJARG();
+ else
+ ulval = UARG();
+ base = 2;
+ /* leading 0b/B only if non-zero */
+ if (flags & ALT &&
+ (flags & INTMAX_SIZE ? ujval != 0 : ulval != 0))
+ ox[1] = ch;
+ goto nosign;
+ break;
case 'C':
flags |= LONGINT;
/*FALLTHROUGH*/
diff --git a/lib/libc/stdio/vfscanf.c b/lib/libc/stdio/vfscanf.c
index cc2e1e428321..9727c9e70c34 100644
--- a/lib/libc/stdio/vfscanf.c
+++ b/lib/libc/stdio/vfscanf.c
@@ -6,6 +6,8 @@
*
* Copyright (c) 2011 The FreeBSD Foundation
*
+ * Copyright (c) 2023 Dag-Erling Smørgrav
+ *
* Portions of this software were developed by David Chisnall
* under sponsorship from the FreeBSD Foundation.
*
@@ -81,16 +83,6 @@ static char sccsid[] = "@(#)vfscanf.c 8.1 (Berkeley) 6/4/93";
#define UNSIGNED 0x8000 /* %[oupxX] conversions */
/*
- * The following are used in integral conversions only:
- * SIGNOK, NDIGITS, PFXOK, and NZDIGITS
- */
-#define SIGNOK 0x40 /* +/- is (still) legal */
-#define NDIGITS 0x80 /* no digits detected */
-#define PFXOK 0x100 /* 0x prefix is (still) legal */
-#define NZDIGITS 0x200 /* no zero digits detected */
-#define HAVESIGN 0x10000 /* sign detected */
-
-/*
* Conversion types.
*/
#define CT_CHAR 0 /* %c conversion */
@@ -307,129 +299,160 @@ convert_wstring(FILE *fp, wchar_t *wcp, int width, locale_t locale)
return (n);
}
+enum parseint_state {
+ begin,
+ havesign,
+ havezero,
+ haveprefix,
+ any,
+};
+
+static __inline int
+parseint_fsm(int c, enum parseint_state *state, int *base)
+{
+ switch (c) {
+ case '+':
+ case '-':
+ if (*state == begin) {
+ *state = havesign;
+ return 1;
+ }
+ break;
+ case '0':
+ if (*state == begin || *state == havesign) {
+ *state = havezero;
+ } else {
+ *state = any;
+ }
+ return 1;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ if (*state == havezero && *base == 0) {
+ *base = 8;
+ }
+ /* FALL THROUGH */
+ case '8':
+ case '9':
+ if (*state == begin ||
+ *state == havesign) {
+ if (*base == 0) {
+ *base = 10;
+ }
+ }
+ if (*state == begin ||
+ *state == havesign ||
+ *state == havezero ||
+ *state == haveprefix ||
+ *state == any) {
+ if (*base > c - '0') {
+ *state = any;
+ return 1;
+ }
+ }
+ break;
+ case 'b':
+ if (*state == havezero) {
+ if (*base == 0 || *base == 2) {
+ *state = haveprefix;
+ *base = 2;
+ return 1;
+ }
+ }
+ /* FALL THROUGH */
+ case 'a':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ if (*state == begin ||
+ *state == havesign ||
+ *state == havezero ||
+ *state == haveprefix ||
+ *state == any) {
+ if (*base > c - 'a' + 10) {
+ *state = any;
+ return 1;
+ }
+ }
+ break;
+ case 'B':
+ if (*state == havezero) {
+ if (*base == 0 || *base == 2) {
+ *state = haveprefix;
+ *base = 2;
+ return 1;
+ }
+ }
+ /* FALL THROUGH */
+ case 'A':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ if (*state == begin ||
+ *state == havesign ||
+ *state == havezero ||
+ *state == haveprefix ||
+ *state == any) {
+ if (*base > c - 'A' + 10) {
+ *state = any;
+ return 1;
+ }
+ }
+ break;
+ case 'x':
+ case 'X':
+ if (*state == havezero) {
+ if (*base == 0 || *base == 16) {
+ *state = haveprefix;
+ *base = 16;
+ return 1;
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
/*
- * Read an integer, storing it in buf. The only relevant bit in the
- * flags argument is PFXOK.
+ * Read an integer, storing it in buf.
*
* Return 0 on a match failure, and the number of characters read
* otherwise.
*/
static __inline int
-parseint(FILE *fp, char * __restrict buf, int width, int base, int flags)
+parseint(FILE *fp, char * __restrict buf, int width, int base)
{
- /* `basefix' is used to avoid `if' tests */
- static const short basefix[17] =
- { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
+ enum parseint_state state = begin;
char *p;
int c;
- flags |= SIGNOK | NDIGITS | NZDIGITS;
for (p = buf; width; width--) {
- c = *fp->_p;
- /*
- * Switch on the character; `goto ok' if we accept it
- * as a part of number.
- */
- switch (c) {
-
- /*
- * The digit 0 is always legal, but is special. For
- * %i conversions, if no digits (zero or nonzero) have
- * been scanned (only signs), we will have base==0.
- * In that case, we should set it to 8 and enable 0x
- * prefixing. Also, if we have not scanned zero
- * digits before this, do not turn off prefixing
- * (someone else will turn it off if we have scanned
- * any nonzero digits).
- */
- case '0':
- if (base == 0) {
- base = 8;
- flags |= PFXOK;
- }
- if (flags & NZDIGITS)
- flags &= ~(SIGNOK|NZDIGITS|NDIGITS);
- else
- flags &= ~(SIGNOK|PFXOK|NDIGITS);
- goto ok;
-
- /* 1 through 7 always legal */
- case '1': case '2': case '3':
- case '4': case '5': case '6': case '7':
- base = basefix[base];
- flags &= ~(SIGNOK | PFXOK | NDIGITS);
- goto ok;
-
- /* digits 8 and 9 ok iff decimal or hex */
- case '8': case '9':
- base = basefix[base];
- if (base <= 8)
- break; /* not legal here */
- flags &= ~(SIGNOK | PFXOK | NDIGITS);
- goto ok;
-
- /* letters ok iff hex */
- case 'A': case 'B': case 'C':
- case 'D': case 'E': case 'F':
- case 'a': case 'b': case 'c':
- case 'd': case 'e': case 'f':
- /* no need to fix base here */
- if (base <= 10)
- break; /* not legal here */
- flags &= ~(SIGNOK | PFXOK | NDIGITS);
- goto ok;
-
- /* sign ok only as first character */
- case '+': case '-':
- if (flags & SIGNOK) {
- flags &= ~SIGNOK;
- flags |= HAVESIGN;
- goto ok;
- }
+ c = __sgetc(fp);
+ if (c == EOF)
break;
-
- /*
- * x ok iff flag still set & 2nd char (or 3rd char if
- * we have a sign).
- */
- case 'x': case 'X':
- if (flags & PFXOK && p ==
- buf + 1 + !!(flags & HAVESIGN)) {
- base = 16; /* if %i */
- flags &= ~PFXOK;
- goto ok;
- }
+ if (!parseint_fsm(c, &state, &base))
break;
- }
-
- /*
- * If we got here, c is not a legal character for a
- * number. Stop accumulating digits.
- */
- break;
- ok:
- /*
- * c is legal: store it and look at the next.
- */
*p++ = c;
- if (--fp->_r > 0)
- fp->_p++;
- else if (__srefill(fp))
- break; /* EOF */
}
/*
- * If we had only a sign, it is no good; push back the sign.
- * If the number ends in `x', it was [sign] '0' 'x', so push
- * back the x and treat it as [sign] '0'.
+ * If we only had a sign, push it back. If we only had a 0b or 0x
+ * prefix (possibly preceded by a sign), we view it as "0" and
+ * push back the letter. In all other cases, if we stopped
+ * because we read a non-number character, push it back.
*/
- if (flags & NDIGITS) {
- if (p > buf)
- (void) __ungetc(*(u_char *)--p, fp);
- return (0);
- }
- c = ((u_char *)p)[-1];
- if (c == 'x' || c == 'X') {
- --p;
+ if (state == havesign) {
+ p--;
+ (void) __ungetc(*(u_char *)p, fp);
+ } else if (state == haveprefix) {
+ p--;
+ (void) __ungetc(c, fp);
+ } else if (width && c != EOF) {
(void) __ungetc(c, fp);
}
return (p - buf);
@@ -554,6 +577,13 @@ literal:
/*
* Conversions.
*/
+ case 'B':
+ case 'b':
+ c = CT_INT;
+ flags |= UNSIGNED;
+ base = 2;
+ break;
+
case 'd':
c = CT_INT;
base = 10;
@@ -578,7 +608,6 @@ literal:
case 'X':
case 'x':
- flags |= PFXOK; /* enable 0x prefixing */
c = CT_INT;
flags |= UNSIGNED;
base = 16;
@@ -613,7 +642,7 @@ literal:
break;
case 'p': /* pointer format is like hex */
- flags |= POINTER | PFXOK;
+ flags |= POINTER;
c = CT_INT; /* assumes sizeof(uintmax_t) */
flags |= UNSIGNED; /* >= sizeof(uintptr_t) */
base = 16;
@@ -738,7 +767,7 @@ literal:
width = sizeof(buf) - 2;
width++;
#endif
- nr = parseint(fp, buf, width, base, flags);
+ nr = parseint(fp, buf, width, base);
if (nr == 0)
goto match_failure;
if ((flags & SUPPRESS) == 0) {
diff --git a/lib/libc/stdio/vfwprintf.c b/lib/libc/stdio/vfwprintf.c
index fc681e8d0575..259a86467ea7 100644
--- a/lib/libc/stdio/vfwprintf.c
+++ b/lib/libc/stdio/vfwprintf.c
@@ -684,6 +684,19 @@ reswitch: switch (ch) {
case 'z':
flags |= SIZET;
goto rflag;
+ case 'B':
+ case 'b':
+ if (flags & INTMAX_SIZE)
+ ujval = UJARG();
+ else
+ ulval = UARG();
+ base = 2;
+ /* leading 0b/B only if non-zero */
+ if (flags & ALT &&
+ (flags & INTMAX_SIZE ? ujval != 0 : ulval != 0))
+ ox[1] = ch;
+ goto nosign;
+ break;
case 'C':
flags |= LONGINT;
/*FALLTHROUGH*/
diff --git a/lib/libc/stdio/vfwscanf.c b/lib/libc/stdio/vfwscanf.c
index 1a28ff665247..b03c9dba0699 100644
--- a/lib/libc/stdio/vfwscanf.c
+++ b/lib/libc/stdio/vfwscanf.c
@@ -9,6 +9,8 @@
*
* Copyright (c) 2011 The FreeBSD Foundation
*
+ * Copyright (c) 2023 Dag-Erling Smørgrav
+ *
* Portions of this software were developed by David Chisnall
* under sponsorship from the FreeBSD Foundation.
*
@@ -79,16 +81,6 @@ static char sccsid[] = "@(#)vfscanf.c 8.1 (Berkeley) 6/4/93";
#define UNSIGNED 0x8000 /* %[oupxX] conversions */
/*
- * The following are used in integral conversions only:
- * SIGNOK, NDIGITS, PFXOK, and NZDIGITS
- */
-#define SIGNOK 0x40 /* +/- is (still) legal */
-#define NDIGITS 0x80 /* no digits detected */
-#define PFXOK 0x100 /* 0x prefix is (still) legal */
-#define NZDIGITS 0x200 /* no zero digits detected */
-#define HAVESIGN 0x10000 /* sign detected */
-
-/*
* Conversion types.
*/
#define CT_CHAR 0 /* %c conversion */
@@ -289,128 +281,161 @@ convert_wstring(FILE *fp, wchar_t *wcp, int width, locale_t locale)
return (nread);
}
+enum parseint_state {
+ begin,
+ havesign,
+ havezero,
+ haveprefix,
+ any,
+};
+
+static __inline int
+parseint_fsm(wchar_t c, enum parseint_state *state, int *base)
+{
+ switch (c) {
+ case '+':
+ case '-':
+ if (*state == begin) {
+ *state = havesign;
+ return 1;
+ }
+ break;
+ case '0':
+ if (*state == begin || *state == havesign) {
+ *state = havezero;
+ } else {
+ *state = any;
+ }
+ return 1;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ if (*state == havezero && *base == 0) {
+ *base = 8;
+ }
+ /* FALL THROUGH */
+ case '8':
+ case '9':
+ if (*state == begin ||
+ *state == havesign) {
+ if (*base == 0) {
+ *base = 10;
+ }
+ }
+ if (*state == begin ||
+ *state == havesign ||
+ *state == havezero ||
+ *state == haveprefix ||
+ *state == any) {
+ if (*base > c - '0') {
+ *state = any;
+ return 1;
+ }
+ }
+ break;
+ case 'b':
+ if (*state == havezero) {
+ if (*base == 0 || *base == 2) {
+ *state = haveprefix;
+ *base = 2;
+ return 1;
+ }
+ }
+ /* FALL THROUGH */
+ case 'a':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ if (*state == begin ||
+ *state == havesign ||
+ *state == havezero ||
+ *state == haveprefix ||
+ *state == any) {
+ if (*base > c - 'a' + 10) {
+ *state = any;
+ return 1;
+ }
+ }
+ break;
+ case 'B':
+ if (*state == havezero) {
+ if (*base == 0 || *base == 2) {
+ *state = haveprefix;
+ *base = 2;
+ return 1;
+ }
+ }
+ /* FALL THROUGH */
+ case 'A':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ if (*state == begin ||
+ *state == havesign ||
+ *state == havezero ||
+ *state == haveprefix ||
+ *state == any) {
+ if (*base > c - 'A' + 10) {
+ *state = any;
+ return 1;
+ }
+ }
+ break;
+ case 'x':
+ case 'X':
+ if (*state == havezero) {
+ if (*base == 0 || *base == 16) {
+ *state = haveprefix;
+ *base = 16;
+ return 1;
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
/*
- * Read an integer, storing it in buf. The only relevant bit in the
- * flags argument is PFXOK.
+ * Read an integer, storing it in buf.
*
* Return 0 on a match failure, and the number of characters read
* otherwise.
*/
static __inline int
-parseint(FILE *fp, wchar_t *buf, int width, int base, int flags,
+parseint(FILE *fp, wchar_t * __restrict buf, int width, int base,
locale_t locale)
{
- /* `basefix' is used to avoid `if' tests */
- static const short basefix[17] =
- { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
+ enum parseint_state state = begin;
wchar_t *wcp;
int c;
- flags |= SIGNOK | NDIGITS | NZDIGITS;
for (wcp = buf; width; width--) {
c = __fgetwc(fp, locale);
- /*
- * Switch on the character; `goto ok' if we accept it
- * as a part of number.
- */
- switch (c) {
-
- /*
- * The digit 0 is always legal, but is special. For
- * %i conversions, if no digits (zero or nonzero) have
- * been scanned (only signs), we will have base==0.
- * In that case, we should set it to 8 and enable 0x
- * prefixing. Also, if we have not scanned zero
- * digits before this, do not turn off prefixing
- * (someone else will turn it off if we have scanned
- * any nonzero digits).
- */
- case '0':
- if (base == 0) {
- base = 8;
- flags |= PFXOK;
- }
- if (flags & NZDIGITS)
- flags &= ~(SIGNOK|NZDIGITS|NDIGITS);
- else
- flags &= ~(SIGNOK|PFXOK|NDIGITS);
- goto ok;
-
- /* 1 through 7 always legal */
- case '1': case '2': case '3':
- case '4': case '5': case '6': case '7':
- base = basefix[base];
- flags &= ~(SIGNOK | PFXOK | NDIGITS);
- goto ok;
-
- /* digits 8 and 9 ok iff decimal or hex */
- case '8': case '9':
- base = basefix[base];
- if (base <= 8)
- break; /* not legal here */
- flags &= ~(SIGNOK | PFXOK | NDIGITS);
- goto ok;
-
- /* letters ok iff hex */
- case 'A': case 'B': case 'C':
- case 'D': case 'E': case 'F':
- case 'a': case 'b': case 'c':
- case 'd': case 'e': case 'f':
- /* no need to fix base here */
- if (base <= 10)
- break; /* not legal here */
- flags &= ~(SIGNOK | PFXOK | NDIGITS);
- goto ok;
-
- /* sign ok only as first character */
- case '+': case '-':
- if (flags & SIGNOK) {
- flags &= ~SIGNOK;
- flags |= HAVESIGN;
- goto ok;
- }
+ if (c == WEOF)
break;
-
- /*
- * x ok iff flag still set & 2nd char (or 3rd char if
- * we have a sign).
- */
- case 'x': case 'X':
- if (flags & PFXOK && wcp ==
- buf + 1 + !!(flags & HAVESIGN)) {
- base = 16; /* if %i */
- flags &= ~PFXOK;
- goto ok;
- }
+ if (!parseint_fsm(c, &state, &base))
break;
- }
-
- /*
- * If we got here, c is not a legal character for a
- * number. Stop accumulating digits.
- */
- if (c != WEOF)
- __ungetwc(c, fp, locale);
- break;
- ok:
- /*
- * c is legal: store it and look at the next.
- */
*wcp++ = (wchar_t)c;
}
/*
- * If we had only a sign, it is no good; push back the sign.
- * If the number ends in `x', it was [sign] '0' 'x', so push
- * back the x and treat it as [sign] '0'.
+ * If we only had a sign, push it back. If we only had a 0b or 0x
+ * prefix (possibly preceded by a sign), we view it as "0" and
+ * push back the letter. In all other cases, if we stopped
+ * because we read a non-number character, push it back.
*/
- if (flags & NDIGITS) {
- if (wcp > buf)
- __ungetwc(*--wcp, fp, locale);
- return (0);
- }
- c = wcp[-1];
- if (c == 'x' || c == 'X') {
- --wcp;
+ if (state == havesign) {
+ wcp--;
+ __ungetwc(*wcp, fp, locale);
+ } else if (state == haveprefix) {
+ wcp--;
+ __ungetwc(c, fp, locale);
+ } else if (width && c != WEOF) {
__ungetwc(c, fp, locale);
}
return (wcp - buf);
@@ -536,6 +561,13 @@ literal:
/*
* Conversions.
*/
+ case 'B':
+ case 'b':
+ c = CT_INT;
+ flags |= UNSIGNED;
+ base = 2;
+ break;
+
case 'd':
c = CT_INT;
base = 10;
@@ -560,7 +592,6 @@ literal:
case 'X':
case 'x':
- flags |= PFXOK; /* enable 0x prefixing */
c = CT_INT;
flags |= UNSIGNED;
base = 16;
@@ -606,7 +637,7 @@ literal:
break;
case 'p': /* pointer format is like hex */
- flags |= POINTER | PFXOK;
+ flags |= POINTER;
c = CT_INT; /* assumes sizeof(uintmax_t) */
flags |= UNSIGNED; /* >= sizeof(uintptr_t) */
base = 16;
@@ -716,7 +747,7 @@ literal:
sizeof(*buf) - 1)
width = sizeof(buf) / sizeof(*buf) - 1;
- nr = parseint(fp, buf, width, base, flags, locale);
+ nr = parseint(fp, buf, width, base, locale);
if (nr == 0)
goto match_failure;
if ((flags & SUPPRESS) == 0) {
diff --git a/lib/libc/stdlib/strtoimax.c b/lib/libc/stdlib/strtoimax.c
index 894d801940fd..5309b7d4305c 100644
--- a/lib/libc/stdlib/strtoimax.c
+++ b/lib/libc/stdlib/strtoimax.c
@@ -87,6 +87,13 @@ strtoimax_l(const char * __restrict nptr, char ** __restrict endptr, int base,
s += 2;
base = 16;
}
+ if ((base == 0 || base == 2) &&
+ c == '0' && (*s == 'b' || *s == 'B') &&
+ (s[1] >= '0' && s[1] <= '1')) {
+ c = s[1];
+ s += 2;
+ base = 2;
+ }
if (base == 0)
base = c == '0' ? 8 : 10;
acc = any = 0;
diff --git a/lib/libc/stdlib/strtol.3 b/lib/libc/stdlib/strtol.3
index 9656ba546915..e2c5ff7ae3cb 100644
--- a/lib/libc/stdlib/strtol.3
+++ b/lib/libc/stdlib/strtol.3
@@ -31,7 +31,7 @@
.\"
.\" @(#)strtol.3 8.1 (Berkeley) 6/4/93
.\"
-.Dd November 28, 2001
+.Dd August 21, 2023
.Dt STRTOL 3
.Os
.Sh NAME
@@ -108,6 +108,8 @@ If
.Fa base
is zero or 16,
the string may then include a
+.Dq Li 0b
+prefix, and the number will be read in base 2; or it may include a
.Dq Li 0x
prefix,
and the number will be read in base 16; otherwise, a zero
diff --git a/lib/libc/stdlib/strtol.c b/lib/libc/stdlib/strtol.c
index 360bb7efc8be..1ca95918ef12 100644
--- a/lib/libc/stdlib/strtol.c
+++ b/lib/libc/stdlib/strtol.c
@@ -87,6 +87,13 @@ strtol_l(const char * __restrict nptr, char ** __restrict endptr, int base,
s += 2;
base = 16;
}
+ if ((base == 0 || base == 2) &&
+ c == '0' && (*s == 'b' || *s == 'B') &&
+ (s[1] >= '0' && s[1] <= '1')) {
+ c = s[1];
+ s += 2;
+ base = 2;
+ }
if (base == 0)
base = c == '0' ? 8 : 10;
acc = any = 0;
diff --git a/lib/libc/stdlib/strtoll.c b/lib/libc/stdlib/strtoll.c
index 51a523e51fb8..6845776c5f03 100644
--- a/lib/libc/stdlib/strtoll.c
+++ b/lib/libc/stdlib/strtoll.c
@@ -63,8 +63,9 @@ strtoll_l(const char * __restrict nptr, char ** __restrict endptr, int base,
/*
* Skip white space and pick up leading +/- sign if any.
- * If base is 0, allow 0x for hex and 0 for octal, else
- * assume decimal; if base is already 16, allow 0x.
+ * If base is 0, allow 0b for binary, 0x for hex, and 0 for
+ * octal, else assume decimal; if base is already 2, allow
+ * 0b; if base is already 16, allow 0x.
*/
s = nptr;
do {
@@ -87,6 +88,13 @@ strtoll_l(const char * __restrict nptr, char ** __restrict endptr, int base,
s += 2;
base = 16;
}
+ if ((base == 0 || base == 2) &&
+ c == '0' && (*s == 'b' || *s == 'B') &&
+ (s[1] >= '0' && s[1] <= '1')) {
+ c = s[1];
+ s += 2;
+ base = 2;
+ }
if (base == 0)
base = c == '0' ? 8 : 10;
acc = any = 0;
diff --git a/lib/libc/stdlib/strtoul.3 b/lib/libc/stdlib/strtoul.3
index e05d7a7728cd..41b3b2c578bc 100644
--- a/lib/libc/stdlib/strtoul.3
+++ b/lib/libc/stdlib/strtoul.3
@@ -31,7 +31,7 @@
.\"
.\" @(#)strtoul.3 8.1 (Berkeley) 6/4/93
.\"
-.Dd November 28, 2001
+.Dd August 21, 2023
.Dt STRTOUL 3
.Os
.Sh NAME
@@ -108,6 +108,8 @@ If
.Fa base
is zero or 16,
the string may then include a
+.Dq Li 0b
+prefix, and the number will be read in base 2; or it may include a
.Dq Li 0x
prefix,
and the number will be read in base 16; otherwise, a zero
diff --git a/lib/libc/stdlib/strtoul.c b/lib/libc/stdlib/strtoul.c
index 133d4c9b4695..c0dbd0c9fdac 100644
--- a/lib/libc/stdlib/strtoul.c
+++ b/lib/libc/stdlib/strtoul.c
@@ -84,6 +84,13 @@ strtoul_l(const char * __restrict nptr, char ** __restrict endptr, int base, loc
s += 2;
base = 16;
}
+ if ((base == 0 || base == 2) &&
+ c == '0' && (*s == 'b' || *s == 'B') &&
+ (s[1] >= '0' && s[1] <= '1')) {
+ c = s[1];
+ s += 2;
+ base = 2;
+ }
if (base == 0)
base = c == '0' ? 8 : 10;
acc = any = 0;
diff --git a/lib/libc/stdlib/strtoull.c b/lib/libc/stdlib/strtoull.c
index 464c37d49ef3..fe44a9e01c05 100644
--- a/lib/libc/stdlib/strtoull.c
+++ b/lib/libc/stdlib/strtoull.c
@@ -85,6 +85,13 @@ strtoull_l(const char * __restrict nptr, char ** __restrict endptr, int base,
s += 2;
base = 16;
}
+ if ((base == 0 || base == 2) &&
+ c == '0' && (*s == 'b' || *s == 'B') &&
+ (s[1] >= '0' && s[1] <= '1')) {
+ c = s[1];
+ s += 2;
+ base = 2;
+ }
if (base == 0)
base = c == '0' ? 8 : 10;
acc = any = 0;
diff --git a/lib/libc/stdlib/strtoumax.c b/lib/libc/stdlib/strtoumax.c
index af02b30254d6..08140be38845 100644
--- a/lib/libc/stdlib/strtoumax.c
+++ b/lib/libc/stdlib/strtoumax.c
@@ -85,6 +85,13 @@ strtoumax_l(const char * __restrict nptr, char ** __restrict endptr, int base,
s += 2;
base = 16;
}
+ if ((base == 0 || base == 2) &&
+ c == '0' && (*s == 'b' || *s == 'B') &&
+ (s[1] >= '0' && s[1] <= '1')) {
+ c = s[1];
+ s += 2;
+ base = 2;
+ }
if (base == 0)
base = c == '0' ? 8 : 10;
acc = any = 0;
diff --git a/lib/libc/tests/stdio/Makefile b/lib/libc/tests/stdio/Makefile
index 12ae44855da5..7716a60c0e6b 100644
--- a/lib/libc/tests/stdio/Makefile
+++ b/lib/libc/tests/stdio/Makefile
@@ -16,6 +16,10 @@ ATF_TESTS_C+= print_positional_test
ATF_TESTS_C+= printbasic_test
ATF_TESTS_C+= printfloat_test
ATF_TESTS_C+= scanfloat_test
+ATF_TESTS_C+= snprintf_test
+ATF_TESTS_C+= sscanf_test
+ATF_TESTS_C+= swprintf_test
+ATF_TESTS_C+= swscanf_test
SRCS.fopen2_test= fopen_test.c
@@ -34,9 +38,15 @@ LIBADD.eintr_test+= md
LIBADD.printfloat_test+= m
LIBADD.scanfloat_test+= m
+# Older toolchains won't understand C23 %b, %wN, %wfN
+PROG_OVERRIDE_VARS+= NO_WFORMAT
+NO_WFORMAT.snprintf_test=
+NO_WFORMAT.sscanf_test=
+NO_WFORMAT.swprintf_test=
+NO_WFORMAT.swscanf_test=
+
.if ${COMPILER_TYPE} == "gcc"
# 90: use of assignment suppression and length modifier together in scanf format
-PROG_OVERRIDE_VARS+= NO_WFORMAT
NO_WFORMAT.scanfloat_test=
.endif
diff --git a/lib/libc/tests/stdio/snprintf_test.c b/lib/libc/tests/stdio/snprintf_test.c
new file mode 100644
index 000000000000..10d938435b96
--- /dev/null
+++ b/lib/libc/tests/stdio/snprintf_test.c
@@ -0,0 +1,139 @@
+/*-
+ * Copyright (c) 2023 Dag-Erling Smørgrav
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <assert.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <atf-c.h>
+
+#ifndef nitems
+#define nitems(a) (sizeof(a) / sizeof(a[0]))
+#endif
+
+#define SNPRINTF_TEST(output, format, ...) \
+ do { \
+ char buf[256]; \
+ assert(strlen(output) < nitems(buf)); \
+ int ret = snprintf(buf, nitems(buf), format, \
+ __VA_ARGS__); \
+ ATF_CHECK_EQ(strlen(output), ret); \
+ if (ret > 0) { \
+ ATF_CHECK_EQ(0, strcmp(output, buf)); \
+ } \
+ } while (0)
+
+ATF_TC_WITHOUT_HEAD(snprintf_b);
+ATF_TC_BODY(snprintf_b, tc)
+{
+ SNPRINTF_TEST("0", "%b", 0);
+ SNPRINTF_TEST(" 0", "%12b", 0);
+ SNPRINTF_TEST("000000000000", "%012b", 0);
+ SNPRINTF_TEST("1", "%b", 1);
+ SNPRINTF_TEST(" 1", "%12b", 1);
+ SNPRINTF_TEST("000000000001", "%012b", 1);
+ SNPRINTF_TEST("1111111111111111111111111111111", "%b", INT_MAX);
+ SNPRINTF_TEST("0", "%#b", 0);
+ SNPRINTF_TEST(" 0", "%#12b", 0);
+ SNPRINTF_TEST("000000000000", "%#012b", 0);
+ SNPRINTF_TEST("0b1", "%#b", 1);
+ SNPRINTF_TEST(" 0b1", "%#12b", 1);
+ SNPRINTF_TEST("0b0000000001", "%#012b", 1);
+ SNPRINTF_TEST("0b1111111111111111111111111111111", "%#b", INT_MAX);
+}
+
+ATF_TC_WITHOUT_HEAD(snprintf_B);
+ATF_TC_BODY(snprintf_B, tc)
+{
+ SNPRINTF_TEST("0", "%B", 0);
+ SNPRINTF_TEST(" 0", "%12B", 0);
+ SNPRINTF_TEST("000000000000", "%012B", 0);
+ SNPRINTF_TEST("1", "%B", 1);
+ SNPRINTF_TEST(" 1", "%12B", 1);
+ SNPRINTF_TEST("000000000001", "%012B", 1);
+ SNPRINTF_TEST("1111111111111111111111111111111", "%B", INT_MAX);
+ SNPRINTF_TEST("0", "%#B", 0);
+ SNPRINTF_TEST(" 0", "%#12B", 0);
+ SNPRINTF_TEST("000000000000", "%#012B", 0);
+ SNPRINTF_TEST("0B1", "%#B", 1);
+ SNPRINTF_TEST(" 0B1", "%#12B", 1);
+ SNPRINTF_TEST("0B0000000001", "%#012B", 1);
+ SNPRINTF_TEST("0B1111111111111111111111111111111", "%#B", INT_MAX);
+}
+
+ATF_TC_WITHOUT_HEAD(snprintf_d);
+ATF_TC_BODY(snprintf_d, tc)
+{
+ SNPRINTF_TEST("0", "%d", 0);
+ SNPRINTF_TEST(" 0", "%12d", 0);
+ SNPRINTF_TEST("000000000000", "%012d", 0);
+ SNPRINTF_TEST("1", "%d", 1);
+ SNPRINTF_TEST(" 1", "%12d", 1);
+ SNPRINTF_TEST("000000000001", "%012d", 1);
+ SNPRINTF_TEST("2147483647", "%d", INT_MAX);
+ SNPRINTF_TEST(" 2147483647", "%12d", INT_MAX);
+ SNPRINTF_TEST("002147483647", "%012d", INT_MAX);
+ SNPRINTF_TEST("2,147,483,647", "%'d", INT_MAX);
+}
+
+ATF_TC_WITHOUT_HEAD(snprintf_x);
+ATF_TC_BODY(snprintf_x, tc)
+{
+ SNPRINTF_TEST("0", "%x", 0);
+ SNPRINTF_TEST(" 0", "%12x", 0);
+ SNPRINTF_TEST("000000000000", "%012x", 0);
+ SNPRINTF_TEST("1", "%x", 1);
+ SNPRINTF_TEST(" 1", "%12x", 1);
+ SNPRINTF_TEST("000000000001", "%012x", 1);
+ SNPRINTF_TEST("7fffffff", "%x", INT_MAX);
+ SNPRINTF_TEST(" 7fffffff", "%12x", INT_MAX);
+ SNPRINTF_TEST("00007fffffff", "%012x", INT_MAX);
+ SNPRINTF_TEST("0", "%#x", 0);
+ SNPRINTF_TEST(" 0", "%#12x", 0);
+ SNPRINTF_TEST("000000000000", "%#012x", 0);
+ SNPRINTF_TEST("0x1", "%#x", 1);
+ SNPRINTF_TEST(" 0x1", "%#12x", 1);
+ SNPRINTF_TEST("0x0000000001", "%#012x", 1);
+ SNPRINTF_TEST("0x7fffffff", "%#x", INT_MAX);
+ SNPRINTF_TEST(" 0x7fffffff", "%#12x", INT_MAX);
+ SNPRINTF_TEST("0x007fffffff", "%#012x", INT_MAX);
+}
+
+ATF_TC_WITHOUT_HEAD(snprintf_X);
+ATF_TC_BODY(snprintf_X, tc)
+{
+ SNPRINTF_TEST("0", "%X", 0);
+ SNPRINTF_TEST(" 0", "%12X", 0);
+ SNPRINTF_TEST("000000000000", "%012X", 0);
+ SNPRINTF_TEST("1", "%X", 1);
+ SNPRINTF_TEST(" 1", "%12X", 1);
+ SNPRINTF_TEST("000000000001", "%012X", 1);
+ SNPRINTF_TEST("7FFFFFFF", "%X", INT_MAX);
+ SNPRINTF_TEST(" 7FFFFFFF", "%12X", INT_MAX);
+ SNPRINTF_TEST("00007FFFFFFF", "%012X", INT_MAX);
+ SNPRINTF_TEST("0", "%#X", 0);
+ SNPRINTF_TEST(" 0", "%#12X", 0);
+ SNPRINTF_TEST("000000000000", "%#012X", 0);
+ SNPRINTF_TEST("0X1", "%#X", 1);
+ SNPRINTF_TEST(" 0X1", "%#12X", 1);
+ SNPRINTF_TEST("0X0000000001", "%#012X", 1);
+ SNPRINTF_TEST("0X7FFFFFFF", "%#X", INT_MAX);
+ SNPRINTF_TEST(" 0X7FFFFFFF", "%#12X", INT_MAX);
+ SNPRINTF_TEST("0X007FFFFFFF", "%#012X", INT_MAX);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ setlocale(LC_NUMERIC, "en_US.UTF-8");
+ ATF_TP_ADD_TC(tp, snprintf_b);
+ ATF_TP_ADD_TC(tp, snprintf_B);
+ ATF_TP_ADD_TC(tp, snprintf_d);
+ ATF_TP_ADD_TC(tp, snprintf_x);
+ ATF_TP_ADD_TC(tp, snprintf_X);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/stdio/sscanf_test.c b/lib/libc/tests/stdio/sscanf_test.c
new file mode 100644
index 000000000000..462b4a6586da
--- /dev/null
+++ b/lib/libc/tests/stdio/sscanf_test.c
@@ -0,0 +1,266 @@
+/*-
+ * Copyright (c) 2023 Dag-Erling Smørgrav
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <locale.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <atf-c.h>
+
+static const struct sscanf_test_case {
+ char input[8];
+ struct {
+ int ret, val, len;
+ } b, o, d, x, i;
+} sscanf_test_cases[] = {
+// input binary octal decimal hexadecimal automatic
+ // all digits
+ { "0", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, },
+ { "1", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, },
+ { "2", { 0, 0, 0 }, { 1, 2, 1 }, { 1, 2, 1 }, { 1, 2, 1 }, { 1, 2, 1 }, },
+ { "3", { 0, 0, 0 }, { 1, 3, 1 }, { 1, 3, 1 }, { 1, 3, 1 }, { 1, 3, 1 }, },
+ { "4", { 0, 0, 0 }, { 1, 4, 1 }, { 1, 4, 1 }, { 1, 4, 1 }, { 1, 4, 1 }, },
+ { "5", { 0, 0, 0 }, { 1, 5, 1 }, { 1, 5, 1 }, { 1, 5, 1 }, { 1, 5, 1 }, },
+ { "6", { 0, 0, 0 }, { 1, 6, 1 }, { 1, 6, 1 }, { 1, 6, 1 }, { 1, 6, 1 }, },
+ { "7", { 0, 0, 0 }, { 1, 7, 1 }, { 1, 7, 1 }, { 1, 7, 1 }, { 1, 7, 1 }, },
+ { "8", { 0, 0, 0 }, { 0, 0, 0 }, { 1, 8, 1 }, { 1, 8, 1 }, { 1, 8, 1 }, },
+ { "9", { 0, 0, 0 }, { 0, 0, 0 }, { 1, 9, 1 }, { 1, 9, 1 }, { 1, 9, 1 }, },
+ { "A", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 10, 1 }, { 0, 0, 0 }, },
+ { "B", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 11, 1 }, { 0, 0, 0 }, },
+ { "C", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 12, 1 }, { 0, 0, 0 }, },
+ { "D", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 13, 1 }, { 0, 0, 0 }, },
+ { "E", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 14, 1 }, { 0, 0, 0 }, },
+ { "F", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 15, 1 }, { 0, 0, 0 }, },
+ { "X", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, },
+ { "a", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 10, 1 }, { 0, 0, 0 }, },
+ { "b", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 11, 1 }, { 0, 0, 0 }, },
+ { "c", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 12, 1 }, { 0, 0, 0 }, },
+ { "d", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 13, 1 }, { 0, 0, 0 }, },
+ { "e", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 14, 1 }, { 0, 0, 0 }, },
+ { "f", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 15, 1 }, { 0, 0, 0 }, },
+ { "x", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, },
+ // all digits with leading zero
+ { "00", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, },
+ { "01", { 1, 1, 2 }, { 1, 1, 2 }, { 1, 1, 2 }, { 1, 1, 2 }, { 1, 1, 2 }, },
+ { "02", { 1, 0, 1 }, { 1, 2, 2 }, { 1, 2, 2 }, { 1, 2, 2 }, { 1, 2, 2 }, },
+ { "03", { 1, 0, 1 }, { 1, 3, 2 }, { 1, 3, 2 }, { 1, 3, 2 }, { 1, 3, 2 }, },
+ { "04", { 1, 0, 1 }, { 1, 4, 2 }, { 1, 4, 2 }, { 1, 4, 2 }, { 1, 4, 2 }, },
+ { "05", { 1, 0, 1 }, { 1, 5, 2 }, { 1, 5, 2 }, { 1, 5, 2 }, { 1, 5, 2 }, },
+ { "06", { 1, 0, 1 }, { 1, 6, 2 }, { 1, 6, 2 }, { 1, 6, 2 }, { 1, 6, 2 }, },
+ { "07", { 1, 0, 1 }, { 1, 7, 2 }, { 1, 7, 2 }, { 1, 7, 2 }, { 1, 7, 2 }, },
+ { "08", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 8, 2 }, { 1, 8, 2 }, { 1, 0, 1 }, },
+ { "09", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 9, 2 }, { 1, 9, 2 }, { 1, 0, 1 }, },
+ { "0A", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 10, 2 }, { 1, 0, 1 }, },
+ { "0B", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 11, 2 }, { 1, 0, 1 }, },
+ { "0C", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 12, 2 }, { 1, 0, 1 }, },
+ { "0D", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 13, 2 }, { 1, 0, 1 }, },
+ { "0E", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 14, 2 }, { 1, 0, 1 }, },
+ { "0F", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 15, 2 }, { 1, 0, 1 }, },
+ { "0X", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, },
+ { "0a", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 10, 2 }, { 1, 0, 1 }, },
+ { "0b", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 11, 2 }, { 1, 0, 1 }, },
+ { "0c", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 12, 2 }, { 1, 0, 1 }, },
+ { "0d", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 13, 2 }, { 1, 0, 1 }, },
+ { "0e", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 14, 2 }, { 1, 0, 1 }, },
+ { "0f", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 15, 2 }, { 1, 0, 1 }, },
+ { "0x", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, },
+ // all digits with leading one
+ { "10", { 1, 2, 2 }, { 1, 8, 2 }, { 1, 10, 2 }, { 1, 16, 2 }, { 1, 10, 2 }, },
+ { "11", { 1, 3, 2 }, { 1, 9, 2 }, { 1, 11, 2 }, { 1, 17, 2 }, { 1, 11, 2 }, },
+ { "12", { 1, 1, 1 }, { 1, 10, 2 }, { 1, 12, 2 }, { 1, 18, 2 }, { 1, 12, 2 }, },
+ { "13", { 1, 1, 1 }, { 1, 11, 2 }, { 1, 13, 2 }, { 1, 19, 2 }, { 1, 13, 2 }, },
+ { "14", { 1, 1, 1 }, { 1, 12, 2 }, { 1, 14, 2 }, { 1, 20, 2 }, { 1, 14, 2 }, },
+ { "15", { 1, 1, 1 }, { 1, 13, 2 }, { 1, 15, 2 }, { 1, 21, 2 }, { 1, 15, 2 }, },
+ { "16", { 1, 1, 1 }, { 1, 14, 2 }, { 1, 16, 2 }, { 1, 22, 2 }, { 1, 16, 2 }, },
+ { "17", { 1, 1, 1 }, { 1, 15, 2 }, { 1, 17, 2 }, { 1, 23, 2 }, { 1, 17, 2 }, },
+ { "18", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 18, 2 }, { 1, 24, 2 }, { 1, 18, 2 }, },
+ { "19", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 19, 2 }, { 1, 25, 2 }, { 1, 19, 2 }, },
+ { "1A", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 26, 2 }, { 1, 1, 1 }, },
+ { "1B", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 27, 2 }, { 1, 1, 1 }, },
+ { "1C", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 28, 2 }, { 1, 1, 1 }, },
+ { "1D", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 29, 2 }, { 1, 1, 1 }, },
+ { "1E", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 30, 2 }, { 1, 1, 1 }, },
+ { "1F", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 31, 2 }, { 1, 1, 1 }, },
+ { "1X", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, },
+ { "1a", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 26, 2 }, { 1, 1, 1 }, },
+ { "1b", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 27, 2 }, { 1, 1, 1 }, },
+ { "1c", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 28, 2 }, { 1, 1, 1 }, },
+ { "1d", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 29, 2 }, { 1, 1, 1 }, },
+ { "1e", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 30, 2 }, { 1, 1, 1 }, },
+ { "1f", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 31, 2 }, { 1, 1, 1 }, },
+ { "1x", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, },
+ // all digits with leading binary prefix
+ { "0b0", { 1, 0, 3 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 176, 3 }, { 1, 0, 3 }, },
+ { "0b1", { 1, 1, 3 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 177, 3 }, { 1, 1, 3 }, },
+ { "0b2", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 178, 3 }, { 1, 0, 1 }, },
+ { "0b3", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 179, 3 }, { 1, 0, 1 }, },
+ { "0b4", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 180, 3 }, { 1, 0, 1 }, },
+ { "0b5", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 181, 3 }, { 1, 0, 1 }, },
+ { "0b6", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 182, 3 }, { 1, 0, 1 }, },
+ { "0b7", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 183, 3 }, { 1, 0, 1 }, },
+ { "0b8", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 184, 3 }, { 1, 0, 1 }, },
+ { "0b9", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 185, 3 }, { 1, 0, 1 }, },
+ { "0bA", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 186, 3 }, { 1, 0, 1 }, },
+ { "0bB", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 187, 3 }, { 1, 0, 1 }, },
+ { "0bC", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 188, 3 }, { 1, 0, 1 }, },
+ { "0bD", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 189, 3 }, { 1, 0, 1 }, },
+ { "0bE", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 190, 3 }, { 1, 0, 1 }, },
+ { "0bF", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 191, 3 }, { 1, 0, 1 }, },
+ { "0bX", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 11, 2 }, { 1, 0, 1 }, },
+ { "0ba", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 186, 3 }, { 1, 0, 1 }, },
+ { "0bb", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 187, 3 }, { 1, 0, 1 }, },
+ { "0bc", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 188, 3 }, { 1, 0, 1 }, },
+ { "0bd", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 189, 3 }, { 1, 0, 1 }, },
+ { "0be", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 190, 3 }, { 1, 0, 1 }, },
+ { "0bf", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 191, 3 }, { 1, 0, 1 }, },
+ { "0bx", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 11, 2 }, { 1, 0, 1 }, },
+ // all digits with leading hexadecimal prefix
+ { "0x0", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 3 }, { 1, 0, 3 }, },
+ { "0x1", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 1, 3 }, { 1, 1, 3 }, },
+ { "0x2", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 2, 3 }, { 1, 2, 3 }, },
+ { "0x3", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 3, 3 }, { 1, 3, 3 }, },
+ { "0x4", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 4, 3 }, { 1, 4, 3 }, },
+ { "0x5", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 5, 3 }, { 1, 5, 3 }, },
+ { "0x6", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 6, 3 }, { 1, 6, 3 }, },
+ { "0x7", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 7, 3 }, { 1, 7, 3 }, },
+ { "0x8", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 8, 3 }, { 1, 8, 3 }, },
+ { "0x9", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 9, 3 }, { 1, 9, 3 }, },
+ { "0xA", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 10, 3 }, { 1, 10, 3 }, },
+ { "0xB", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 11, 3 }, { 1, 11, 3 }, },
+ { "0xC", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 12, 3 }, { 1, 12, 3 }, },
+ { "0xD", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 13, 3 }, { 1, 13, 3 }, },
+ { "0xE", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 14, 3 }, { 1, 14, 3 }, },
+ { "0xF", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 15, 3 }, { 1, 15, 3 }, },
+ { "0xX", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, },
+ { "0xa", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 10, 3 }, { 1, 10, 3 }, },
+ { "0xb", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 11, 3 }, { 1, 11, 3 }, },
+ { "0xc", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 12, 3 }, { 1, 12, 3 }, },
+ { "0xd", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 13, 3 }, { 1, 13, 3 }, },
+ { "0xe", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 14, 3 }, { 1, 14, 3 }, },
+ { "0xf", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 15, 3 }, { 1, 15, 3 }, },
+ { "0xX", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, },
+ // terminator
+ { "" }
+};
+
+#define SSCANF_TEST(string, format, expret, expval, explen) \
+ do { \
+ int ret = 0, val = 0, len = 0; \
+ ret = sscanf(string, format "%n", &val, &len); \
+ ATF_CHECK_EQ(expret, ret); \
+ if (expret && ret) { \
+ ATF_CHECK_EQ(expval, val); \
+ ATF_CHECK_EQ(explen, len); \
+ } \
+ } while (0)
+
+ATF_TC_WITHOUT_HEAD(sscanf_b);
+ATF_TC_BODY(sscanf_b, tc)
+{
+ const struct sscanf_test_case *stc;
+ char input[16];
+
+ for (stc = sscanf_test_cases; *stc->input; stc++) {
+ strcpy(input + 1, stc->input);
+ SSCANF_TEST(input + 1, "%b", stc->b.ret, stc->b.val, stc->b.len);
+ input[0] = '+';
+ SSCANF_TEST(input, "%b", stc->b.ret, stc->b.val, stc->b.len ? stc->b.len + 1 : 0);
+ input[0] = '-';
+ SSCANF_TEST(input, "%b", stc->b.ret, -stc->b.val, stc->b.len ? stc->b.len + 1 : 0);
+ }
+}
+
+ATF_TC_WITHOUT_HEAD(sscanf_o);
+ATF_TC_BODY(sscanf_o, tc)
+{
+ const struct sscanf_test_case *stc;
+ char input[16];
+
+ for (stc = sscanf_test_cases; *stc->input; stc++) {
+ strcpy(input + 1, stc->input);
+ SSCANF_TEST(input + 1, "%o", stc->o.ret, stc->o.val, stc->o.len);
+ input[0] = '+';
+ SSCANF_TEST(input, "%o", stc->o.ret, stc->o.val, stc->o.len ? stc->o.len + 1 : 0);
+ input[0] = '-';
+ SSCANF_TEST(input, "%o", stc->o.ret, -stc->o.val, stc->o.len ? stc->o.len + 1 : 0);
+ }
+}
+
+ATF_TC_WITHOUT_HEAD(sscanf_d);
+ATF_TC_BODY(sscanf_d, tc)
+{
+ const struct sscanf_test_case *stc;
+ char input[16];
+
+ for (stc = sscanf_test_cases; *stc->input; stc++) {
+ strcpy(input + 1, stc->input);
+ SSCANF_TEST(input + 1, "%d", stc->d.ret, stc->d.val, stc->d.len);
+ input[0] = '+';
+ SSCANF_TEST(input, "%d", stc->d.ret, stc->d.val, stc->d.len ? stc->d.len + 1 : 0);
+ input[0] = '-';
+ SSCANF_TEST(input, "%d", stc->d.ret, -stc->d.val, stc->d.len ? stc->d.len + 1 : 0);
+ }
+}
+
+ATF_TC_WITHOUT_HEAD(sscanf_x);
+ATF_TC_BODY(sscanf_x, tc)
+{
+ const struct sscanf_test_case *stc;
+ char input[16];
+
+ for (stc = sscanf_test_cases; *stc->input; stc++) {
+ strcpy(input + 1, stc->input);
+ SSCANF_TEST(input + 1, "%x", stc->x.ret, stc->x.val, stc->x.len);
+ input[0] = '+';
+ SSCANF_TEST(input, "%x", stc->x.ret, stc->x.val, stc->x.len ? stc->x.len + 1 : 0);
+ input[0] = '-';
+ SSCANF_TEST(input, "%x", stc->x.ret, -stc->x.val, stc->x.len ? stc->x.len + 1 : 0);
+ }
+}
+
+ATF_TC_WITHOUT_HEAD(sscanf_i);
+ATF_TC_BODY(sscanf_i, tc)
+{
+ const struct sscanf_test_case *stc;
+ char input[16];
+
+ for (stc = sscanf_test_cases; *stc->input; stc++) {
+ strcpy(input + 1, stc->input);
+ SSCANF_TEST(input + 1, "%i", stc->i.ret, stc->i.val, stc->i.len);
+ input[0] = '+';
+ SSCANF_TEST(input, "%i", stc->i.ret, stc->i.val, stc->i.len ? stc->i.len + 1 : 0);
+ input[0] = '-';
+ SSCANF_TEST(input, "%i", stc->i.ret, -stc->i.val, stc->i.len ? stc->i.len + 1 : 0);
+ }
+}
+
+/*
+ * Test termination cases: non-numeric character, fixed width, EOF
+ */
+ATF_TC_WITHOUT_HEAD(sscanf_termination);
+ATF_TC_BODY(sscanf_termination, tc)
+{
+ int a = 0, b = 0, c = 0;
+ char d = 0;
+
+ ATF_CHECK_EQ(4, sscanf("3.1415", "%d%c%2d%d", &a, &d, &b, &c));
+ ATF_CHECK_EQ(3, a);
+ ATF_CHECK_EQ(14, b);
+ ATF_CHECK_EQ(15, c);
+ ATF_CHECK_EQ('.', d);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ setlocale(LC_NUMERIC, "en_US.UTF-8");
+ ATF_TP_ADD_TC(tp, sscanf_b);
+ ATF_TP_ADD_TC(tp, sscanf_o);
+ ATF_TP_ADD_TC(tp, sscanf_d);
+ ATF_TP_ADD_TC(tp, sscanf_x);
+ ATF_TP_ADD_TC(tp, sscanf_i);
+ ATF_TP_ADD_TC(tp, sscanf_termination);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/stdio/swprintf_test.c b/lib/libc/tests/stdio/swprintf_test.c
new file mode 100644
index 000000000000..23859b5cd2e1
--- /dev/null
+++ b/lib/libc/tests/stdio/swprintf_test.c
@@ -0,0 +1,140 @@
+/*-
+ * Copyright (c) 2023 Dag-Erling Smørgrav
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <assert.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <wchar.h>
+
+#include <atf-c.h>
+
+#ifndef nitems
+#define nitems(a) (sizeof(a) / sizeof(a[0]))
+#endif
+
+#define SWPRINTF_TEST(output, format, ...) \
+ do { \
+ wchar_t buf[256]; \
+ assert(wcslen(L##output) < nitems(buf)); \
+ int ret = swprintf(buf, nitems(buf), L##format, \
+ __VA_ARGS__); \
+ ATF_CHECK_EQ(wcslen(L##output), ret); \
+ if (ret > 0) { \
+ ATF_CHECK_EQ(0, wcscmp(L##output, buf)); \
+ } \
+ } while (0)
+
+ATF_TC_WITHOUT_HEAD(swprintf_b);
+ATF_TC_BODY(swprintf_b, tc)
+{
+ SWPRINTF_TEST("0", "%b", 0);
+ SWPRINTF_TEST(" 0", "%12b", 0);
+ SWPRINTF_TEST("000000000000", "%012b", 0);
+ SWPRINTF_TEST("1", "%b", 1);
+ SWPRINTF_TEST(" 1", "%12b", 1);
+ SWPRINTF_TEST("000000000001", "%012b", 1);
+ SWPRINTF_TEST("1111111111111111111111111111111", "%b", INT_MAX);
+ SWPRINTF_TEST("0", "%#b", 0);
+ SWPRINTF_TEST(" 0", "%#12b", 0);
+ SWPRINTF_TEST("000000000000", "%#012b", 0);
+ SWPRINTF_TEST("0b1", "%#b", 1);
+ SWPRINTF_TEST(" 0b1", "%#12b", 1);
+ SWPRINTF_TEST("0b0000000001", "%#012b", 1);
+ SWPRINTF_TEST("0b1111111111111111111111111111111", "%#b", INT_MAX);
+}
+
+ATF_TC_WITHOUT_HEAD(swprintf_B);
+ATF_TC_BODY(swprintf_B, tc)
+{
+ SWPRINTF_TEST("0", "%B", 0);
+ SWPRINTF_TEST(" 0", "%12B", 0);
+ SWPRINTF_TEST("000000000000", "%012B", 0);
+ SWPRINTF_TEST("1", "%B", 1);
+ SWPRINTF_TEST(" 1", "%12B", 1);
+ SWPRINTF_TEST("000000000001", "%012B", 1);
+ SWPRINTF_TEST("1111111111111111111111111111111", "%B", INT_MAX);
+ SWPRINTF_TEST("0", "%#B", 0);
+ SWPRINTF_TEST(" 0", "%#12B", 0);
+ SWPRINTF_TEST("000000000000", "%#012B", 0);
+ SWPRINTF_TEST("0B1", "%#B", 1);
+ SWPRINTF_TEST(" 0B1", "%#12B", 1);
+ SWPRINTF_TEST("0B0000000001", "%#012B", 1);
+ SWPRINTF_TEST("0B1111111111111111111111111111111", "%#B", INT_MAX);
+}
+
+ATF_TC_WITHOUT_HEAD(swprintf_d);
+ATF_TC_BODY(swprintf_d, tc)
+{
+ SWPRINTF_TEST("0", "%d", 0);
+ SWPRINTF_TEST(" 0", "%12d", 0);
+ SWPRINTF_TEST("000000000000", "%012d", 0);
+ SWPRINTF_TEST("1", "%d", 1);
+ SWPRINTF_TEST(" 1", "%12d", 1);
+ SWPRINTF_TEST("000000000001", "%012d", 1);
+ SWPRINTF_TEST("2147483647", "%d", INT_MAX);
+ SWPRINTF_TEST(" 2147483647", "%12d", INT_MAX);
+ SWPRINTF_TEST("002147483647", "%012d", INT_MAX);
+ SWPRINTF_TEST("2,147,483,647", "%'d", INT_MAX);
+}
+
+ATF_TC_WITHOUT_HEAD(swprintf_x);
+ATF_TC_BODY(swprintf_x, tc)
+{
+ SWPRINTF_TEST("0", "%x", 0);
+ SWPRINTF_TEST(" 0", "%12x", 0);
+ SWPRINTF_TEST("000000000000", "%012x", 0);
+ SWPRINTF_TEST("1", "%x", 1);
+ SWPRINTF_TEST(" 1", "%12x", 1);
+ SWPRINTF_TEST("000000000001", "%012x", 1);
+ SWPRINTF_TEST("7fffffff", "%x", INT_MAX);
+ SWPRINTF_TEST(" 7fffffff", "%12x", INT_MAX);
+ SWPRINTF_TEST("00007fffffff", "%012x", INT_MAX);
+ SWPRINTF_TEST("0", "%#x", 0);
+ SWPRINTF_TEST(" 0", "%#12x", 0);
+ SWPRINTF_TEST("000000000000", "%#012x", 0);
+ SWPRINTF_TEST("0x1", "%#x", 1);
+ SWPRINTF_TEST(" 0x1", "%#12x", 1);
+ SWPRINTF_TEST("0x0000000001", "%#012x", 1);
+ SWPRINTF_TEST("0x7fffffff", "%#x", INT_MAX);
+ SWPRINTF_TEST(" 0x7fffffff", "%#12x", INT_MAX);
+ SWPRINTF_TEST("0x007fffffff", "%#012x", INT_MAX);
+}
+
+ATF_TC_WITHOUT_HEAD(swprintf_X);
+ATF_TC_BODY(swprintf_X, tc)
+{
+ SWPRINTF_TEST("0", "%X", 0);
+ SWPRINTF_TEST(" 0", "%12X", 0);
+ SWPRINTF_TEST("000000000000", "%012X", 0);
+ SWPRINTF_TEST("1", "%X", 1);
+ SWPRINTF_TEST(" 1", "%12X", 1);
+ SWPRINTF_TEST("000000000001", "%012X", 1);
+ SWPRINTF_TEST("7FFFFFFF", "%X", INT_MAX);
+ SWPRINTF_TEST(" 7FFFFFFF", "%12X", INT_MAX);
+ SWPRINTF_TEST("00007FFFFFFF", "%012X", INT_MAX);
+ SWPRINTF_TEST("0", "%#X", 0);
+ SWPRINTF_TEST(" 0", "%#12X", 0);
+ SWPRINTF_TEST("000000000000", "%#012X", 0);
+ SWPRINTF_TEST("0X1", "%#X", 1);
+ SWPRINTF_TEST(" 0X1", "%#12X", 1);
+ SWPRINTF_TEST("0X0000000001", "%#012X", 1);
+ SWPRINTF_TEST("0X7FFFFFFF", "%#X", INT_MAX);
+ SWPRINTF_TEST(" 0X7FFFFFFF", "%#12X", INT_MAX);
+ SWPRINTF_TEST("0X007FFFFFFF", "%#012X", INT_MAX);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ setlocale(LC_NUMERIC, "en_US.UTF-8");
+ ATF_TP_ADD_TC(tp, swprintf_b);
+ ATF_TP_ADD_TC(tp, swprintf_B);
+ ATF_TP_ADD_TC(tp, swprintf_d);
+ ATF_TP_ADD_TC(tp, swprintf_x);
+ ATF_TP_ADD_TC(tp, swprintf_X);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/stdio/swscanf_test.c b/lib/libc/tests/stdio/swscanf_test.c
new file mode 100644
index 000000000000..10eaf786e5fa
--- /dev/null
+++ b/lib/libc/tests/stdio/swscanf_test.c
@@ -0,0 +1,267 @@
+/*-
+ * Copyright (c) 2023 Dag-Erling Smørgrav
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <locale.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <wchar.h>
+
+#include <atf-c.h>
+
+static const struct swscanf_test_case {
+ wchar_t input[8];
+ struct {
+ int ret, val, len;
+ } b, o, d, x, i;
+} swscanf_test_cases[] = {
+// input binary octal decimal hexadecimal automatic
+ // all digits
+ { L"0", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, },
+ { L"1", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, },
+ { L"2", { 0, 0, 0 }, { 1, 2, 1 }, { 1, 2, 1 }, { 1, 2, 1 }, { 1, 2, 1 }, },
+ { L"3", { 0, 0, 0 }, { 1, 3, 1 }, { 1, 3, 1 }, { 1, 3, 1 }, { 1, 3, 1 }, },
+ { L"4", { 0, 0, 0 }, { 1, 4, 1 }, { 1, 4, 1 }, { 1, 4, 1 }, { 1, 4, 1 }, },
+ { L"5", { 0, 0, 0 }, { 1, 5, 1 }, { 1, 5, 1 }, { 1, 5, 1 }, { 1, 5, 1 }, },
+ { L"6", { 0, 0, 0 }, { 1, 6, 1 }, { 1, 6, 1 }, { 1, 6, 1 }, { 1, 6, 1 }, },
+ { L"7", { 0, 0, 0 }, { 1, 7, 1 }, { 1, 7, 1 }, { 1, 7, 1 }, { 1, 7, 1 }, },
+ { L"8", { 0, 0, 0 }, { 0, 0, 0 }, { 1, 8, 1 }, { 1, 8, 1 }, { 1, 8, 1 }, },
+ { L"9", { 0, 0, 0 }, { 0, 0, 0 }, { 1, 9, 1 }, { 1, 9, 1 }, { 1, 9, 1 }, },
+ { L"A", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 10, 1 }, { 0, 0, 0 }, },
+ { L"B", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 11, 1 }, { 0, 0, 0 }, },
+ { L"C", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 12, 1 }, { 0, 0, 0 }, },
+ { L"D", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 13, 1 }, { 0, 0, 0 }, },
+ { L"E", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 14, 1 }, { 0, 0, 0 }, },
+ { L"F", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 15, 1 }, { 0, 0, 0 }, },
+ { L"X", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, },
+ { L"a", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 10, 1 }, { 0, 0, 0 }, },
+ { L"b", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 11, 1 }, { 0, 0, 0 }, },
+ { L"c", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 12, 1 }, { 0, 0, 0 }, },
+ { L"d", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 13, 1 }, { 0, 0, 0 }, },
+ { L"e", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 14, 1 }, { 0, 0, 0 }, },
+ { L"f", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 1, 15, 1 }, { 0, 0, 0 }, },
+ { L"x", { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, },
+ // all digits with leading zero
+ { L"00", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, },
+ { L"01", { 1, 1, 2 }, { 1, 1, 2 }, { 1, 1, 2 }, { 1, 1, 2 }, { 1, 1, 2 }, },
+ { L"02", { 1, 0, 1 }, { 1, 2, 2 }, { 1, 2, 2 }, { 1, 2, 2 }, { 1, 2, 2 }, },
+ { L"03", { 1, 0, 1 }, { 1, 3, 2 }, { 1, 3, 2 }, { 1, 3, 2 }, { 1, 3, 2 }, },
+ { L"04", { 1, 0, 1 }, { 1, 4, 2 }, { 1, 4, 2 }, { 1, 4, 2 }, { 1, 4, 2 }, },
+ { L"05", { 1, 0, 1 }, { 1, 5, 2 }, { 1, 5, 2 }, { 1, 5, 2 }, { 1, 5, 2 }, },
+ { L"06", { 1, 0, 1 }, { 1, 6, 2 }, { 1, 6, 2 }, { 1, 6, 2 }, { 1, 6, 2 }, },
+ { L"07", { 1, 0, 1 }, { 1, 7, 2 }, { 1, 7, 2 }, { 1, 7, 2 }, { 1, 7, 2 }, },
+ { L"08", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 8, 2 }, { 1, 8, 2 }, { 1, 0, 1 }, },
+ { L"09", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 9, 2 }, { 1, 9, 2 }, { 1, 0, 1 }, },
+ { L"0A", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 10, 2 }, { 1, 0, 1 }, },
+ { L"0B", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 11, 2 }, { 1, 0, 1 }, },
+ { L"0C", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 12, 2 }, { 1, 0, 1 }, },
+ { L"0D", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 13, 2 }, { 1, 0, 1 }, },
+ { L"0E", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 14, 2 }, { 1, 0, 1 }, },
+ { L"0F", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 15, 2 }, { 1, 0, 1 }, },
+ { L"0X", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, },
+ { L"0a", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 10, 2 }, { 1, 0, 1 }, },
+ { L"0b", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 11, 2 }, { 1, 0, 1 }, },
+ { L"0c", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 12, 2 }, { 1, 0, 1 }, },
+ { L"0d", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 13, 2 }, { 1, 0, 1 }, },
+ { L"0e", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 14, 2 }, { 1, 0, 1 }, },
+ { L"0f", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 15, 2 }, { 1, 0, 1 }, },
+ { L"0x", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, },
+ // all digits with leading one
+ { L"10", { 1, 2, 2 }, { 1, 8, 2 }, { 1, 10, 2 }, { 1, 16, 2 }, { 1, 10, 2 }, },
+ { L"11", { 1, 3, 2 }, { 1, 9, 2 }, { 1, 11, 2 }, { 1, 17, 2 }, { 1, 11, 2 }, },
+ { L"12", { 1, 1, 1 }, { 1, 10, 2 }, { 1, 12, 2 }, { 1, 18, 2 }, { 1, 12, 2 }, },
+ { L"13", { 1, 1, 1 }, { 1, 11, 2 }, { 1, 13, 2 }, { 1, 19, 2 }, { 1, 13, 2 }, },
+ { L"14", { 1, 1, 1 }, { 1, 12, 2 }, { 1, 14, 2 }, { 1, 20, 2 }, { 1, 14, 2 }, },
+ { L"15", { 1, 1, 1 }, { 1, 13, 2 }, { 1, 15, 2 }, { 1, 21, 2 }, { 1, 15, 2 }, },
+ { L"16", { 1, 1, 1 }, { 1, 14, 2 }, { 1, 16, 2 }, { 1, 22, 2 }, { 1, 16, 2 }, },
+ { L"17", { 1, 1, 1 }, { 1, 15, 2 }, { 1, 17, 2 }, { 1, 23, 2 }, { 1, 17, 2 }, },
+ { L"18", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 18, 2 }, { 1, 24, 2 }, { 1, 18, 2 }, },
+ { L"19", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 19, 2 }, { 1, 25, 2 }, { 1, 19, 2 }, },
+ { L"1A", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 26, 2 }, { 1, 1, 1 }, },
+ { L"1B", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 27, 2 }, { 1, 1, 1 }, },
+ { L"1C", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 28, 2 }, { 1, 1, 1 }, },
+ { L"1D", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 29, 2 }, { 1, 1, 1 }, },
+ { L"1E", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 30, 2 }, { 1, 1, 1 }, },
+ { L"1F", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 31, 2 }, { 1, 1, 1 }, },
+ { L"1X", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, },
+ { L"1a", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 26, 2 }, { 1, 1, 1 }, },
+ { L"1b", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 27, 2 }, { 1, 1, 1 }, },
+ { L"1c", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 28, 2 }, { 1, 1, 1 }, },
+ { L"1d", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 29, 2 }, { 1, 1, 1 }, },
+ { L"1e", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 30, 2 }, { 1, 1, 1 }, },
+ { L"1f", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 31, 2 }, { 1, 1, 1 }, },
+ { L"1x", { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, },
+ // all digits with leading binary prefix
+ { L"0b0", { 1, 0, 3 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 176, 3 }, { 1, 0, 3 }, },
+ { L"0b1", { 1, 1, 3 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 177, 3 }, { 1, 1, 3 }, },
+ { L"0b2", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 178, 3 }, { 1, 0, 1 }, },
+ { L"0b3", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 179, 3 }, { 1, 0, 1 }, },
+ { L"0b4", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 180, 3 }, { 1, 0, 1 }, },
+ { L"0b5", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 181, 3 }, { 1, 0, 1 }, },
+ { L"0b6", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 182, 3 }, { 1, 0, 1 }, },
+ { L"0b7", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 183, 3 }, { 1, 0, 1 }, },
+ { L"0b8", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 184, 3 }, { 1, 0, 1 }, },
+ { L"0b9", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 185, 3 }, { 1, 0, 1 }, },
+ { L"0bA", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 186, 3 }, { 1, 0, 1 }, },
+ { L"0bB", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 187, 3 }, { 1, 0, 1 }, },
+ { L"0bC", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 188, 3 }, { 1, 0, 1 }, },
+ { L"0bD", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 189, 3 }, { 1, 0, 1 }, },
+ { L"0bE", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 190, 3 }, { 1, 0, 1 }, },
+ { L"0bF", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 191, 3 }, { 1, 0, 1 }, },
+ { L"0bX", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 11, 2 }, { 1, 0, 1 }, },
+ { L"0ba", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 186, 3 }, { 1, 0, 1 }, },
+ { L"0bb", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 187, 3 }, { 1, 0, 1 }, },
+ { L"0bc", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 188, 3 }, { 1, 0, 1 }, },
+ { L"0bd", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 189, 3 }, { 1, 0, 1 }, },
+ { L"0be", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 190, 3 }, { 1, 0, 1 }, },
+ { L"0bf", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 191, 3 }, { 1, 0, 1 }, },
+ { L"0bx", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 11, 2 }, { 1, 0, 1 }, },
+ // all digits with leading hexadecimal prefix
+ { L"0x0", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 3 }, { 1, 0, 3 }, },
+ { L"0x1", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 1, 3 }, { 1, 1, 3 }, },
+ { L"0x2", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 2, 3 }, { 1, 2, 3 }, },
+ { L"0x3", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 3, 3 }, { 1, 3, 3 }, },
+ { L"0x4", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 4, 3 }, { 1, 4, 3 }, },
+ { L"0x5", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 5, 3 }, { 1, 5, 3 }, },
+ { L"0x6", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 6, 3 }, { 1, 6, 3 }, },
+ { L"0x7", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 7, 3 }, { 1, 7, 3 }, },
+ { L"0x8", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 8, 3 }, { 1, 8, 3 }, },
+ { L"0x9", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 9, 3 }, { 1, 9, 3 }, },
+ { L"0xA", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 10, 3 }, { 1, 10, 3 }, },
+ { L"0xB", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 11, 3 }, { 1, 11, 3 }, },
+ { L"0xC", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 12, 3 }, { 1, 12, 3 }, },
+ { L"0xD", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 13, 3 }, { 1, 13, 3 }, },
+ { L"0xE", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 14, 3 }, { 1, 14, 3 }, },
+ { L"0xF", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 15, 3 }, { 1, 15, 3 }, },
+ { L"0xX", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, },
+ { L"0xa", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 10, 3 }, { 1, 10, 3 }, },
+ { L"0xb", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 11, 3 }, { 1, 11, 3 }, },
+ { L"0xc", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 12, 3 }, { 1, 12, 3 }, },
+ { L"0xd", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 13, 3 }, { 1, 13, 3 }, },
+ { L"0xe", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 14, 3 }, { 1, 14, 3 }, },
+ { L"0xf", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 15, 3 }, { 1, 15, 3 }, },
+ { L"0xX", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, },
+ // terminator
+ { L"" }
+};
+
+#define SWSCANF_TEST(string, format, expret, expval, explen) \
+ do { \
+ int ret = 0, val = 0, len = 0; \
+ ret = swscanf(string, format "%n", &val, &len); \
+ ATF_CHECK_EQ(expret, ret); \
+ if (expret && ret) { \
+ ATF_CHECK_EQ(expval, val); \
+ ATF_CHECK_EQ(explen, len); \
+ } \
+ } while (0)
+
+ATF_TC_WITHOUT_HEAD(swscanf_b);
+ATF_TC_BODY(swscanf_b, tc)
+{
+ const struct swscanf_test_case *stc;
+ wchar_t input[16];
+
+ for (stc = swscanf_test_cases; *stc->input; stc++) {
+ wcscpy(input + 1, stc->input);
+ SWSCANF_TEST(input + 1, L"%b", stc->b.ret, stc->b.val, stc->b.len);
+ input[0] = L'+';
+ SWSCANF_TEST(input, L"%b", stc->b.ret, stc->b.val, stc->b.len ? stc->b.len + 1 : 0);
+ input[0] = L'-';
+ SWSCANF_TEST(input, L"%b", stc->b.ret, -stc->b.val, stc->b.len ? stc->b.len + 1 : 0);
+ }
+}
+
+ATF_TC_WITHOUT_HEAD(swscanf_o);
+ATF_TC_BODY(swscanf_o, tc)
+{
+ const struct swscanf_test_case *stc;
+ wchar_t input[16];
+
+ for (stc = swscanf_test_cases; *stc->input; stc++) {
+ wcscpy(input + 1, stc->input);
+ SWSCANF_TEST(input + 1, L"%o", stc->o.ret, stc->o.val, stc->o.len);
+ input[0] = L'+';
+ SWSCANF_TEST(input, L"%o", stc->o.ret, stc->o.val, stc->o.len ? stc->o.len + 1 : 0);
+ input[0] = L'-';
+ SWSCANF_TEST(input, L"%o", stc->o.ret, -stc->o.val, stc->o.len ? stc->o.len + 1 : 0);
+ }
+}
+
+ATF_TC_WITHOUT_HEAD(swscanf_d);
+ATF_TC_BODY(swscanf_d, tc)
+{
+ const struct swscanf_test_case *stc;
+ wchar_t input[16];
+
+ for (stc = swscanf_test_cases; *stc->input; stc++) {
+ wcscpy(input + 1, stc->input);
+ SWSCANF_TEST(input + 1, L"%d", stc->d.ret, stc->d.val, stc->d.len);
+ input[0] = L'+';
+ SWSCANF_TEST(input, L"%d", stc->d.ret, stc->d.val, stc->d.len ? stc->d.len + 1 : 0);
+ input[0] = L'-';
+ SWSCANF_TEST(input, L"%d", stc->d.ret, -stc->d.val, stc->d.len ? stc->d.len + 1 : 0);
+ }
+}
+
+ATF_TC_WITHOUT_HEAD(swscanf_x);
+ATF_TC_BODY(swscanf_x, tc)
+{
+ const struct swscanf_test_case *stc;
+ wchar_t input[16];
+
+ for (stc = swscanf_test_cases; *stc->input; stc++) {
+ wcscpy(input + 1, stc->input);
+ SWSCANF_TEST(input + 1, L"%x", stc->x.ret, stc->x.val, stc->x.len);
+ input[0] = L'+';
+ SWSCANF_TEST(input, L"%x", stc->x.ret, stc->x.val, stc->x.len ? stc->x.len + 1 : 0);
+ input[0] = L'-';
+ SWSCANF_TEST(input, L"%x", stc->x.ret, -stc->x.val, stc->x.len ? stc->x.len + 1 : 0);
+ }
+}
+
+ATF_TC_WITHOUT_HEAD(swscanf_i);
+ATF_TC_BODY(swscanf_i, tc)
+{
+ const struct swscanf_test_case *stc;
+ wchar_t input[16];
+
+ for (stc = swscanf_test_cases; *stc->input; stc++) {
+ wcscpy(input + 1, stc->input);
+ SWSCANF_TEST(input + 1, L"%i", stc->i.ret, stc->i.val, stc->i.len);
+ input[0] = L'+';
+ SWSCANF_TEST(input, L"%i", stc->i.ret, stc->i.val, stc->i.len ? stc->i.len + 1 : 0);
+ input[0] = L'-';
+ SWSCANF_TEST(input, L"%i", stc->i.ret, -stc->i.val, stc->i.len ? stc->i.len + 1 : 0);
+ }
+}
+
+/*
+ * Test termination cases: non-numeric character, fixed width, EOF
+ */
+ATF_TC_WITHOUT_HEAD(swscanf_termination);
+ATF_TC_BODY(swscanf_termination, tc)
+{
+ int a = 0, b = 0, c = 0;
+ char d = 0;
+
+ ATF_CHECK_EQ(4, swscanf(L"3.1415", L"%d%c%2d%d", &a, &d, &b, &c));
+ ATF_CHECK_EQ(3, a);
+ ATF_CHECK_EQ(14, b);
+ ATF_CHECK_EQ(15, c);
+ ATF_CHECK_EQ(L'.', d);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ setlocale(LC_NUMERIC, "en_US.UTF-8");
+ ATF_TP_ADD_TC(tp, swscanf_b);
+ ATF_TP_ADD_TC(tp, swscanf_o);
+ ATF_TP_ADD_TC(tp, swscanf_d);
+ ATF_TP_ADD_TC(tp, swscanf_x);
+ ATF_TP_ADD_TC(tp, swscanf_i);
+ ATF_TP_ADD_TC(tp, swscanf_termination);
+ return (atf_no_error());
+}