diff mbox series

C2x scanf %wN, %wfN support

Message ID 611ae3f4-969-441e-8a51-68dbdf45f62a@codesourcery.com
State New
Headers show
Series C2x scanf %wN, %wfN support | expand

Commit Message

Joseph Myers Sept. 14, 2023, 7:03 p.m. UTC
ISO C2x defines scanf length modifiers wN (for intN_t / int_leastN_t /
uintN_t / uint_leastN_t) and wfN (for int_fastN_t / uint_fastN_t).
Add support for those length modifiers, similar to the printf support
previously added.

Tested for x86_64 and x86.

Comments

Joseph Myers Sept. 20, 2023, 1:26 p.m. UTC | #1
Ping.  This patch 
<https://sourceware.org/pipermail/libc-alpha/2023-September/151586.html> 
is pending review.
Joseph Myers Sept. 27, 2023, 2:45 p.m. UTC | #2
Ping^2.  This patch 
<https://sourceware.org/pipermail/libc-alpha/2023-September/151586.html> 
is still pending review.
Adhemerval Zanella Netto Sept. 27, 2023, 8:26 p.m. UTC | #3
On 14/09/23 16:03, Joseph Myers wrote:
> ISO C2x defines scanf length modifiers wN (for intN_t / int_leastN_t /
> uintN_t / uint_leastN_t) and wfN (for int_fastN_t / uint_fastN_t).
> Add support for those length modifiers, similar to the printf support
> previously added.
> 
> Tested for x86_64 and x86.
> 

Patch looks good, some minor remarks below.

> diff --git a/NEWS b/NEWS
> index a48c32e76f..d2ae239895 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -31,6 +31,13 @@ Major new features:
>    the process ID associated with the process file descriptor created by
>    pid_spawn, fork_np, or pidfd_open.
>  
> +* scanf-family functions now support the wN format length modifiers for
> +  arguments pointing to types intN_t, int_leastN_t, uintN_t or
> +  uint_leastN_t (for example, %w32d to read int32_t or int_least32_t in
> +  decimal, or %w32x to read uint32_t or uint_least32_t in hexadecimal)
> +  and the wfN format length modifiers for arguments pointing to types
> +  int_fastN_t or uint_fastN_t, as specified in draft ISO C2X.
> +
>  Deprecated and removed features, and other changes affecting compatibility:
>  
>    [Add deprecations, removals and changes affecting compatibility here]

Ok.

> diff --git a/manual/stdio.texi b/manual/stdio.texi
> index 9cf622403f..002fce7a10 100644
> --- a/manual/stdio.texi
> +++ b/manual/stdio.texi
> @@ -3714,6 +3714,20 @@ Specifies that the argument is a @code{ptrdiff_t *}.
>  
>  This modifier was introduced in @w{ISO C99}.
>  
> +@item w@var{n}
> +Specifies that the argument is an @code{int@var{n}_t *} or
> +@code{int_least@var{n}_t *} (which are the same type), or
> +@code{uint@var{n}_t *} or @code{uint_least@var{n}_t *} (which are the
> +same type).
> +
> +This modifier was introduced in @w{ISO C2X}.
> +
> +@item wf@var{n}
> +Specifies that the argument is an @code{int_fast@var{n}_t *} or
> +@code{uint_fast@var{n}_t *}.
> +
> +This modifier was introduced in @w{ISO C2X}.
> +
>  @item z
>  Specifies that the argument is a @code{size_t *}.
>  

Ok.

> diff --git a/stdio-common/Makefile b/stdio-common/Makefile
> index 3866362bae..bacb795fed 100644
> --- a/stdio-common/Makefile
> +++ b/stdio-common/Makefile
> @@ -243,6 +243,7 @@ tests := \
>    tst-scanf-binary-c2x \
>    tst-scanf-binary-gnu11 \
>    tst-scanf-binary-gnu89 \
> +  tst-scanf-intn \
>    tst-scanf-round \
>    tst-scanf-to_inpunct \
>    tst-setvbuf1 \

Ok.

> diff --git a/stdio-common/tst-scanf-intn-main.c b/stdio-common/tst-scanf-intn-main.c
> new file mode 100644
> index 0000000000..fa0462a8de
> --- /dev/null
> +++ b/stdio-common/tst-scanf-intn-main.c
> @@ -0,0 +1,479 @@
> +/* Test scanf formats for intN_t, int_leastN_t and int_fastN_t types.
> +   Copyright (C) 2023 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#include <errno.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <wchar.h>
> +
> +#include <libc-diag.h>
> +#include <support/check.h>
> +
> +/* GCC does not know the %wN or %wfN length modifiers before GCC 13.  */
> +DIAG_PUSH_NEEDS_COMMENT;
> +#if !__GNUC_PREREQ (13, 0)
> +DIAG_IGNORE_NEEDS_COMMENT (12, "-Wformat");
> +DIAG_IGNORE_NEEDS_COMMENT (12, "-Wformat-extra-args");
> +#endif
> +
> +#define CHECK_SCANF1(EXPECTED, STR, FMT)				\
> +  do									\
> +    {									\
> +      var = ((typeof (var)) 0xabababab);				\
> +      int ret = SSCANF (L_(STR), L_(FMT), &var);			\
> +      TEST_COMPARE (var, (EXPECTED));					\
> +      TEST_COMPARE (ret, 1);						\
> +    }									\
> +  while (0)
> +
> +#define CHECK_SCANF1N(EXPECTED, STR, FMT)				\
> +  do									\
> +    {									\
> +      var = ((typeof (var)) 0xabababab);				\
> +      n = 123;								\
> +      int ret = SSCANF (L_(STR), L_(FMT), &var, &n);			\
> +      TEST_COMPARE (var, (EXPECTED));					\
> +      TEST_COMPARE (n, STRLEN (L_(STR)));				\
> +      TEST_COMPARE (ret, 1);						\
> +    }									\
> +  while (0)
> +
> +#define CHECK_SCANF_ERR(OK, STR, FMT, ...)				\
> +  do									\
> +    {									\
> +      int ret = SSCANF (L_(STR), L_(FMT), __VA_ARGS__);			\
> +      TEST_VERIFY (ret == (OK));					\
> +      TEST_COMPARE (errno, EINVAL);					\
> +    }									\
> +  while (0)
> +
> +static void
> +test_w8 (void)
> +{
> +  {
> +    int8_t var, n;
> +    CHECK_SCANF1 (42, "42", "%w8d");
> +    CHECK_SCANF1N (42, "42", "%w8d%w8n");
> +    CHECK_SCANF1 (-43, "-43", "%w8d");
> +    CHECK_SCANF1 (42, "42", "%w8i");
> +    CHECK_SCANF1 (-43, "-43", "%w8i");
> +    CHECK_SCANF1 (123, "0b1111011", "%w8i");
> +    CHECK_SCANF1 (127, "0x7f", "%w8i");
> +    CHECK_SCANF1 (-19, "-023", "%w8i");
> +  }
> +  {
> +    uint8_t var;
> +    int8_t n;
> +    CHECK_SCANF1 (123, "1111011", "%w8b");
> +    CHECK_SCANF1 (19, "023", "%w8o");
> +    CHECK_SCANF1 (50, "50", "%w8u");
> +    CHECK_SCANF1 (65, "41", "%w8x");
> +    CHECK_SCANF1N (65, "41", "%w8x%w8n");
> +    CHECK_SCANF1 (66, "42", "%w8X");
> +  }
> +  {
> +    int_least8_t var, n;
> +    CHECK_SCANF1 (42, "42", "%w8d");
> +    CHECK_SCANF1N (42, "42", "%w8d%w8n");
> +    CHECK_SCANF1 (-43, "-43", "%w8d");
> +    CHECK_SCANF1 (42, "42", "%w8i");
> +    CHECK_SCANF1 (-43, "-43", "%w8i");
> +    CHECK_SCANF1 (123, "0b1111011", "%w8i");
> +    CHECK_SCANF1 (127, "0x7f", "%w8i");
> +    CHECK_SCANF1 (-19, "-023", "%w8i");
> +  }
> +  {
> +    uint_least8_t var;
> +    int_least8_t n;
> +    CHECK_SCANF1 (123, "1111011", "%w8b");
> +    CHECK_SCANF1 (19, "023", "%w8o");
> +    CHECK_SCANF1 (50, "50", "%w8u");
> +    CHECK_SCANF1 (65, "41", "%w8x");
> +    CHECK_SCANF1N (65, "41", "%w8x%w8n");
> +    CHECK_SCANF1 (66, "42", "%w8X");
> +  }
> +}
> +

Ok.

> +static void
> +test_wf8 (void)
> +{
> +  {
> +    int_fast8_t var, n;
> +    CHECK_SCANF1 (42, "42", "%wf8d");
> +    CHECK_SCANF1N (42, "42", "%wf8d%wf8n");
> +    CHECK_SCANF1 (-43, "-43", "%wf8d");
> +    CHECK_SCANF1 (42, "42", "%wf8i");
> +    CHECK_SCANF1 (-43, "-43", "%wf8i");
> +    CHECK_SCANF1 (123, "0b1111011", "%wf8i");
> +    CHECK_SCANF1 (127, "0x7f", "%wf8i");
> +    CHECK_SCANF1 (-19, "-023", "%wf8i");
> +  }
> +  {
> +    uint_fast8_t var;
> +    int_fast8_t n;
> +    CHECK_SCANF1 (123, "1111011", "%wf8b");
> +    CHECK_SCANF1 (19, "023", "%wf8o");
> +    CHECK_SCANF1 (50, "50", "%wf8u");
> +    CHECK_SCANF1 (65, "41", "%wf8x");
> +    CHECK_SCANF1N (65, "41", "%wf8x%wf8n");
> +    CHECK_SCANF1 (66, "42", "%wf8X");
> +  }
> +}
> +
> +static void
> +test_w16 (void)
> +{
> +  {
> +    int16_t var, n;
> +    CHECK_SCANF1 (12345, "12345", "%w16d");
> +    CHECK_SCANF1N (23456, "23456", "%w16d%w16n");
> +    CHECK_SCANF1 (-10101, "-10101", "%w16d");
> +    CHECK_SCANF1 (30000, "30000", "%w16i");
> +    CHECK_SCANF1 (-19876, "-19876", "%w16i");
> +    CHECK_SCANF1 (16384, "0b100000000000000", "%w16i");
> +    CHECK_SCANF1 (32767, "0x7fff", "%w16i");
> +    CHECK_SCANF1 (-16383, "-037777", "%w16i");
> +  }
> +  {
> +    uint16_t var;
> +    int16_t n;
> +    CHECK_SCANF1 (32767, "111111111111111", "%w16b");
> +    CHECK_SCANF1 (4095, "07777", "%w16o");
> +    CHECK_SCANF1 (9999, "9999", "%w16u");
> +    CHECK_SCANF1 (23456, "5ba0", "%w16x");
> +    CHECK_SCANF1N (23456, "5ba0", "%w16x%w16n");
> +    CHECK_SCANF1 (23457, "5ba1", "%w16X");
> +  }
> +  {
> +    int_least16_t var, n;
> +    CHECK_SCANF1 (12345, "12345", "%w16d");
> +    CHECK_SCANF1N (23456, "23456", "%w16d%w16n");
> +    CHECK_SCANF1 (-10101, "-10101", "%w16d");
> +    CHECK_SCANF1 (30000, "30000", "%w16i");
> +    CHECK_SCANF1 (-19876, "-19876", "%w16i");
> +    CHECK_SCANF1 (16384, "0b100000000000000", "%w16i");
> +    CHECK_SCANF1 (32767, "0x7fff", "%w16i");
> +    CHECK_SCANF1 (-16383, "-037777", "%w16i");
> +  }
> +  {
> +    uint_least16_t var;
> +    int_least16_t n;
> +    CHECK_SCANF1 (32767, "111111111111111", "%w16b");
> +    CHECK_SCANF1 (4095, "07777", "%w16o");
> +    CHECK_SCANF1 (9999, "9999", "%w16u");
> +    CHECK_SCANF1 (23456, "5ba0", "%w16x");
> +    CHECK_SCANF1N (23456, "5ba0", "%w16x%w16n");
> +    CHECK_SCANF1 (23457, "5ba1", "%w16X");
> +  }
> +}
> +

Ok.

> +static void
> +test_wf16 (void)
> +{
> +  {
> +    int_fast16_t var, n;
> +    CHECK_SCANF1 (12345, "12345", "%wf16d");
> +    CHECK_SCANF1N (23456, "23456", "%wf16d%wf16n");
> +    CHECK_SCANF1 (-10101, "-10101", "%wf16d");
> +    CHECK_SCANF1 (30000, "30000", "%wf16i");
> +    CHECK_SCANF1 (-19876, "-19876", "%wf16i");
> +    CHECK_SCANF1 (16384, "0b100000000000000", "%wf16i");
> +    CHECK_SCANF1 (32767, "0x7fff", "%wf16i");
> +    CHECK_SCANF1 (-16383, "-037777", "%wf16i");
> +  }
> +  {
> +    uint_fast16_t var;
> +    int_fast16_t n;
> +    CHECK_SCANF1 (32767, "111111111111111", "%wf16b");
> +    CHECK_SCANF1 (4095, "07777", "%wf16o");
> +    CHECK_SCANF1 (9999, "9999", "%wf16u");
> +    CHECK_SCANF1 (23456, "5ba0", "%wf16x");
> +    CHECK_SCANF1N (23456, "5ba0", "%wf16x%wf16n");
> +    CHECK_SCANF1 (23457, "5ba1", "%wf16X");
> +  }
> +#if INT_FAST16_MAX >= INT32_MAX
> +  {
> +    int_fast16_t var, n;
> +    CHECK_SCANF1 (1234567, "1234567", "%wf16d");
> +    CHECK_SCANF1N (2345678, "2345678", "%wf16d%wf16n");
> +    CHECK_SCANF1 (-1010101, "-1010101", "%wf16d");
> +    CHECK_SCANF1 (3000000, "3000000", "%wf16i");
> +    CHECK_SCANF1 (-98765432, "-98765432", "%wf16i");
> +    CHECK_SCANF1 (1048576, "0b100000000000000000000", "%wf16i");
> +    CHECK_SCANF1 (1048575, "0xfffff", "%wf16i");
> +    CHECK_SCANF1 (-1048575, "-03777777", "%wf16i");
> +  }
> +  {
> +    uint_fast16_t var;
> +    int_fast16_t n;
> +    CHECK_SCANF1 (1234567, "100101101011010000111", "%wf16b");
> +    CHECK_SCANF1 (1048575, "03777777", "%wf16o");
> +    CHECK_SCANF1 (999999, "999999", "%wf16u");
> +    CHECK_SCANF1 (987654, "f1206", "%wf16x");
> +    CHECK_SCANF1N (987654, "f1206", "%wf16x%wf16n");
> +    CHECK_SCANF1 (987655, "f1207", "%wf16X");
> +  }
> +#endif
> +#if INT_FAST16_MAX >= INT64_MAX
> +  {
> +    int_fast16_t var, n;
> +    CHECK_SCANF1 (123456789012LL, "123456789012", "%wf16d");
> +    CHECK_SCANF1N (234567890123LL, "234567890123", "%wf16d%wf16n");
> +    CHECK_SCANF1 (-10101010101LL, "-10101010101", "%wf16d");
> +    CHECK_SCANF1 (3000000000000000LL, "3000000000000000", "%wf16i");
> +    CHECK_SCANF1 (-9876543210LL, "-9876543210", "%wf16i");
> +    CHECK_SCANF1 (1LL << 40, "0b10000000000000000000000000000000000000000",
> +		  "%wf16i");
> +    CHECK_SCANF1 (1LL << 41, "0x20000000000", "%wf16i");
> +    CHECK_SCANF1 (-(1LL << 42), "-0100000000000000", "%wf16i");
> +  }
> +  {
> +    uint_fast16_t var;
> +    int_fast16_t n;
> +    CHECK_SCANF1 (123456789012ULL, "1110010111110100110010001101000010100",
> +		  "%wf16b");
> +    CHECK_SCANF1 (1ULL << 40, "20000000000000", "%wf16o");
> +    CHECK_SCANF1 (999999999999ULL, "999999999999", "%wf16u");
> +    CHECK_SCANF1 (9876543210ULL, "24cb016ea", "%wf16x");
> +    CHECK_SCANF1N (9876543210ULL, "24cb016ea", "%wf16x%wf16n");
> +    CHECK_SCANF1 (9876543211ULL, "24cb016eb", "%wf16X");
> +  }
> +#endif
> +}
> +

Ok.

> +static void
> +test_w32 (void)
> +{
> +  {
> +    int32_t var, n;
> +    CHECK_SCANF1 (1234567, "1234567", "%w32d");
> +    CHECK_SCANF1N (2345678, "2345678", "%w32d%w32n");
> +    CHECK_SCANF1 (-1010101, "-1010101", "%w32d");
> +    CHECK_SCANF1 (3000000, "3000000", "%w32i");
> +    CHECK_SCANF1 (-98765432, "-98765432", "%w32i");
> +    CHECK_SCANF1 (1048576, "0b100000000000000000000", "%w32i");
> +    CHECK_SCANF1 (1048575, "0xfffff", "%w32i");
> +    CHECK_SCANF1 (-1048575, "-03777777", "%w32i");
> +  }
> +  {
> +    uint32_t var;
> +    int32_t n;
> +    CHECK_SCANF1 (1234567, "100101101011010000111", "%w32b");
> +    CHECK_SCANF1 (1048575, "03777777", "%w32o");
> +    CHECK_SCANF1 (999999, "999999", "%w32u");
> +    CHECK_SCANF1 (987654, "f1206", "%w32x");
> +    CHECK_SCANF1N (987654, "f1206", "%w32x%w32n");
> +    CHECK_SCANF1 (987655, "f1207", "%w32X");
> +  }
> +  {
> +    int_least32_t var, n;
> +    CHECK_SCANF1 (1234567, "1234567", "%w32d");
> +    CHECK_SCANF1N (2345678, "2345678", "%w32d%w32n");
> +    CHECK_SCANF1 (-1010101, "-1010101", "%w32d");
> +    CHECK_SCANF1 (3000000, "3000000", "%w32i");
> +    CHECK_SCANF1 (-98765432, "-98765432", "%w32i");
> +    CHECK_SCANF1 (1048576, "0b100000000000000000000", "%w32i");
> +    CHECK_SCANF1 (1048575, "0xfffff", "%w32i");
> +    CHECK_SCANF1 (-1048575, "-03777777", "%w32i");
> +  }
> +  {
> +    uint_least32_t var;
> +    int_least32_t n;
> +    CHECK_SCANF1 (1234567, "100101101011010000111", "%w32b");
> +    CHECK_SCANF1 (1048575, "03777777", "%w32o");
> +    CHECK_SCANF1 (999999, "999999", "%w32u");
> +    CHECK_SCANF1 (987654, "f1206", "%w32x");
> +    CHECK_SCANF1N (987654, "f1206", "%w32x%w32n");
> +    CHECK_SCANF1 (987655, "f1207", "%w32X");
> +  }
> +}
> +

Ok.

> +static void
> +test_wf32 (void)
> +{
> +  {
> +    int_fast32_t var, n;
> +    CHECK_SCANF1 (1234567, "1234567", "%wf32d");
> +    CHECK_SCANF1N (2345678, "2345678", "%wf32d%wf32n");
> +    CHECK_SCANF1 (-1010101, "-1010101", "%wf32d");
> +    CHECK_SCANF1 (3000000, "3000000", "%wf32i");
> +    CHECK_SCANF1 (-98765432, "-98765432", "%wf32i");
> +    CHECK_SCANF1 (1048576, "0b100000000000000000000", "%wf32i");
> +    CHECK_SCANF1 (1048575, "0xfffff", "%wf32i");
> +    CHECK_SCANF1 (-1048575, "-03777777", "%wf32i");
> +  }
> +  {
> +    uint_fast32_t var;
> +    int_fast32_t n;
> +    CHECK_SCANF1 (1234567, "100101101011010000111", "%wf32b");
> +    CHECK_SCANF1 (1048575, "03777777", "%wf32o");
> +    CHECK_SCANF1 (999999, "999999", "%wf32u");
> +    CHECK_SCANF1 (987654, "f1206", "%wf32x");
> +    CHECK_SCANF1N (987654, "f1206", "%wf32x%wf32n");
> +    CHECK_SCANF1 (987655, "f1207", "%wf32X");
> +  }
> +#if INT_FAST32_MAX >= INT64_MAX
> +  {
> +    int_fast32_t var, n;
> +    CHECK_SCANF1 (123456789012LL, "123456789012", "%wf32d");
> +    CHECK_SCANF1N (234567890123LL, "234567890123", "%wf32d%wf32n");
> +    CHECK_SCANF1 (-10101010101LL, "-10101010101", "%wf32d");
> +    CHECK_SCANF1 (3000000000000000LL, "3000000000000000", "%wf32i");
> +    CHECK_SCANF1 (-9876543210LL, "-9876543210", "%wf32i");
> +    CHECK_SCANF1 (1LL << 40, "0b10000000000000000000000000000000000000000",
> +		  "%wf32i");
> +    CHECK_SCANF1 (1LL << 41, "0x20000000000", "%wf32i");
> +    CHECK_SCANF1 (-(1LL << 42), "-0100000000000000", "%wf32i");
> +  }
> +  {
> +    uint_fast32_t var;
> +    int_fast32_t n;
> +    CHECK_SCANF1 (123456789012ULL, "1110010111110100110010001101000010100",
> +		  "%wf32b");
> +    CHECK_SCANF1 (1ULL << 40, "20000000000000", "%wf32o");
> +    CHECK_SCANF1 (999999999999ULL, "999999999999", "%wf32u");
> +    CHECK_SCANF1 (9876543210ULL, "24cb016ea", "%wf32x");
> +    CHECK_SCANF1N (9876543210ULL, "24cb016ea", "%wf32x%wf32n");
> +    CHECK_SCANF1 (9876543211ULL, "24cb016eb", "%wf32X");
> +  }
> +#endif
> +}
> +

Ok.

> +static void
> +test_w64 (void)
> +{
> +  {
> +    int64_t var, n;
> +    CHECK_SCANF1 (123456789012LL, "123456789012", "%w64d");
> +    CHECK_SCANF1N (234567890123LL, "234567890123", "%w64d%w64n");
> +    CHECK_SCANF1 (-10101010101LL, "-10101010101", "%w64d");
> +    CHECK_SCANF1 (3000000000000000LL, "3000000000000000", "%w64i");
> +    CHECK_SCANF1 (-9876543210LL, "-9876543210", "%w64i");
> +    CHECK_SCANF1 (1LL << 40, "0b10000000000000000000000000000000000000000",
> +		  "%w64i");
> +    CHECK_SCANF1 (1LL << 41, "0x20000000000", "%w64i");
> +    CHECK_SCANF1 (-(1LL << 42), "-0100000000000000", "%w64i");
> +  }
> +  {
> +    uint64_t var;
> +    int64_t n;
> +    CHECK_SCANF1 (123456789012ULL, "1110010111110100110010001101000010100",
> +		  "%w64b");
> +    CHECK_SCANF1 (1ULL << 40, "20000000000000", "%w64o");
> +    CHECK_SCANF1 (999999999999ULL, "999999999999", "%w64u");
> +    CHECK_SCANF1 (9876543210ULL, "24cb016ea", "%w64x");
> +    CHECK_SCANF1N (9876543210ULL, "24cb016ea", "%w64x%w64n");
> +    CHECK_SCANF1 (9876543211ULL, "24cb016eb", "%w64X");
> +  }
> +  {
> +    int_least64_t var, n;
> +    CHECK_SCANF1 (123456789012LL, "123456789012", "%w64d");
> +    CHECK_SCANF1N (234567890123LL, "234567890123", "%w64d%w64n");
> +    CHECK_SCANF1 (-10101010101LL, "-10101010101", "%w64d");
> +    CHECK_SCANF1 (3000000000000000LL, "3000000000000000", "%w64i");
> +    CHECK_SCANF1 (-9876543210LL, "-9876543210", "%w64i");
> +    CHECK_SCANF1 (1LL << 40, "0b10000000000000000000000000000000000000000",
> +		  "%w64i");
> +    CHECK_SCANF1 (1LL << 41, "0x20000000000", "%w64i");
> +    CHECK_SCANF1 (-(1LL << 42), "-0100000000000000", "%w64i");
> +  }
> +  {
> +    uint_least64_t var;
> +    int_least64_t n;
> +    CHECK_SCANF1 (123456789012ULL, "1110010111110100110010001101000010100",
> +		  "%w64b");
> +    CHECK_SCANF1 (1ULL << 40, "20000000000000", "%w64o");
> +    CHECK_SCANF1 (999999999999ULL, "999999999999", "%w64u");
> +    CHECK_SCANF1 (9876543210ULL, "24cb016ea", "%w64x");
> +    CHECK_SCANF1N (9876543210ULL, "24cb016ea", "%w64x%w64n");
> +    CHECK_SCANF1 (9876543211ULL, "24cb016eb", "%w64X");
> +  }
> +}
> +

Ok.

> +static void
> +test_wf64 (void)
> +{
> +  {
> +    int_fast64_t var, n;
> +    CHECK_SCANF1 (123456789012LL, "123456789012", "%wf64d");
> +    CHECK_SCANF1N (234567890123LL, "234567890123", "%wf64d%wf64n");
> +    CHECK_SCANF1 (-10101010101LL, "-10101010101", "%wf64d");
> +    CHECK_SCANF1 (3000000000000000LL, "3000000000000000", "%wf64i");
> +    CHECK_SCANF1 (-9876543210LL, "-9876543210", "%wf64i");
> +    CHECK_SCANF1 (1LL << 40, "0b10000000000000000000000000000000000000000",
> +		  "%wf64i");
> +    CHECK_SCANF1 (1LL << 41, "0x20000000000", "%wf64i");
> +    CHECK_SCANF1 (-(1LL << 42), "-0100000000000000", "%wf64i");
> +  }
> +  {
> +    uint_fast64_t var;
> +    int_fast64_t n;
> +    CHECK_SCANF1 (123456789012ULL, "1110010111110100110010001101000010100",
> +		  "%wf64b");
> +    CHECK_SCANF1 (1ULL << 40, "20000000000000", "%wf64o");
> +    CHECK_SCANF1 (999999999999ULL, "999999999999", "%wf64u");
> +    CHECK_SCANF1 (9876543210ULL, "24cb016ea", "%wf64x");
> +    CHECK_SCANF1N (9876543210ULL, "24cb016ea", "%wf64x%wf64n");
> +    CHECK_SCANF1 (9876543211ULL, "24cb016eb", "%wf64X");
> +  }
> +}
> +
> +static int
> +do_test (void)
> +{
> +  int a, b;
> +  test_w8 ();
> +  test_wf8 ();
> +  test_w16 ();
> +  test_wf16 ();
> +  test_w32 ();
> +  test_wf32 ();
> +  test_w64 ();
> +  test_wf64 ();
> +  /* Bad N in %wN and %wfN are required to produce an error return (of
> +     the number of input items assigned) from scanf functions (and can
> +     also be seen to be invalid at compile time).  */
> +  DIAG_PUSH_NEEDS_COMMENT;
> +  DIAG_IGNORE_NEEDS_COMMENT (13, "-Wformat");
> +  DIAG_IGNORE_NEEDS_COMMENT (13, "-Wformat-extra-args");
> +  CHECK_SCANF_ERR (0, "1", "%w1d", &a);
> +  CHECK_SCANF_ERR (0, "1", "%w123d", &a);
> +  CHECK_SCANF_ERR (0, "1", "%w99999999999999999999d", &a);
> +  CHECK_SCANF_ERR (0, "1", "%wf1d", &a);
> +  CHECK_SCANF_ERR (0, "1", "%wf123d", &a);
> +  CHECK_SCANF_ERR (0, "1", "%wf99999999999999999999d", &a);
> +  CHECK_SCANF_ERR (1, "1 1", "%d %w1d", &a, &b);
> +  CHECK_SCANF_ERR (1, "1 1", "%d %w123d", &a, &b);
> +  CHECK_SCANF_ERR (1, "1 1", "%d %w99999999999999999999d", &a, &b);
> +  CHECK_SCANF_ERR (1, "1 1", "%d %wf1d", &a, &b);
> +  CHECK_SCANF_ERR (1, "1 1", "%d %wf123d", &a, &b);
> +  CHECK_SCANF_ERR (1, "1 1", "%d %wf99999999999999999999d", &a, &b);
> +  CHECK_SCANF_ERR (0, "1", "%1$w1d", &a);
> +  CHECK_SCANF_ERR (0, "1", "%1$w123d", &a);
> +  CHECK_SCANF_ERR (0, "1", "%1$w99999999999999999999d", &a);
> +  CHECK_SCANF_ERR (0, "1", "%1$wf1d", &a);
> +  CHECK_SCANF_ERR (0, "1", "%1$wf123d", &a);
> +  CHECK_SCANF_ERR (0, "1", "%1$wf99999999999999999999d", &a);
> +  DIAG_POP_NEEDS_COMMENT;
> +  return 0;
> +}
> +
> +DIAG_POP_NEEDS_COMMENT;
> +
> +#include <support/test-driver.c>

Ok.

> diff --git a/stdio-common/tst-scanf-intn.c b/stdio-common/tst-scanf-intn.c
> new file mode 100644
> index 0000000000..876eda30ee
> --- /dev/null
> +++ b/stdio-common/tst-scanf-intn.c
> @@ -0,0 +1,24 @@
> +/* Test scanf formats for intN_t, int_leastN_t and int_fastN_t types.
> +   Narrow string version.
> +   Copyright (C) 2023 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#define SSCANF sscanf
> +#define STRLEN strlen
> +#define L_(C) C
> +
> +#include <tst-scanf-intn-main.c>

Ok.

> diff --git a/stdio-common/vfscanf-internal.c b/stdio-common/vfscanf-internal.c
> index 9b1197d751..cf94af4f54 100644
> --- a/stdio-common/vfscanf-internal.c
> +++ b/stdio-common/vfscanf-internal.c
> @@ -381,6 +381,7 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
>    while (*f != '\0')
>      {
>        unsigned int argpos;
> +      bool is_fast;
>        /* Extract the next argument, which is of type TYPE.
>  	 For a %N$... spec, this is the Nth argument from the beginning;
>  	 otherwise it is the next argument after the state now in ARG.  */
> @@ -602,6 +603,51 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
>  	  else if (sizeof (ptrdiff_t) > sizeof (int))
>  	    flags |= LONG;
>  	  break;
> +	case L_('w'):
> +	  is_fast = false;
> +	  if (*f == L_('f'))
> +	    {
> +	      ++f;
> +	      is_fast = true;
> +	    }
> +	  int bitwidth = 0;

Maybe it would be better to put this declaration on its own scope
to avoid it escape on possible future extensions (at least this is
what we are recently doing on tests).

> +	  if (ISDIGIT (*f))
> +	    bitwidth = read_int (&f);
> +	  if (is_fast)
> +	    switch (bitwidth)
> +	      {
> +	      case 8:
> +		bitwidth = INT_FAST8_WIDTH;
> +		break;

Isn't this redundant (at least for current supported INT_FAST8_WIDTH)?

> +	      case 16:
> +		bitwidth = INT_FAST16_WIDTH;
> +		break;
> +	      case 32:
> +		bitwidth = INT_FAST32_WIDTH;
> +		break;
> +	      case 64:
> +		bitwidth = INT_FAST64_WIDTH;
> +		break;
> +	      }
> +	  switch (bitwidth)
> +	    {
> +	    case 8:
> +	      flags |= CHAR;
> +	      break;
> +	    case 16:
> +	      flags |= SHORT;
> +	      break;
> +	    case 32:
> +	      break;
> +	    case 64:
> +	      flags |= LONGDBL | LONG;
> +	      break;
> +	    default:
> +	      /* ISO C requires this error to be detected.  */
> +	      __set_errno (EINVAL);
> +	      goto errout;
> +	    }
> +	  break;
>  	default:
>  	  /* Not a recognized modifier.  Backup.  */
>  	  --f;

Ok.

> diff --git a/wcsmbs/Makefile b/wcsmbs/Makefile
> index 431136b9c9..cde9a3221e 100644
> --- a/wcsmbs/Makefile
> +++ b/wcsmbs/Makefile
> @@ -196,6 +196,7 @@ tests := \
>    tst-wscanf-binary-c2x \
>    tst-wscanf-binary-gnu11 \
>    tst-wscanf-binary-gnu89 \
> +  tst-wscanf-intn \
>    tst-wscanf-to_inpunct \
>    wcsatcliff \
>    wcsmbs-tst1 \

Ok.

> diff --git a/wcsmbs/tst-wscanf-intn.c b/wcsmbs/tst-wscanf-intn.c
> new file mode 100644
> index 0000000000..8b6e6d3c0e
> --- /dev/null
> +++ b/wcsmbs/tst-wscanf-intn.c
> @@ -0,0 +1,24 @@
> +/* Test scanf formats for intN_t, int_leastN_t and int_fastN_t types.
> +   Wide string version.
> +   Copyright (C) 2023 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#define SSCANF swscanf
> +#define STRLEN wcslen
> +#define L_(C) L ## C
> +
> +#include "../stdio-common/tst-scanf-intn-main.c"
> 

Ok.
Joseph Myers Sept. 28, 2023, 5:31 p.m. UTC | #4
On Wed, 27 Sep 2023, Adhemerval Zanella Netto wrote:

> > +	case L_('w'):
> > +	  is_fast = false;
> > +	  if (*f == L_('f'))
> > +	    {
> > +	      ++f;
> > +	      is_fast = true;
> > +	    }
> > +	  int bitwidth = 0;
> 
> Maybe it would be better to put this declaration on its own scope
> to avoid it escape on possible future extensions (at least this is
> what we are recently doing on tests).

I've committed the patch below, which adds a scope for this case within 
the switch.

> > +	  if (ISDIGIT (*f))
> > +	    bitwidth = read_int (&f);
> > +	  if (is_fast)
> > +	    switch (bitwidth)
> > +	      {
> > +	      case 8:
> > +		bitwidth = INT_FAST8_WIDTH;
> > +		break;
> 
> Isn't this redundant (at least for current supported INT_FAST8_WIDTH)?

Redundant, but writing this way, consistent among all the widths, seems 
cleaner to me than the alternative of a _Static_assert about the width of 
int_fast8_t (let alone quietly hardcoding that dependency here without 
such an assertion).

Committed patch version follows:

C2x scanf %wN, %wfN support

ISO C2x defines scanf length modifiers wN (for intN_t / int_leastN_t /
uintN_t / uint_leastN_t) and wfN (for int_fastN_t / uint_fastN_t).
Add support for those length modifiers, similar to the printf support
previously added.

Tested for x86_64 and x86.

diff --git a/NEWS b/NEWS
index 5b7b327b29..a94650da64 100644
--- a/NEWS
+++ b/NEWS
@@ -31,6 +31,13 @@ Major new features:
   the process ID associated with the process file descriptor created by
   pid_spawn, fork_np, or pidfd_open.
 
+* scanf-family functions now support the wN format length modifiers for
+  arguments pointing to types intN_t, int_leastN_t, uintN_t or
+  uint_leastN_t (for example, %w32d to read int32_t or int_least32_t in
+  decimal, or %w32x to read uint32_t or uint_least32_t in hexadecimal)
+  and the wfN format length modifiers for arguments pointing to types
+  int_fastN_t or uint_fastN_t, as specified in draft ISO C2X.
+
 Deprecated and removed features, and other changes affecting compatibility:
 
   [Add deprecations, removals and changes affecting compatibility here]
diff --git a/manual/stdio.texi b/manual/stdio.texi
index 9cf622403f..002fce7a10 100644
--- a/manual/stdio.texi
+++ b/manual/stdio.texi
@@ -3714,6 +3714,20 @@ Specifies that the argument is a @code{ptrdiff_t *}.
 
 This modifier was introduced in @w{ISO C99}.
 
+@item w@var{n}
+Specifies that the argument is an @code{int@var{n}_t *} or
+@code{int_least@var{n}_t *} (which are the same type), or
+@code{uint@var{n}_t *} or @code{uint_least@var{n}_t *} (which are the
+same type).
+
+This modifier was introduced in @w{ISO C2X}.
+
+@item wf@var{n}
+Specifies that the argument is an @code{int_fast@var{n}_t *} or
+@code{uint_fast@var{n}_t *}.
+
+This modifier was introduced in @w{ISO C2X}.
+
 @item z
 Specifies that the argument is a @code{size_t *}.
 
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index 3866362bae..bacb795fed 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -243,6 +243,7 @@ tests := \
   tst-scanf-binary-c2x \
   tst-scanf-binary-gnu11 \
   tst-scanf-binary-gnu89 \
+  tst-scanf-intn \
   tst-scanf-round \
   tst-scanf-to_inpunct \
   tst-setvbuf1 \
diff --git a/stdio-common/tst-scanf-intn-main.c b/stdio-common/tst-scanf-intn-main.c
new file mode 100644
index 0000000000..fa0462a8de
--- /dev/null
+++ b/stdio-common/tst-scanf-intn-main.c
@@ -0,0 +1,479 @@
+/* Test scanf formats for intN_t, int_leastN_t and int_fastN_t types.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <wchar.h>
+
+#include <libc-diag.h>
+#include <support/check.h>
+
+/* GCC does not know the %wN or %wfN length modifiers before GCC 13.  */
+DIAG_PUSH_NEEDS_COMMENT;
+#if !__GNUC_PREREQ (13, 0)
+DIAG_IGNORE_NEEDS_COMMENT (12, "-Wformat");
+DIAG_IGNORE_NEEDS_COMMENT (12, "-Wformat-extra-args");
+#endif
+
+#define CHECK_SCANF1(EXPECTED, STR, FMT)				\
+  do									\
+    {									\
+      var = ((typeof (var)) 0xabababab);				\
+      int ret = SSCANF (L_(STR), L_(FMT), &var);			\
+      TEST_COMPARE (var, (EXPECTED));					\
+      TEST_COMPARE (ret, 1);						\
+    }									\
+  while (0)
+
+#define CHECK_SCANF1N(EXPECTED, STR, FMT)				\
+  do									\
+    {									\
+      var = ((typeof (var)) 0xabababab);				\
+      n = 123;								\
+      int ret = SSCANF (L_(STR), L_(FMT), &var, &n);			\
+      TEST_COMPARE (var, (EXPECTED));					\
+      TEST_COMPARE (n, STRLEN (L_(STR)));				\
+      TEST_COMPARE (ret, 1);						\
+    }									\
+  while (0)
+
+#define CHECK_SCANF_ERR(OK, STR, FMT, ...)				\
+  do									\
+    {									\
+      int ret = SSCANF (L_(STR), L_(FMT), __VA_ARGS__);			\
+      TEST_VERIFY (ret == (OK));					\
+      TEST_COMPARE (errno, EINVAL);					\
+    }									\
+  while (0)
+
+static void
+test_w8 (void)
+{
+  {
+    int8_t var, n;
+    CHECK_SCANF1 (42, "42", "%w8d");
+    CHECK_SCANF1N (42, "42", "%w8d%w8n");
+    CHECK_SCANF1 (-43, "-43", "%w8d");
+    CHECK_SCANF1 (42, "42", "%w8i");
+    CHECK_SCANF1 (-43, "-43", "%w8i");
+    CHECK_SCANF1 (123, "0b1111011", "%w8i");
+    CHECK_SCANF1 (127, "0x7f", "%w8i");
+    CHECK_SCANF1 (-19, "-023", "%w8i");
+  }
+  {
+    uint8_t var;
+    int8_t n;
+    CHECK_SCANF1 (123, "1111011", "%w8b");
+    CHECK_SCANF1 (19, "023", "%w8o");
+    CHECK_SCANF1 (50, "50", "%w8u");
+    CHECK_SCANF1 (65, "41", "%w8x");
+    CHECK_SCANF1N (65, "41", "%w8x%w8n");
+    CHECK_SCANF1 (66, "42", "%w8X");
+  }
+  {
+    int_least8_t var, n;
+    CHECK_SCANF1 (42, "42", "%w8d");
+    CHECK_SCANF1N (42, "42", "%w8d%w8n");
+    CHECK_SCANF1 (-43, "-43", "%w8d");
+    CHECK_SCANF1 (42, "42", "%w8i");
+    CHECK_SCANF1 (-43, "-43", "%w8i");
+    CHECK_SCANF1 (123, "0b1111011", "%w8i");
+    CHECK_SCANF1 (127, "0x7f", "%w8i");
+    CHECK_SCANF1 (-19, "-023", "%w8i");
+  }
+  {
+    uint_least8_t var;
+    int_least8_t n;
+    CHECK_SCANF1 (123, "1111011", "%w8b");
+    CHECK_SCANF1 (19, "023", "%w8o");
+    CHECK_SCANF1 (50, "50", "%w8u");
+    CHECK_SCANF1 (65, "41", "%w8x");
+    CHECK_SCANF1N (65, "41", "%w8x%w8n");
+    CHECK_SCANF1 (66, "42", "%w8X");
+  }
+}
+
+static void
+test_wf8 (void)
+{
+  {
+    int_fast8_t var, n;
+    CHECK_SCANF1 (42, "42", "%wf8d");
+    CHECK_SCANF1N (42, "42", "%wf8d%wf8n");
+    CHECK_SCANF1 (-43, "-43", "%wf8d");
+    CHECK_SCANF1 (42, "42", "%wf8i");
+    CHECK_SCANF1 (-43, "-43", "%wf8i");
+    CHECK_SCANF1 (123, "0b1111011", "%wf8i");
+    CHECK_SCANF1 (127, "0x7f", "%wf8i");
+    CHECK_SCANF1 (-19, "-023", "%wf8i");
+  }
+  {
+    uint_fast8_t var;
+    int_fast8_t n;
+    CHECK_SCANF1 (123, "1111011", "%wf8b");
+    CHECK_SCANF1 (19, "023", "%wf8o");
+    CHECK_SCANF1 (50, "50", "%wf8u");
+    CHECK_SCANF1 (65, "41", "%wf8x");
+    CHECK_SCANF1N (65, "41", "%wf8x%wf8n");
+    CHECK_SCANF1 (66, "42", "%wf8X");
+  }
+}
+
+static void
+test_w16 (void)
+{
+  {
+    int16_t var, n;
+    CHECK_SCANF1 (12345, "12345", "%w16d");
+    CHECK_SCANF1N (23456, "23456", "%w16d%w16n");
+    CHECK_SCANF1 (-10101, "-10101", "%w16d");
+    CHECK_SCANF1 (30000, "30000", "%w16i");
+    CHECK_SCANF1 (-19876, "-19876", "%w16i");
+    CHECK_SCANF1 (16384, "0b100000000000000", "%w16i");
+    CHECK_SCANF1 (32767, "0x7fff", "%w16i");
+    CHECK_SCANF1 (-16383, "-037777", "%w16i");
+  }
+  {
+    uint16_t var;
+    int16_t n;
+    CHECK_SCANF1 (32767, "111111111111111", "%w16b");
+    CHECK_SCANF1 (4095, "07777", "%w16o");
+    CHECK_SCANF1 (9999, "9999", "%w16u");
+    CHECK_SCANF1 (23456, "5ba0", "%w16x");
+    CHECK_SCANF1N (23456, "5ba0", "%w16x%w16n");
+    CHECK_SCANF1 (23457, "5ba1", "%w16X");
+  }
+  {
+    int_least16_t var, n;
+    CHECK_SCANF1 (12345, "12345", "%w16d");
+    CHECK_SCANF1N (23456, "23456", "%w16d%w16n");
+    CHECK_SCANF1 (-10101, "-10101", "%w16d");
+    CHECK_SCANF1 (30000, "30000", "%w16i");
+    CHECK_SCANF1 (-19876, "-19876", "%w16i");
+    CHECK_SCANF1 (16384, "0b100000000000000", "%w16i");
+    CHECK_SCANF1 (32767, "0x7fff", "%w16i");
+    CHECK_SCANF1 (-16383, "-037777", "%w16i");
+  }
+  {
+    uint_least16_t var;
+    int_least16_t n;
+    CHECK_SCANF1 (32767, "111111111111111", "%w16b");
+    CHECK_SCANF1 (4095, "07777", "%w16o");
+    CHECK_SCANF1 (9999, "9999", "%w16u");
+    CHECK_SCANF1 (23456, "5ba0", "%w16x");
+    CHECK_SCANF1N (23456, "5ba0", "%w16x%w16n");
+    CHECK_SCANF1 (23457, "5ba1", "%w16X");
+  }
+}
+
+static void
+test_wf16 (void)
+{
+  {
+    int_fast16_t var, n;
+    CHECK_SCANF1 (12345, "12345", "%wf16d");
+    CHECK_SCANF1N (23456, "23456", "%wf16d%wf16n");
+    CHECK_SCANF1 (-10101, "-10101", "%wf16d");
+    CHECK_SCANF1 (30000, "30000", "%wf16i");
+    CHECK_SCANF1 (-19876, "-19876", "%wf16i");
+    CHECK_SCANF1 (16384, "0b100000000000000", "%wf16i");
+    CHECK_SCANF1 (32767, "0x7fff", "%wf16i");
+    CHECK_SCANF1 (-16383, "-037777", "%wf16i");
+  }
+  {
+    uint_fast16_t var;
+    int_fast16_t n;
+    CHECK_SCANF1 (32767, "111111111111111", "%wf16b");
+    CHECK_SCANF1 (4095, "07777", "%wf16o");
+    CHECK_SCANF1 (9999, "9999", "%wf16u");
+    CHECK_SCANF1 (23456, "5ba0", "%wf16x");
+    CHECK_SCANF1N (23456, "5ba0", "%wf16x%wf16n");
+    CHECK_SCANF1 (23457, "5ba1", "%wf16X");
+  }
+#if INT_FAST16_MAX >= INT32_MAX
+  {
+    int_fast16_t var, n;
+    CHECK_SCANF1 (1234567, "1234567", "%wf16d");
+    CHECK_SCANF1N (2345678, "2345678", "%wf16d%wf16n");
+    CHECK_SCANF1 (-1010101, "-1010101", "%wf16d");
+    CHECK_SCANF1 (3000000, "3000000", "%wf16i");
+    CHECK_SCANF1 (-98765432, "-98765432", "%wf16i");
+    CHECK_SCANF1 (1048576, "0b100000000000000000000", "%wf16i");
+    CHECK_SCANF1 (1048575, "0xfffff", "%wf16i");
+    CHECK_SCANF1 (-1048575, "-03777777", "%wf16i");
+  }
+  {
+    uint_fast16_t var;
+    int_fast16_t n;
+    CHECK_SCANF1 (1234567, "100101101011010000111", "%wf16b");
+    CHECK_SCANF1 (1048575, "03777777", "%wf16o");
+    CHECK_SCANF1 (999999, "999999", "%wf16u");
+    CHECK_SCANF1 (987654, "f1206", "%wf16x");
+    CHECK_SCANF1N (987654, "f1206", "%wf16x%wf16n");
+    CHECK_SCANF1 (987655, "f1207", "%wf16X");
+  }
+#endif
+#if INT_FAST16_MAX >= INT64_MAX
+  {
+    int_fast16_t var, n;
+    CHECK_SCANF1 (123456789012LL, "123456789012", "%wf16d");
+    CHECK_SCANF1N (234567890123LL, "234567890123", "%wf16d%wf16n");
+    CHECK_SCANF1 (-10101010101LL, "-10101010101", "%wf16d");
+    CHECK_SCANF1 (3000000000000000LL, "3000000000000000", "%wf16i");
+    CHECK_SCANF1 (-9876543210LL, "-9876543210", "%wf16i");
+    CHECK_SCANF1 (1LL << 40, "0b10000000000000000000000000000000000000000",
+		  "%wf16i");
+    CHECK_SCANF1 (1LL << 41, "0x20000000000", "%wf16i");
+    CHECK_SCANF1 (-(1LL << 42), "-0100000000000000", "%wf16i");
+  }
+  {
+    uint_fast16_t var;
+    int_fast16_t n;
+    CHECK_SCANF1 (123456789012ULL, "1110010111110100110010001101000010100",
+		  "%wf16b");
+    CHECK_SCANF1 (1ULL << 40, "20000000000000", "%wf16o");
+    CHECK_SCANF1 (999999999999ULL, "999999999999", "%wf16u");
+    CHECK_SCANF1 (9876543210ULL, "24cb016ea", "%wf16x");
+    CHECK_SCANF1N (9876543210ULL, "24cb016ea", "%wf16x%wf16n");
+    CHECK_SCANF1 (9876543211ULL, "24cb016eb", "%wf16X");
+  }
+#endif
+}
+
+static void
+test_w32 (void)
+{
+  {
+    int32_t var, n;
+    CHECK_SCANF1 (1234567, "1234567", "%w32d");
+    CHECK_SCANF1N (2345678, "2345678", "%w32d%w32n");
+    CHECK_SCANF1 (-1010101, "-1010101", "%w32d");
+    CHECK_SCANF1 (3000000, "3000000", "%w32i");
+    CHECK_SCANF1 (-98765432, "-98765432", "%w32i");
+    CHECK_SCANF1 (1048576, "0b100000000000000000000", "%w32i");
+    CHECK_SCANF1 (1048575, "0xfffff", "%w32i");
+    CHECK_SCANF1 (-1048575, "-03777777", "%w32i");
+  }
+  {
+    uint32_t var;
+    int32_t n;
+    CHECK_SCANF1 (1234567, "100101101011010000111", "%w32b");
+    CHECK_SCANF1 (1048575, "03777777", "%w32o");
+    CHECK_SCANF1 (999999, "999999", "%w32u");
+    CHECK_SCANF1 (987654, "f1206", "%w32x");
+    CHECK_SCANF1N (987654, "f1206", "%w32x%w32n");
+    CHECK_SCANF1 (987655, "f1207", "%w32X");
+  }
+  {
+    int_least32_t var, n;
+    CHECK_SCANF1 (1234567, "1234567", "%w32d");
+    CHECK_SCANF1N (2345678, "2345678", "%w32d%w32n");
+    CHECK_SCANF1 (-1010101, "-1010101", "%w32d");
+    CHECK_SCANF1 (3000000, "3000000", "%w32i");
+    CHECK_SCANF1 (-98765432, "-98765432", "%w32i");
+    CHECK_SCANF1 (1048576, "0b100000000000000000000", "%w32i");
+    CHECK_SCANF1 (1048575, "0xfffff", "%w32i");
+    CHECK_SCANF1 (-1048575, "-03777777", "%w32i");
+  }
+  {
+    uint_least32_t var;
+    int_least32_t n;
+    CHECK_SCANF1 (1234567, "100101101011010000111", "%w32b");
+    CHECK_SCANF1 (1048575, "03777777", "%w32o");
+    CHECK_SCANF1 (999999, "999999", "%w32u");
+    CHECK_SCANF1 (987654, "f1206", "%w32x");
+    CHECK_SCANF1N (987654, "f1206", "%w32x%w32n");
+    CHECK_SCANF1 (987655, "f1207", "%w32X");
+  }
+}
+
+static void
+test_wf32 (void)
+{
+  {
+    int_fast32_t var, n;
+    CHECK_SCANF1 (1234567, "1234567", "%wf32d");
+    CHECK_SCANF1N (2345678, "2345678", "%wf32d%wf32n");
+    CHECK_SCANF1 (-1010101, "-1010101", "%wf32d");
+    CHECK_SCANF1 (3000000, "3000000", "%wf32i");
+    CHECK_SCANF1 (-98765432, "-98765432", "%wf32i");
+    CHECK_SCANF1 (1048576, "0b100000000000000000000", "%wf32i");
+    CHECK_SCANF1 (1048575, "0xfffff", "%wf32i");
+    CHECK_SCANF1 (-1048575, "-03777777", "%wf32i");
+  }
+  {
+    uint_fast32_t var;
+    int_fast32_t n;
+    CHECK_SCANF1 (1234567, "100101101011010000111", "%wf32b");
+    CHECK_SCANF1 (1048575, "03777777", "%wf32o");
+    CHECK_SCANF1 (999999, "999999", "%wf32u");
+    CHECK_SCANF1 (987654, "f1206", "%wf32x");
+    CHECK_SCANF1N (987654, "f1206", "%wf32x%wf32n");
+    CHECK_SCANF1 (987655, "f1207", "%wf32X");
+  }
+#if INT_FAST32_MAX >= INT64_MAX
+  {
+    int_fast32_t var, n;
+    CHECK_SCANF1 (123456789012LL, "123456789012", "%wf32d");
+    CHECK_SCANF1N (234567890123LL, "234567890123", "%wf32d%wf32n");
+    CHECK_SCANF1 (-10101010101LL, "-10101010101", "%wf32d");
+    CHECK_SCANF1 (3000000000000000LL, "3000000000000000", "%wf32i");
+    CHECK_SCANF1 (-9876543210LL, "-9876543210", "%wf32i");
+    CHECK_SCANF1 (1LL << 40, "0b10000000000000000000000000000000000000000",
+		  "%wf32i");
+    CHECK_SCANF1 (1LL << 41, "0x20000000000", "%wf32i");
+    CHECK_SCANF1 (-(1LL << 42), "-0100000000000000", "%wf32i");
+  }
+  {
+    uint_fast32_t var;
+    int_fast32_t n;
+    CHECK_SCANF1 (123456789012ULL, "1110010111110100110010001101000010100",
+		  "%wf32b");
+    CHECK_SCANF1 (1ULL << 40, "20000000000000", "%wf32o");
+    CHECK_SCANF1 (999999999999ULL, "999999999999", "%wf32u");
+    CHECK_SCANF1 (9876543210ULL, "24cb016ea", "%wf32x");
+    CHECK_SCANF1N (9876543210ULL, "24cb016ea", "%wf32x%wf32n");
+    CHECK_SCANF1 (9876543211ULL, "24cb016eb", "%wf32X");
+  }
+#endif
+}
+
+static void
+test_w64 (void)
+{
+  {
+    int64_t var, n;
+    CHECK_SCANF1 (123456789012LL, "123456789012", "%w64d");
+    CHECK_SCANF1N (234567890123LL, "234567890123", "%w64d%w64n");
+    CHECK_SCANF1 (-10101010101LL, "-10101010101", "%w64d");
+    CHECK_SCANF1 (3000000000000000LL, "3000000000000000", "%w64i");
+    CHECK_SCANF1 (-9876543210LL, "-9876543210", "%w64i");
+    CHECK_SCANF1 (1LL << 40, "0b10000000000000000000000000000000000000000",
+		  "%w64i");
+    CHECK_SCANF1 (1LL << 41, "0x20000000000", "%w64i");
+    CHECK_SCANF1 (-(1LL << 42), "-0100000000000000", "%w64i");
+  }
+  {
+    uint64_t var;
+    int64_t n;
+    CHECK_SCANF1 (123456789012ULL, "1110010111110100110010001101000010100",
+		  "%w64b");
+    CHECK_SCANF1 (1ULL << 40, "20000000000000", "%w64o");
+    CHECK_SCANF1 (999999999999ULL, "999999999999", "%w64u");
+    CHECK_SCANF1 (9876543210ULL, "24cb016ea", "%w64x");
+    CHECK_SCANF1N (9876543210ULL, "24cb016ea", "%w64x%w64n");
+    CHECK_SCANF1 (9876543211ULL, "24cb016eb", "%w64X");
+  }
+  {
+    int_least64_t var, n;
+    CHECK_SCANF1 (123456789012LL, "123456789012", "%w64d");
+    CHECK_SCANF1N (234567890123LL, "234567890123", "%w64d%w64n");
+    CHECK_SCANF1 (-10101010101LL, "-10101010101", "%w64d");
+    CHECK_SCANF1 (3000000000000000LL, "3000000000000000", "%w64i");
+    CHECK_SCANF1 (-9876543210LL, "-9876543210", "%w64i");
+    CHECK_SCANF1 (1LL << 40, "0b10000000000000000000000000000000000000000",
+		  "%w64i");
+    CHECK_SCANF1 (1LL << 41, "0x20000000000", "%w64i");
+    CHECK_SCANF1 (-(1LL << 42), "-0100000000000000", "%w64i");
+  }
+  {
+    uint_least64_t var;
+    int_least64_t n;
+    CHECK_SCANF1 (123456789012ULL, "1110010111110100110010001101000010100",
+		  "%w64b");
+    CHECK_SCANF1 (1ULL << 40, "20000000000000", "%w64o");
+    CHECK_SCANF1 (999999999999ULL, "999999999999", "%w64u");
+    CHECK_SCANF1 (9876543210ULL, "24cb016ea", "%w64x");
+    CHECK_SCANF1N (9876543210ULL, "24cb016ea", "%w64x%w64n");
+    CHECK_SCANF1 (9876543211ULL, "24cb016eb", "%w64X");
+  }
+}
+
+static void
+test_wf64 (void)
+{
+  {
+    int_fast64_t var, n;
+    CHECK_SCANF1 (123456789012LL, "123456789012", "%wf64d");
+    CHECK_SCANF1N (234567890123LL, "234567890123", "%wf64d%wf64n");
+    CHECK_SCANF1 (-10101010101LL, "-10101010101", "%wf64d");
+    CHECK_SCANF1 (3000000000000000LL, "3000000000000000", "%wf64i");
+    CHECK_SCANF1 (-9876543210LL, "-9876543210", "%wf64i");
+    CHECK_SCANF1 (1LL << 40, "0b10000000000000000000000000000000000000000",
+		  "%wf64i");
+    CHECK_SCANF1 (1LL << 41, "0x20000000000", "%wf64i");
+    CHECK_SCANF1 (-(1LL << 42), "-0100000000000000", "%wf64i");
+  }
+  {
+    uint_fast64_t var;
+    int_fast64_t n;
+    CHECK_SCANF1 (123456789012ULL, "1110010111110100110010001101000010100",
+		  "%wf64b");
+    CHECK_SCANF1 (1ULL << 40, "20000000000000", "%wf64o");
+    CHECK_SCANF1 (999999999999ULL, "999999999999", "%wf64u");
+    CHECK_SCANF1 (9876543210ULL, "24cb016ea", "%wf64x");
+    CHECK_SCANF1N (9876543210ULL, "24cb016ea", "%wf64x%wf64n");
+    CHECK_SCANF1 (9876543211ULL, "24cb016eb", "%wf64X");
+  }
+}
+
+static int
+do_test (void)
+{
+  int a, b;
+  test_w8 ();
+  test_wf8 ();
+  test_w16 ();
+  test_wf16 ();
+  test_w32 ();
+  test_wf32 ();
+  test_w64 ();
+  test_wf64 ();
+  /* Bad N in %wN and %wfN are required to produce an error return (of
+     the number of input items assigned) from scanf functions (and can
+     also be seen to be invalid at compile time).  */
+  DIAG_PUSH_NEEDS_COMMENT;
+  DIAG_IGNORE_NEEDS_COMMENT (13, "-Wformat");
+  DIAG_IGNORE_NEEDS_COMMENT (13, "-Wformat-extra-args");
+  CHECK_SCANF_ERR (0, "1", "%w1d", &a);
+  CHECK_SCANF_ERR (0, "1", "%w123d", &a);
+  CHECK_SCANF_ERR (0, "1", "%w99999999999999999999d", &a);
+  CHECK_SCANF_ERR (0, "1", "%wf1d", &a);
+  CHECK_SCANF_ERR (0, "1", "%wf123d", &a);
+  CHECK_SCANF_ERR (0, "1", "%wf99999999999999999999d", &a);
+  CHECK_SCANF_ERR (1, "1 1", "%d %w1d", &a, &b);
+  CHECK_SCANF_ERR (1, "1 1", "%d %w123d", &a, &b);
+  CHECK_SCANF_ERR (1, "1 1", "%d %w99999999999999999999d", &a, &b);
+  CHECK_SCANF_ERR (1, "1 1", "%d %wf1d", &a, &b);
+  CHECK_SCANF_ERR (1, "1 1", "%d %wf123d", &a, &b);
+  CHECK_SCANF_ERR (1, "1 1", "%d %wf99999999999999999999d", &a, &b);
+  CHECK_SCANF_ERR (0, "1", "%1$w1d", &a);
+  CHECK_SCANF_ERR (0, "1", "%1$w123d", &a);
+  CHECK_SCANF_ERR (0, "1", "%1$w99999999999999999999d", &a);
+  CHECK_SCANF_ERR (0, "1", "%1$wf1d", &a);
+  CHECK_SCANF_ERR (0, "1", "%1$wf123d", &a);
+  CHECK_SCANF_ERR (0, "1", "%1$wf99999999999999999999d", &a);
+  DIAG_POP_NEEDS_COMMENT;
+  return 0;
+}
+
+DIAG_POP_NEEDS_COMMENT;
+
+#include <support/test-driver.c>
diff --git a/stdio-common/tst-scanf-intn.c b/stdio-common/tst-scanf-intn.c
new file mode 100644
index 0000000000..876eda30ee
--- /dev/null
+++ b/stdio-common/tst-scanf-intn.c
@@ -0,0 +1,24 @@
+/* Test scanf formats for intN_t, int_leastN_t and int_fastN_t types.
+   Narrow string version.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#define SSCANF sscanf
+#define STRLEN strlen
+#define L_(C) C
+
+#include <tst-scanf-intn-main.c>
diff --git a/stdio-common/vfscanf-internal.c b/stdio-common/vfscanf-internal.c
index 9b1197d751..cacb9668ba 100644
--- a/stdio-common/vfscanf-internal.c
+++ b/stdio-common/vfscanf-internal.c
@@ -381,6 +381,7 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
   while (*f != '\0')
     {
       unsigned int argpos;
+      bool is_fast;
       /* Extract the next argument, which is of type TYPE.
 	 For a %N$... spec, this is the Nth argument from the beginning;
 	 otherwise it is the next argument after the state now in ARG.  */
@@ -602,6 +603,53 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
 	  else if (sizeof (ptrdiff_t) > sizeof (int))
 	    flags |= LONG;
 	  break;
+	case L_('w'):
+	  {
+	    is_fast = false;
+	    if (*f == L_('f'))
+	      {
+		++f;
+		is_fast = true;
+	      }
+	    int bitwidth = 0;
+	    if (ISDIGIT (*f))
+	      bitwidth = read_int (&f);
+	    if (is_fast)
+	      switch (bitwidth)
+		{
+		case 8:
+		  bitwidth = INT_FAST8_WIDTH;
+		  break;
+		case 16:
+		  bitwidth = INT_FAST16_WIDTH;
+		  break;
+		case 32:
+		  bitwidth = INT_FAST32_WIDTH;
+		  break;
+		case 64:
+		  bitwidth = INT_FAST64_WIDTH;
+		  break;
+		}
+	    switch (bitwidth)
+	      {
+	      case 8:
+		flags |= CHAR;
+		break;
+	      case 16:
+		flags |= SHORT;
+		break;
+	      case 32:
+		break;
+	      case 64:
+		flags |= LONGDBL | LONG;
+		break;
+	      default:
+		/* ISO C requires this error to be detected.  */
+		__set_errno (EINVAL);
+		goto errout;
+	      }
+	  }
+	  break;
 	default:
 	  /* Not a recognized modifier.  Backup.  */
 	  --f;
diff --git a/wcsmbs/Makefile b/wcsmbs/Makefile
index 431136b9c9..cde9a3221e 100644
--- a/wcsmbs/Makefile
+++ b/wcsmbs/Makefile
@@ -196,6 +196,7 @@ tests := \
   tst-wscanf-binary-c2x \
   tst-wscanf-binary-gnu11 \
   tst-wscanf-binary-gnu89 \
+  tst-wscanf-intn \
   tst-wscanf-to_inpunct \
   wcsatcliff \
   wcsmbs-tst1 \
diff --git a/wcsmbs/tst-wscanf-intn.c b/wcsmbs/tst-wscanf-intn.c
new file mode 100644
index 0000000000..8b6e6d3c0e
--- /dev/null
+++ b/wcsmbs/tst-wscanf-intn.c
@@ -0,0 +1,24 @@
+/* Test scanf formats for intN_t, int_leastN_t and int_fastN_t types.
+   Wide string version.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#define SSCANF swscanf
+#define STRLEN wcslen
+#define L_(C) L ## C
+
+#include "../stdio-common/tst-scanf-intn-main.c"
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index a48c32e76f..d2ae239895 100644
--- a/NEWS
+++ b/NEWS
@@ -31,6 +31,13 @@  Major new features:
   the process ID associated with the process file descriptor created by
   pid_spawn, fork_np, or pidfd_open.
 
+* scanf-family functions now support the wN format length modifiers for
+  arguments pointing to types intN_t, int_leastN_t, uintN_t or
+  uint_leastN_t (for example, %w32d to read int32_t or int_least32_t in
+  decimal, or %w32x to read uint32_t or uint_least32_t in hexadecimal)
+  and the wfN format length modifiers for arguments pointing to types
+  int_fastN_t or uint_fastN_t, as specified in draft ISO C2X.
+
 Deprecated and removed features, and other changes affecting compatibility:
 
   [Add deprecations, removals and changes affecting compatibility here]
diff --git a/manual/stdio.texi b/manual/stdio.texi
index 9cf622403f..002fce7a10 100644
--- a/manual/stdio.texi
+++ b/manual/stdio.texi
@@ -3714,6 +3714,20 @@  Specifies that the argument is a @code{ptrdiff_t *}.
 
 This modifier was introduced in @w{ISO C99}.
 
+@item w@var{n}
+Specifies that the argument is an @code{int@var{n}_t *} or
+@code{int_least@var{n}_t *} (which are the same type), or
+@code{uint@var{n}_t *} or @code{uint_least@var{n}_t *} (which are the
+same type).
+
+This modifier was introduced in @w{ISO C2X}.
+
+@item wf@var{n}
+Specifies that the argument is an @code{int_fast@var{n}_t *} or
+@code{uint_fast@var{n}_t *}.
+
+This modifier was introduced in @w{ISO C2X}.
+
 @item z
 Specifies that the argument is a @code{size_t *}.
 
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index 3866362bae..bacb795fed 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -243,6 +243,7 @@  tests := \
   tst-scanf-binary-c2x \
   tst-scanf-binary-gnu11 \
   tst-scanf-binary-gnu89 \
+  tst-scanf-intn \
   tst-scanf-round \
   tst-scanf-to_inpunct \
   tst-setvbuf1 \
diff --git a/stdio-common/tst-scanf-intn-main.c b/stdio-common/tst-scanf-intn-main.c
new file mode 100644
index 0000000000..fa0462a8de
--- /dev/null
+++ b/stdio-common/tst-scanf-intn-main.c
@@ -0,0 +1,479 @@ 
+/* Test scanf formats for intN_t, int_leastN_t and int_fastN_t types.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <wchar.h>
+
+#include <libc-diag.h>
+#include <support/check.h>
+
+/* GCC does not know the %wN or %wfN length modifiers before GCC 13.  */
+DIAG_PUSH_NEEDS_COMMENT;
+#if !__GNUC_PREREQ (13, 0)
+DIAG_IGNORE_NEEDS_COMMENT (12, "-Wformat");
+DIAG_IGNORE_NEEDS_COMMENT (12, "-Wformat-extra-args");
+#endif
+
+#define CHECK_SCANF1(EXPECTED, STR, FMT)				\
+  do									\
+    {									\
+      var = ((typeof (var)) 0xabababab);				\
+      int ret = SSCANF (L_(STR), L_(FMT), &var);			\
+      TEST_COMPARE (var, (EXPECTED));					\
+      TEST_COMPARE (ret, 1);						\
+    }									\
+  while (0)
+
+#define CHECK_SCANF1N(EXPECTED, STR, FMT)				\
+  do									\
+    {									\
+      var = ((typeof (var)) 0xabababab);				\
+      n = 123;								\
+      int ret = SSCANF (L_(STR), L_(FMT), &var, &n);			\
+      TEST_COMPARE (var, (EXPECTED));					\
+      TEST_COMPARE (n, STRLEN (L_(STR)));				\
+      TEST_COMPARE (ret, 1);						\
+    }									\
+  while (0)
+
+#define CHECK_SCANF_ERR(OK, STR, FMT, ...)				\
+  do									\
+    {									\
+      int ret = SSCANF (L_(STR), L_(FMT), __VA_ARGS__);			\
+      TEST_VERIFY (ret == (OK));					\
+      TEST_COMPARE (errno, EINVAL);					\
+    }									\
+  while (0)
+
+static void
+test_w8 (void)
+{
+  {
+    int8_t var, n;
+    CHECK_SCANF1 (42, "42", "%w8d");
+    CHECK_SCANF1N (42, "42", "%w8d%w8n");
+    CHECK_SCANF1 (-43, "-43", "%w8d");
+    CHECK_SCANF1 (42, "42", "%w8i");
+    CHECK_SCANF1 (-43, "-43", "%w8i");
+    CHECK_SCANF1 (123, "0b1111011", "%w8i");
+    CHECK_SCANF1 (127, "0x7f", "%w8i");
+    CHECK_SCANF1 (-19, "-023", "%w8i");
+  }
+  {
+    uint8_t var;
+    int8_t n;
+    CHECK_SCANF1 (123, "1111011", "%w8b");
+    CHECK_SCANF1 (19, "023", "%w8o");
+    CHECK_SCANF1 (50, "50", "%w8u");
+    CHECK_SCANF1 (65, "41", "%w8x");
+    CHECK_SCANF1N (65, "41", "%w8x%w8n");
+    CHECK_SCANF1 (66, "42", "%w8X");
+  }
+  {
+    int_least8_t var, n;
+    CHECK_SCANF1 (42, "42", "%w8d");
+    CHECK_SCANF1N (42, "42", "%w8d%w8n");
+    CHECK_SCANF1 (-43, "-43", "%w8d");
+    CHECK_SCANF1 (42, "42", "%w8i");
+    CHECK_SCANF1 (-43, "-43", "%w8i");
+    CHECK_SCANF1 (123, "0b1111011", "%w8i");
+    CHECK_SCANF1 (127, "0x7f", "%w8i");
+    CHECK_SCANF1 (-19, "-023", "%w8i");
+  }
+  {
+    uint_least8_t var;
+    int_least8_t n;
+    CHECK_SCANF1 (123, "1111011", "%w8b");
+    CHECK_SCANF1 (19, "023", "%w8o");
+    CHECK_SCANF1 (50, "50", "%w8u");
+    CHECK_SCANF1 (65, "41", "%w8x");
+    CHECK_SCANF1N (65, "41", "%w8x%w8n");
+    CHECK_SCANF1 (66, "42", "%w8X");
+  }
+}
+
+static void
+test_wf8 (void)
+{
+  {
+    int_fast8_t var, n;
+    CHECK_SCANF1 (42, "42", "%wf8d");
+    CHECK_SCANF1N (42, "42", "%wf8d%wf8n");
+    CHECK_SCANF1 (-43, "-43", "%wf8d");
+    CHECK_SCANF1 (42, "42", "%wf8i");
+    CHECK_SCANF1 (-43, "-43", "%wf8i");
+    CHECK_SCANF1 (123, "0b1111011", "%wf8i");
+    CHECK_SCANF1 (127, "0x7f", "%wf8i");
+    CHECK_SCANF1 (-19, "-023", "%wf8i");
+  }
+  {
+    uint_fast8_t var;
+    int_fast8_t n;
+    CHECK_SCANF1 (123, "1111011", "%wf8b");
+    CHECK_SCANF1 (19, "023", "%wf8o");
+    CHECK_SCANF1 (50, "50", "%wf8u");
+    CHECK_SCANF1 (65, "41", "%wf8x");
+    CHECK_SCANF1N (65, "41", "%wf8x%wf8n");
+    CHECK_SCANF1 (66, "42", "%wf8X");
+  }
+}
+
+static void
+test_w16 (void)
+{
+  {
+    int16_t var, n;
+    CHECK_SCANF1 (12345, "12345", "%w16d");
+    CHECK_SCANF1N (23456, "23456", "%w16d%w16n");
+    CHECK_SCANF1 (-10101, "-10101", "%w16d");
+    CHECK_SCANF1 (30000, "30000", "%w16i");
+    CHECK_SCANF1 (-19876, "-19876", "%w16i");
+    CHECK_SCANF1 (16384, "0b100000000000000", "%w16i");
+    CHECK_SCANF1 (32767, "0x7fff", "%w16i");
+    CHECK_SCANF1 (-16383, "-037777", "%w16i");
+  }
+  {
+    uint16_t var;
+    int16_t n;
+    CHECK_SCANF1 (32767, "111111111111111", "%w16b");
+    CHECK_SCANF1 (4095, "07777", "%w16o");
+    CHECK_SCANF1 (9999, "9999", "%w16u");
+    CHECK_SCANF1 (23456, "5ba0", "%w16x");
+    CHECK_SCANF1N (23456, "5ba0", "%w16x%w16n");
+    CHECK_SCANF1 (23457, "5ba1", "%w16X");
+  }
+  {
+    int_least16_t var, n;
+    CHECK_SCANF1 (12345, "12345", "%w16d");
+    CHECK_SCANF1N (23456, "23456", "%w16d%w16n");
+    CHECK_SCANF1 (-10101, "-10101", "%w16d");
+    CHECK_SCANF1 (30000, "30000", "%w16i");
+    CHECK_SCANF1 (-19876, "-19876", "%w16i");
+    CHECK_SCANF1 (16384, "0b100000000000000", "%w16i");
+    CHECK_SCANF1 (32767, "0x7fff", "%w16i");
+    CHECK_SCANF1 (-16383, "-037777", "%w16i");
+  }
+  {
+    uint_least16_t var;
+    int_least16_t n;
+    CHECK_SCANF1 (32767, "111111111111111", "%w16b");
+    CHECK_SCANF1 (4095, "07777", "%w16o");
+    CHECK_SCANF1 (9999, "9999", "%w16u");
+    CHECK_SCANF1 (23456, "5ba0", "%w16x");
+    CHECK_SCANF1N (23456, "5ba0", "%w16x%w16n");
+    CHECK_SCANF1 (23457, "5ba1", "%w16X");
+  }
+}
+
+static void
+test_wf16 (void)
+{
+  {
+    int_fast16_t var, n;
+    CHECK_SCANF1 (12345, "12345", "%wf16d");
+    CHECK_SCANF1N (23456, "23456", "%wf16d%wf16n");
+    CHECK_SCANF1 (-10101, "-10101", "%wf16d");
+    CHECK_SCANF1 (30000, "30000", "%wf16i");
+    CHECK_SCANF1 (-19876, "-19876", "%wf16i");
+    CHECK_SCANF1 (16384, "0b100000000000000", "%wf16i");
+    CHECK_SCANF1 (32767, "0x7fff", "%wf16i");
+    CHECK_SCANF1 (-16383, "-037777", "%wf16i");
+  }
+  {
+    uint_fast16_t var;
+    int_fast16_t n;
+    CHECK_SCANF1 (32767, "111111111111111", "%wf16b");
+    CHECK_SCANF1 (4095, "07777", "%wf16o");
+    CHECK_SCANF1 (9999, "9999", "%wf16u");
+    CHECK_SCANF1 (23456, "5ba0", "%wf16x");
+    CHECK_SCANF1N (23456, "5ba0", "%wf16x%wf16n");
+    CHECK_SCANF1 (23457, "5ba1", "%wf16X");
+  }
+#if INT_FAST16_MAX >= INT32_MAX
+  {
+    int_fast16_t var, n;
+    CHECK_SCANF1 (1234567, "1234567", "%wf16d");
+    CHECK_SCANF1N (2345678, "2345678", "%wf16d%wf16n");
+    CHECK_SCANF1 (-1010101, "-1010101", "%wf16d");
+    CHECK_SCANF1 (3000000, "3000000", "%wf16i");
+    CHECK_SCANF1 (-98765432, "-98765432", "%wf16i");
+    CHECK_SCANF1 (1048576, "0b100000000000000000000", "%wf16i");
+    CHECK_SCANF1 (1048575, "0xfffff", "%wf16i");
+    CHECK_SCANF1 (-1048575, "-03777777", "%wf16i");
+  }
+  {
+    uint_fast16_t var;
+    int_fast16_t n;
+    CHECK_SCANF1 (1234567, "100101101011010000111", "%wf16b");
+    CHECK_SCANF1 (1048575, "03777777", "%wf16o");
+    CHECK_SCANF1 (999999, "999999", "%wf16u");
+    CHECK_SCANF1 (987654, "f1206", "%wf16x");
+    CHECK_SCANF1N (987654, "f1206", "%wf16x%wf16n");
+    CHECK_SCANF1 (987655, "f1207", "%wf16X");
+  }
+#endif
+#if INT_FAST16_MAX >= INT64_MAX
+  {
+    int_fast16_t var, n;
+    CHECK_SCANF1 (123456789012LL, "123456789012", "%wf16d");
+    CHECK_SCANF1N (234567890123LL, "234567890123", "%wf16d%wf16n");
+    CHECK_SCANF1 (-10101010101LL, "-10101010101", "%wf16d");
+    CHECK_SCANF1 (3000000000000000LL, "3000000000000000", "%wf16i");
+    CHECK_SCANF1 (-9876543210LL, "-9876543210", "%wf16i");
+    CHECK_SCANF1 (1LL << 40, "0b10000000000000000000000000000000000000000",
+		  "%wf16i");
+    CHECK_SCANF1 (1LL << 41, "0x20000000000", "%wf16i");
+    CHECK_SCANF1 (-(1LL << 42), "-0100000000000000", "%wf16i");
+  }
+  {
+    uint_fast16_t var;
+    int_fast16_t n;
+    CHECK_SCANF1 (123456789012ULL, "1110010111110100110010001101000010100",
+		  "%wf16b");
+    CHECK_SCANF1 (1ULL << 40, "20000000000000", "%wf16o");
+    CHECK_SCANF1 (999999999999ULL, "999999999999", "%wf16u");
+    CHECK_SCANF1 (9876543210ULL, "24cb016ea", "%wf16x");
+    CHECK_SCANF1N (9876543210ULL, "24cb016ea", "%wf16x%wf16n");
+    CHECK_SCANF1 (9876543211ULL, "24cb016eb", "%wf16X");
+  }
+#endif
+}
+
+static void
+test_w32 (void)
+{
+  {
+    int32_t var, n;
+    CHECK_SCANF1 (1234567, "1234567", "%w32d");
+    CHECK_SCANF1N (2345678, "2345678", "%w32d%w32n");
+    CHECK_SCANF1 (-1010101, "-1010101", "%w32d");
+    CHECK_SCANF1 (3000000, "3000000", "%w32i");
+    CHECK_SCANF1 (-98765432, "-98765432", "%w32i");
+    CHECK_SCANF1 (1048576, "0b100000000000000000000", "%w32i");
+    CHECK_SCANF1 (1048575, "0xfffff", "%w32i");
+    CHECK_SCANF1 (-1048575, "-03777777", "%w32i");
+  }
+  {
+    uint32_t var;
+    int32_t n;
+    CHECK_SCANF1 (1234567, "100101101011010000111", "%w32b");
+    CHECK_SCANF1 (1048575, "03777777", "%w32o");
+    CHECK_SCANF1 (999999, "999999", "%w32u");
+    CHECK_SCANF1 (987654, "f1206", "%w32x");
+    CHECK_SCANF1N (987654, "f1206", "%w32x%w32n");
+    CHECK_SCANF1 (987655, "f1207", "%w32X");
+  }
+  {
+    int_least32_t var, n;
+    CHECK_SCANF1 (1234567, "1234567", "%w32d");
+    CHECK_SCANF1N (2345678, "2345678", "%w32d%w32n");
+    CHECK_SCANF1 (-1010101, "-1010101", "%w32d");
+    CHECK_SCANF1 (3000000, "3000000", "%w32i");
+    CHECK_SCANF1 (-98765432, "-98765432", "%w32i");
+    CHECK_SCANF1 (1048576, "0b100000000000000000000", "%w32i");
+    CHECK_SCANF1 (1048575, "0xfffff", "%w32i");
+    CHECK_SCANF1 (-1048575, "-03777777", "%w32i");
+  }
+  {
+    uint_least32_t var;
+    int_least32_t n;
+    CHECK_SCANF1 (1234567, "100101101011010000111", "%w32b");
+    CHECK_SCANF1 (1048575, "03777777", "%w32o");
+    CHECK_SCANF1 (999999, "999999", "%w32u");
+    CHECK_SCANF1 (987654, "f1206", "%w32x");
+    CHECK_SCANF1N (987654, "f1206", "%w32x%w32n");
+    CHECK_SCANF1 (987655, "f1207", "%w32X");
+  }
+}
+
+static void
+test_wf32 (void)
+{
+  {
+    int_fast32_t var, n;
+    CHECK_SCANF1 (1234567, "1234567", "%wf32d");
+    CHECK_SCANF1N (2345678, "2345678", "%wf32d%wf32n");
+    CHECK_SCANF1 (-1010101, "-1010101", "%wf32d");
+    CHECK_SCANF1 (3000000, "3000000", "%wf32i");
+    CHECK_SCANF1 (-98765432, "-98765432", "%wf32i");
+    CHECK_SCANF1 (1048576, "0b100000000000000000000", "%wf32i");
+    CHECK_SCANF1 (1048575, "0xfffff", "%wf32i");
+    CHECK_SCANF1 (-1048575, "-03777777", "%wf32i");
+  }
+  {
+    uint_fast32_t var;
+    int_fast32_t n;
+    CHECK_SCANF1 (1234567, "100101101011010000111", "%wf32b");
+    CHECK_SCANF1 (1048575, "03777777", "%wf32o");
+    CHECK_SCANF1 (999999, "999999", "%wf32u");
+    CHECK_SCANF1 (987654, "f1206", "%wf32x");
+    CHECK_SCANF1N (987654, "f1206", "%wf32x%wf32n");
+    CHECK_SCANF1 (987655, "f1207", "%wf32X");
+  }
+#if INT_FAST32_MAX >= INT64_MAX
+  {
+    int_fast32_t var, n;
+    CHECK_SCANF1 (123456789012LL, "123456789012", "%wf32d");
+    CHECK_SCANF1N (234567890123LL, "234567890123", "%wf32d%wf32n");
+    CHECK_SCANF1 (-10101010101LL, "-10101010101", "%wf32d");
+    CHECK_SCANF1 (3000000000000000LL, "3000000000000000", "%wf32i");
+    CHECK_SCANF1 (-9876543210LL, "-9876543210", "%wf32i");
+    CHECK_SCANF1 (1LL << 40, "0b10000000000000000000000000000000000000000",
+		  "%wf32i");
+    CHECK_SCANF1 (1LL << 41, "0x20000000000", "%wf32i");
+    CHECK_SCANF1 (-(1LL << 42), "-0100000000000000", "%wf32i");
+  }
+  {
+    uint_fast32_t var;
+    int_fast32_t n;
+    CHECK_SCANF1 (123456789012ULL, "1110010111110100110010001101000010100",
+		  "%wf32b");
+    CHECK_SCANF1 (1ULL << 40, "20000000000000", "%wf32o");
+    CHECK_SCANF1 (999999999999ULL, "999999999999", "%wf32u");
+    CHECK_SCANF1 (9876543210ULL, "24cb016ea", "%wf32x");
+    CHECK_SCANF1N (9876543210ULL, "24cb016ea", "%wf32x%wf32n");
+    CHECK_SCANF1 (9876543211ULL, "24cb016eb", "%wf32X");
+  }
+#endif
+}
+
+static void
+test_w64 (void)
+{
+  {
+    int64_t var, n;
+    CHECK_SCANF1 (123456789012LL, "123456789012", "%w64d");
+    CHECK_SCANF1N (234567890123LL, "234567890123", "%w64d%w64n");
+    CHECK_SCANF1 (-10101010101LL, "-10101010101", "%w64d");
+    CHECK_SCANF1 (3000000000000000LL, "3000000000000000", "%w64i");
+    CHECK_SCANF1 (-9876543210LL, "-9876543210", "%w64i");
+    CHECK_SCANF1 (1LL << 40, "0b10000000000000000000000000000000000000000",
+		  "%w64i");
+    CHECK_SCANF1 (1LL << 41, "0x20000000000", "%w64i");
+    CHECK_SCANF1 (-(1LL << 42), "-0100000000000000", "%w64i");
+  }
+  {
+    uint64_t var;
+    int64_t n;
+    CHECK_SCANF1 (123456789012ULL, "1110010111110100110010001101000010100",
+		  "%w64b");
+    CHECK_SCANF1 (1ULL << 40, "20000000000000", "%w64o");
+    CHECK_SCANF1 (999999999999ULL, "999999999999", "%w64u");
+    CHECK_SCANF1 (9876543210ULL, "24cb016ea", "%w64x");
+    CHECK_SCANF1N (9876543210ULL, "24cb016ea", "%w64x%w64n");
+    CHECK_SCANF1 (9876543211ULL, "24cb016eb", "%w64X");
+  }
+  {
+    int_least64_t var, n;
+    CHECK_SCANF1 (123456789012LL, "123456789012", "%w64d");
+    CHECK_SCANF1N (234567890123LL, "234567890123", "%w64d%w64n");
+    CHECK_SCANF1 (-10101010101LL, "-10101010101", "%w64d");
+    CHECK_SCANF1 (3000000000000000LL, "3000000000000000", "%w64i");
+    CHECK_SCANF1 (-9876543210LL, "-9876543210", "%w64i");
+    CHECK_SCANF1 (1LL << 40, "0b10000000000000000000000000000000000000000",
+		  "%w64i");
+    CHECK_SCANF1 (1LL << 41, "0x20000000000", "%w64i");
+    CHECK_SCANF1 (-(1LL << 42), "-0100000000000000", "%w64i");
+  }
+  {
+    uint_least64_t var;
+    int_least64_t n;
+    CHECK_SCANF1 (123456789012ULL, "1110010111110100110010001101000010100",
+		  "%w64b");
+    CHECK_SCANF1 (1ULL << 40, "20000000000000", "%w64o");
+    CHECK_SCANF1 (999999999999ULL, "999999999999", "%w64u");
+    CHECK_SCANF1 (9876543210ULL, "24cb016ea", "%w64x");
+    CHECK_SCANF1N (9876543210ULL, "24cb016ea", "%w64x%w64n");
+    CHECK_SCANF1 (9876543211ULL, "24cb016eb", "%w64X");
+  }
+}
+
+static void
+test_wf64 (void)
+{
+  {
+    int_fast64_t var, n;
+    CHECK_SCANF1 (123456789012LL, "123456789012", "%wf64d");
+    CHECK_SCANF1N (234567890123LL, "234567890123", "%wf64d%wf64n");
+    CHECK_SCANF1 (-10101010101LL, "-10101010101", "%wf64d");
+    CHECK_SCANF1 (3000000000000000LL, "3000000000000000", "%wf64i");
+    CHECK_SCANF1 (-9876543210LL, "-9876543210", "%wf64i");
+    CHECK_SCANF1 (1LL << 40, "0b10000000000000000000000000000000000000000",
+		  "%wf64i");
+    CHECK_SCANF1 (1LL << 41, "0x20000000000", "%wf64i");
+    CHECK_SCANF1 (-(1LL << 42), "-0100000000000000", "%wf64i");
+  }
+  {
+    uint_fast64_t var;
+    int_fast64_t n;
+    CHECK_SCANF1 (123456789012ULL, "1110010111110100110010001101000010100",
+		  "%wf64b");
+    CHECK_SCANF1 (1ULL << 40, "20000000000000", "%wf64o");
+    CHECK_SCANF1 (999999999999ULL, "999999999999", "%wf64u");
+    CHECK_SCANF1 (9876543210ULL, "24cb016ea", "%wf64x");
+    CHECK_SCANF1N (9876543210ULL, "24cb016ea", "%wf64x%wf64n");
+    CHECK_SCANF1 (9876543211ULL, "24cb016eb", "%wf64X");
+  }
+}
+
+static int
+do_test (void)
+{
+  int a, b;
+  test_w8 ();
+  test_wf8 ();
+  test_w16 ();
+  test_wf16 ();
+  test_w32 ();
+  test_wf32 ();
+  test_w64 ();
+  test_wf64 ();
+  /* Bad N in %wN and %wfN are required to produce an error return (of
+     the number of input items assigned) from scanf functions (and can
+     also be seen to be invalid at compile time).  */
+  DIAG_PUSH_NEEDS_COMMENT;
+  DIAG_IGNORE_NEEDS_COMMENT (13, "-Wformat");
+  DIAG_IGNORE_NEEDS_COMMENT (13, "-Wformat-extra-args");
+  CHECK_SCANF_ERR (0, "1", "%w1d", &a);
+  CHECK_SCANF_ERR (0, "1", "%w123d", &a);
+  CHECK_SCANF_ERR (0, "1", "%w99999999999999999999d", &a);
+  CHECK_SCANF_ERR (0, "1", "%wf1d", &a);
+  CHECK_SCANF_ERR (0, "1", "%wf123d", &a);
+  CHECK_SCANF_ERR (0, "1", "%wf99999999999999999999d", &a);
+  CHECK_SCANF_ERR (1, "1 1", "%d %w1d", &a, &b);
+  CHECK_SCANF_ERR (1, "1 1", "%d %w123d", &a, &b);
+  CHECK_SCANF_ERR (1, "1 1", "%d %w99999999999999999999d", &a, &b);
+  CHECK_SCANF_ERR (1, "1 1", "%d %wf1d", &a, &b);
+  CHECK_SCANF_ERR (1, "1 1", "%d %wf123d", &a, &b);
+  CHECK_SCANF_ERR (1, "1 1", "%d %wf99999999999999999999d", &a, &b);
+  CHECK_SCANF_ERR (0, "1", "%1$w1d", &a);
+  CHECK_SCANF_ERR (0, "1", "%1$w123d", &a);
+  CHECK_SCANF_ERR (0, "1", "%1$w99999999999999999999d", &a);
+  CHECK_SCANF_ERR (0, "1", "%1$wf1d", &a);
+  CHECK_SCANF_ERR (0, "1", "%1$wf123d", &a);
+  CHECK_SCANF_ERR (0, "1", "%1$wf99999999999999999999d", &a);
+  DIAG_POP_NEEDS_COMMENT;
+  return 0;
+}
+
+DIAG_POP_NEEDS_COMMENT;
+
+#include <support/test-driver.c>
diff --git a/stdio-common/tst-scanf-intn.c b/stdio-common/tst-scanf-intn.c
new file mode 100644
index 0000000000..876eda30ee
--- /dev/null
+++ b/stdio-common/tst-scanf-intn.c
@@ -0,0 +1,24 @@ 
+/* Test scanf formats for intN_t, int_leastN_t and int_fastN_t types.
+   Narrow string version.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#define SSCANF sscanf
+#define STRLEN strlen
+#define L_(C) C
+
+#include <tst-scanf-intn-main.c>
diff --git a/stdio-common/vfscanf-internal.c b/stdio-common/vfscanf-internal.c
index 9b1197d751..cf94af4f54 100644
--- a/stdio-common/vfscanf-internal.c
+++ b/stdio-common/vfscanf-internal.c
@@ -381,6 +381,7 @@  __vfscanf_internal (FILE *s, const char *format, va_list argptr,
   while (*f != '\0')
     {
       unsigned int argpos;
+      bool is_fast;
       /* Extract the next argument, which is of type TYPE.
 	 For a %N$... spec, this is the Nth argument from the beginning;
 	 otherwise it is the next argument after the state now in ARG.  */
@@ -602,6 +603,51 @@  __vfscanf_internal (FILE *s, const char *format, va_list argptr,
 	  else if (sizeof (ptrdiff_t) > sizeof (int))
 	    flags |= LONG;
 	  break;
+	case L_('w'):
+	  is_fast = false;
+	  if (*f == L_('f'))
+	    {
+	      ++f;
+	      is_fast = true;
+	    }
+	  int bitwidth = 0;
+	  if (ISDIGIT (*f))
+	    bitwidth = read_int (&f);
+	  if (is_fast)
+	    switch (bitwidth)
+	      {
+	      case 8:
+		bitwidth = INT_FAST8_WIDTH;
+		break;
+	      case 16:
+		bitwidth = INT_FAST16_WIDTH;
+		break;
+	      case 32:
+		bitwidth = INT_FAST32_WIDTH;
+		break;
+	      case 64:
+		bitwidth = INT_FAST64_WIDTH;
+		break;
+	      }
+	  switch (bitwidth)
+	    {
+	    case 8:
+	      flags |= CHAR;
+	      break;
+	    case 16:
+	      flags |= SHORT;
+	      break;
+	    case 32:
+	      break;
+	    case 64:
+	      flags |= LONGDBL | LONG;
+	      break;
+	    default:
+	      /* ISO C requires this error to be detected.  */
+	      __set_errno (EINVAL);
+	      goto errout;
+	    }
+	  break;
 	default:
 	  /* Not a recognized modifier.  Backup.  */
 	  --f;
diff --git a/wcsmbs/Makefile b/wcsmbs/Makefile
index 431136b9c9..cde9a3221e 100644
--- a/wcsmbs/Makefile
+++ b/wcsmbs/Makefile
@@ -196,6 +196,7 @@  tests := \
   tst-wscanf-binary-c2x \
   tst-wscanf-binary-gnu11 \
   tst-wscanf-binary-gnu89 \
+  tst-wscanf-intn \
   tst-wscanf-to_inpunct \
   wcsatcliff \
   wcsmbs-tst1 \
diff --git a/wcsmbs/tst-wscanf-intn.c b/wcsmbs/tst-wscanf-intn.c
new file mode 100644
index 0000000000..8b6e6d3c0e
--- /dev/null
+++ b/wcsmbs/tst-wscanf-intn.c
@@ -0,0 +1,24 @@ 
+/* Test scanf formats for intN_t, int_leastN_t and int_fastN_t types.
+   Wide string version.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#define SSCANF swscanf
+#define STRLEN wcslen
+#define L_(C) L ## C
+
+#include "../stdio-common/tst-scanf-intn-main.c"