The next step towards the *zprintf family of functions. Part of step 1 of
<https://lists.gnu.org/archive/html/bug-gnulib/2024-04/msg00352.html>.

I added three unit tests for %s with large arguments. (You need to pass option
--with-longrunning-tests to gnulib-tool, in order to include them.) Without
a change to lib/vasnprintf.c, all three failed:

FAIL: test-u8-asnprintf-big
===========================

../../gltests/unistdio/test-u8-asnprintf-big.c:131: assertion 'errno == ENOMEM' 
failed
FAIL test-u8-asnprintf-big (exit status: 134)

FAIL: test-ulc-asnprintf-big
============================

../../gltests/unistdio/test-ulc-asnprintf-big.c:156: assertion 'errno == 
ENOMEM' failed
FAIL test-ulc-asnprintf-big (exit status: 134)

FAIL: test-vasnprintf-big
=========================

../../gltests/test-vasnprintf-big.c:131: assertion 'errno == ENOMEM' failed
FAIL test-vasnprintf-big (exit status: 134)

So, here's the vasnprintf update that makes all these tests work.


2024-06-19  Bruno Haible  <br...@clisp.org>

        ulc-asnprintf tests: Add test of %U, %s directives with large arguments.
        * tests/unistdio/test-ulc-asnprintf-big.c: New file, based on
        tests/test-vasnprintf-big.c.
        * modules/unistdio/ulc-asnprintf-extra-tests: New file.
        * modules/unistdio/ulc-asnprintf-tests (Depends-on): Depend on it.

        u8-asnprintf tests: Add test of %U, %s directives with large arguments.
        * tests/unistdio/test-u8-asnprintf-big.c: New file, based on
        tests/test-vasnprintf-big.c.
        * modules/unistdio/u8-asnprintf-extra-tests: New file.
        * modules/unistdio/u8-asnprintf-tests (Depends-on): Depend on it.

        vasnprintf tests: Add test of %s directive with large arguments.
        * tests/test-vasnprintf-big.c: New file.
        * modules/vasnprintf-extra-tests: New file.
        * modules/vasnprintf-tests (Depends-on): Depend on it.

        vasnprintf, u*-vasnprintf: Support string arguments longer than 2 GiB.
        * lib/vasnprintf.c: Include <stdint.h>.
        (VASNPRINTF): In 64-bit builds, handle the %s directive ourselves.
        (local_strnlen): Adjust #if condition.
        * modules/unistdio/u8-vasnprintf (Depends-on): Add stdint.
        * modules/unistdio/u8-u8-vasnprintf (Depends-on): Likewise.
        * modules/unistdio/u16-vasnprintf (Depends-on): Likewise.
        * modules/unistdio/u16-u16-vasnprintf (Depends-on): Likewise.
        * modules/unistdio/u32-vasnprintf (Depends-on): Likewise.
        * modules/unistdio/u32-u32-vasnprintf (Depends-on): Likewise.
        * modules/unistdio/ulc-vasnprintf (Depends-on): Likewise.

>From 4cada7ff4ddfc45f204f042ff2acfbac851eb25f Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 19 Jun 2024 20:46:47 +0200
Subject: [PATCH 1/4] vasnprintf, u*-vasnprintf: Support string arguments
 longer than 2 GiB.

* lib/vasnprintf.c: Include <stdint.h>.
(VASNPRINTF): In 64-bit builds, handle the %s directive ourselves.
(local_strnlen): Adjust #if condition.
* modules/unistdio/u8-vasnprintf (Depends-on): Add stdint.
* modules/unistdio/u8-u8-vasnprintf (Depends-on): Likewise.
* modules/unistdio/u16-vasnprintf (Depends-on): Likewise.
* modules/unistdio/u16-u16-vasnprintf (Depends-on): Likewise.
* modules/unistdio/u32-vasnprintf (Depends-on): Likewise.
* modules/unistdio/u32-u32-vasnprintf (Depends-on): Likewise.
* modules/unistdio/ulc-vasnprintf (Depends-on): Likewise.
---
 ChangeLog                           |  14 +++
 lib/vasnprintf.c                    | 186 +++++++++++++++++++++++++++-
 modules/unistdio/u16-u16-vasnprintf |   1 +
 modules/unistdio/u16-vasnprintf     |   1 +
 modules/unistdio/u32-u32-vasnprintf |   1 +
 modules/unistdio/u32-vasnprintf     |   1 +
 modules/unistdio/u8-u8-vasnprintf   |   1 +
 modules/unistdio/u8-vasnprintf      |   1 +
 modules/unistdio/ulc-vasnprintf     |   1 +
 9 files changed, 206 insertions(+), 1 deletion(-)

diff --git a/ChangeLog b/ChangeLog
index 0f89dacf4a..fc98fc99af 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+2024-06-19  Bruno Haible  <br...@clisp.org>
+
+	vasnprintf, u*-vasnprintf: Support string arguments longer than 2 GiB.
+	* lib/vasnprintf.c: Include <stdint.h>.
+	(VASNPRINTF): In 64-bit builds, handle the %s directive ourselves.
+	(local_strnlen): Adjust #if condition.
+	* modules/unistdio/u8-vasnprintf (Depends-on): Add stdint.
+	* modules/unistdio/u8-u8-vasnprintf (Depends-on): Likewise.
+	* modules/unistdio/u16-vasnprintf (Depends-on): Likewise.
+	* modules/unistdio/u16-u16-vasnprintf (Depends-on): Likewise.
+	* modules/unistdio/u32-vasnprintf (Depends-on): Likewise.
+	* modules/unistdio/u32-u32-vasnprintf (Depends-on): Likewise.
+	* modules/unistdio/ulc-vasnprintf (Depends-on): Likewise.
+
 2024-06-19  Bruno Haible  <br...@clisp.org>
 
 	vasnwprintf: Optimize handling of %c directive.
diff --git a/lib/vasnprintf.c b/lib/vasnprintf.c
index 1838ded22d..1178822df8 100644
--- a/lib/vasnprintf.c
+++ b/lib/vasnprintf.c
@@ -80,6 +80,7 @@
 #endif
 
 #include <locale.h>     /* localeconv() */
+#include <stdint.h>     /* PTRDIFF_MAX */
 #include <stdio.h>      /* snprintf(), sprintf() */
 #include <stdlib.h>     /* abort(), malloc(), realloc(), free() */
 #include <string.h>     /* memcpy(), strlen() */
@@ -231,7 +232,7 @@
 #undef remainder
 #define remainder rem
 
-#if (!USE_SNPRINTF || !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF) && !WIDE_CHAR_VERSION
+#if (!USE_SNPRINTF || !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF || (PTRDIFF_MAX > INT_MAX)) && !WIDE_CHAR_VERSION
 # if (HAVE_STRNLEN && !defined _AIX)
 #  define local_strnlen strnlen
 # else
@@ -2809,6 +2810,189 @@ VASNPRINTF (DCHAR_T *resultbuf, size_t *lengthp,
                   }
               }
 #endif
+#if !WIDE_CHAR_VERSION && (PTRDIFF_MAX > INT_MAX)
+            else if (dp->conversion == 's'
+                     && a.arg[dp->arg_index].type != TYPE_WIDE_STRING)
+              {
+                /* %s in vasnprintf.  See the specification of fprintf.
+                   We handle it ourselves here, because the string may be longer
+                   than INT_MAX characters, whence snprintf or sprintf would
+                   fail to process it.  */
+                int flags = dp->flags;
+                int has_width;
+                size_t width;
+                int has_precision;
+                size_t precision;
+
+                has_width = 0;
+                width = 0;
+                if (dp->width_start != dp->width_end)
+                  {
+                    if (dp->width_arg_index != ARG_NONE)
+                      {
+                        int arg;
+
+                        if (!(a.arg[dp->width_arg_index].type == TYPE_INT))
+                          abort ();
+                        arg = a.arg[dp->width_arg_index].a.a_int;
+                        width = arg;
+                        if (arg < 0)
+                          {
+                            /* "A negative field width is taken as a '-' flag
+                                followed by a positive field width."  */
+                            flags |= FLAG_LEFT;
+                            width = -width;
+                          }
+                      }
+                    else
+                      {
+                        const FCHAR_T *digitp = dp->width_start;
+
+                        do
+                          width = xsum (xtimes (width, 10), *digitp++ - '0');
+                        while (digitp != dp->width_end);
+                      }
+                    if (width > (size_t) INT_MAX)
+                      goto overflow;
+                    has_width = 1;
+                  }
+
+                has_precision = 0;
+                precision = 6;
+                if (dp->precision_start != dp->precision_end)
+                  {
+                    if (dp->precision_arg_index != ARG_NONE)
+                      {
+                        int arg;
+
+                        if (!(a.arg[dp->precision_arg_index].type == TYPE_INT))
+                          abort ();
+                        arg = a.arg[dp->precision_arg_index].a.a_int;
+                        /* "A negative precision is taken as if the precision
+                            were omitted."  */
+                        if (arg >= 0)
+                          {
+                            precision = arg;
+                            has_precision = 1;
+                          }
+                      }
+                    else
+                      {
+                        const FCHAR_T *digitp = dp->precision_start + 1;
+
+                        precision = 0;
+                        while (digitp != dp->precision_end)
+                          precision = xsum (xtimes (precision, 10), *digitp++ - '0');
+                        has_precision = 1;
+                      }
+                  }
+
+                {
+                  const char *arg = a.arg[dp->arg_index].a.a_string;
+                  size_t bytes;
+# if ENABLE_UNISTDIO && DCHAR_IS_TCHAR
+                  size_t characters;
+# endif
+# if !DCHAR_IS_TCHAR
+                  /* This code assumes that TCHAR_T is 'char'.  */
+                  static_assert (sizeof (TCHAR_T) == 1);
+                  DCHAR_T *tmpdst;
+                  size_t tmpdst_len;
+# endif
+                  size_t w;
+
+                  if (has_precision)
+                    {
+                      /* Use only at most PRECISION bytes, from the left.  */
+                      bytes = local_strnlen (arg, precision);
+                    }
+                  else
+                    {
+                      /* Use the entire string, and count the number of
+                         bytes.  */
+                      bytes = strlen (arg);
+                    }
+
+# if ENABLE_UNISTDIO && DCHAR_IS_TCHAR
+                  if (has_width)
+                    characters = mbsnlen (arg, bytes);
+                  else
+                    {
+                      /* The number of characters doesn't matter,
+                         because !has_width and therefore width==0.  */
+                      characters = 0;
+                    }
+# endif
+
+# if !DCHAR_IS_TCHAR
+                  /* Convert from TCHAR_T[] to DCHAR_T[].  */
+                  tmpdst =
+                    DCHAR_CONV_FROM_ENCODING (locale_charset (),
+                                              iconveh_question_mark,
+                                              arg, bytes,
+                                              NULL,
+                                              NULL, &tmpdst_len);
+                  if (tmpdst == NULL)
+                    goto fail_with_errno;
+# endif
+
+                  if (has_width)
+                    {
+# if ENABLE_UNISTDIO
+                      /* Outside POSIX, it's preferable to compare the width
+                         against the number of _characters_ of the converted
+                         value.  */
+#  if DCHAR_IS_TCHAR
+                      w = characters;
+#  else
+                      w = DCHAR_MBSNLEN (tmpdst, tmpdst_len);
+#  endif
+# else
+                      /* The width is compared against the number of _bytes_
+                         of the converted value, says POSIX.  */
+                      w = bytes;
+# endif
+                    }
+                  else
+                    /* w doesn't matter.  */
+                    w = 0;
+
+                  {
+# if DCHAR_IS_TCHAR
+                    size_t total = bytes + (w < width ? width - w : 0);
+                    ENSURE_ALLOCATION (xsum (length, total));
+# else
+                    size_t total = tmpdst_len + (w < width ? width - w : 0);
+                    ENSURE_ALLOCATION_ELSE (xsum (length, total),
+                      { free (tmpdst); goto out_of_memory; });
+# endif
+
+                    if (w < width && !(flags & FLAG_LEFT))
+                      {
+                        size_t n = width - w;
+                        DCHAR_SET (result + length, ' ', n);
+                        length += n;
+                      }
+
+# if DCHAR_IS_TCHAR
+                    memcpy (result + length, arg, bytes);
+                    length += bytes;
+# else
+                    DCHAR_CPY (result + length, tmpdst, tmpdst_len);
+                    free (tmpdst);
+                    length += tmpdst_len;
+# endif
+
+                    if (w < width && (flags & FLAG_LEFT))
+                      {
+                        size_t n = width - w;
+                        DCHAR_SET (result + length, ' ', n);
+                        length += n;
+                      }
+                  }
+                }
+              }
+#endif
 #if WIDE_CHAR_VERSION && (!DCHAR_IS_TCHAR || NEED_WPRINTF_DIRECTIVE_LC)
             else if ((dp->conversion == 's'
                       && a.arg[dp->arg_index].type == TYPE_WIDE_STRING)
diff --git a/modules/unistdio/u16-u16-vasnprintf b/modules/unistdio/u16-u16-vasnprintf
index ef5886cb9d..98adbbb37a 100644
--- a/modules/unistdio/u16-u16-vasnprintf
+++ b/modules/unistdio/u16-u16-vasnprintf
@@ -30,6 +30,7 @@ unistr/u16-strmblen
 unistr/u32-strlen
 unistr/u32-strmblen
 attribute
+stdint
 isnand-nolibm
 isnanl-nolibm
 frexpl-nolibm
diff --git a/modules/unistdio/u16-vasnprintf b/modules/unistdio/u16-vasnprintf
index 7769617562..de541ef1ca 100644
--- a/modules/unistdio/u16-vasnprintf
+++ b/modules/unistdio/u16-vasnprintf
@@ -30,6 +30,7 @@ unistr/u16-strmblen
 unistr/u32-strlen
 unistr/u32-strmblen
 attribute
+stdint
 isnand-nolibm
 isnanl-nolibm
 frexpl-nolibm
diff --git a/modules/unistdio/u32-u32-vasnprintf b/modules/unistdio/u32-u32-vasnprintf
index 587e4adf99..168cdbd7a9 100644
--- a/modules/unistdio/u32-u32-vasnprintf
+++ b/modules/unistdio/u32-u32-vasnprintf
@@ -30,6 +30,7 @@ unistr/u16-strmblen
 unistr/u32-strlen
 unistr/u32-strmblen
 attribute
+stdint
 isnand-nolibm
 isnanl-nolibm
 frexpl-nolibm
diff --git a/modules/unistdio/u32-vasnprintf b/modules/unistdio/u32-vasnprintf
index 84466c8b87..bdea7468b1 100644
--- a/modules/unistdio/u32-vasnprintf
+++ b/modules/unistdio/u32-vasnprintf
@@ -30,6 +30,7 @@ unistr/u16-strmblen
 unistr/u32-strlen
 unistr/u32-strmblen
 attribute
+stdint
 isnand-nolibm
 isnanl-nolibm
 frexpl-nolibm
diff --git a/modules/unistdio/u8-u8-vasnprintf b/modules/unistdio/u8-u8-vasnprintf
index d34f513485..5168794bf1 100644
--- a/modules/unistdio/u8-u8-vasnprintf
+++ b/modules/unistdio/u8-u8-vasnprintf
@@ -30,6 +30,7 @@ unistr/u16-strmblen
 unistr/u32-strlen
 unistr/u32-strmblen
 attribute
+stdint
 isnand-nolibm
 isnanl-nolibm
 frexpl-nolibm
diff --git a/modules/unistdio/u8-vasnprintf b/modules/unistdio/u8-vasnprintf
index dd71ae96a9..cf83b9343c 100644
--- a/modules/unistdio/u8-vasnprintf
+++ b/modules/unistdio/u8-vasnprintf
@@ -30,6 +30,7 @@ unistr/u16-strmblen
 unistr/u32-strlen
 unistr/u32-strmblen
 attribute
+stdint
 isnand-nolibm
 isnanl-nolibm
 frexpl-nolibm
diff --git a/modules/unistdio/ulc-vasnprintf b/modules/unistdio/ulc-vasnprintf
index b2476b1e65..bea310b874 100644
--- a/modules/unistdio/ulc-vasnprintf
+++ b/modules/unistdio/ulc-vasnprintf
@@ -27,6 +27,7 @@ unistr/u16-strmblen
 unistr/u32-strlen
 unistr/u32-strmblen
 attribute
+stdint
 mbsnlen
 isnand-nolibm
 isnanl-nolibm
-- 
2.34.1

>From 8b43f5a6745c67bd7e6de6be0334685d4f6b88db Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 19 Jun 2024 20:52:08 +0200
Subject: [PATCH 2/4] vasnprintf tests: Add test of %s directive with large
 arguments.

* tests/test-vasnprintf-big.c: New file.
* modules/vasnprintf-extra-tests: New file.
* modules/vasnprintf-tests (Depends-on): Depend on it.
---
 ChangeLog                      |   5 +
 modules/vasnprintf-extra-tests |  18 +++
 modules/vasnprintf-tests       |   2 +-
 tests/test-vasnprintf-big.c    | 200 +++++++++++++++++++++++++++++++++
 4 files changed, 224 insertions(+), 1 deletion(-)
 create mode 100644 modules/vasnprintf-extra-tests
 create mode 100644 tests/test-vasnprintf-big.c

diff --git a/ChangeLog b/ChangeLog
index fc98fc99af..cc1671b2b5 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2024-06-19  Bruno Haible  <br...@clisp.org>
 
+	vasnprintf tests: Add test of %s directive with large arguments.
+	* tests/test-vasnprintf-big.c: New file.
+	* modules/vasnprintf-extra-tests: New file.
+	* modules/vasnprintf-tests (Depends-on): Depend on it.
+
 	vasnprintf, u*-vasnprintf: Support string arguments longer than 2 GiB.
 	* lib/vasnprintf.c: Include <stdint.h>.
 	(VASNPRINTF): In 64-bit builds, handle the %s directive ourselves.
diff --git a/modules/vasnprintf-extra-tests b/modules/vasnprintf-extra-tests
new file mode 100644
index 0000000000..b86c8a05d4
--- /dev/null
+++ b/modules/vasnprintf-extra-tests
@@ -0,0 +1,18 @@
+Status:
+longrunning-test
+
+Files:
+tests/test-vasnprintf-big.c
+tests/macros.h
+
+Depends-on:
+stdbool
+stdint
+physmem
+
+configure.ac:
+AC_CHECK_FUNCS_ONCE([setrlimit])
+
+Makefile.am:
+TESTS += test-vasnprintf-big
+check_PROGRAMS += test-vasnprintf-big
diff --git a/modules/vasnprintf-tests b/modules/vasnprintf-tests
index 7ae88c6b4c..062a4fd37e 100644
--- a/modules/vasnprintf-tests
+++ b/modules/vasnprintf-tests
@@ -3,10 +3,10 @@ tests/test-vasnprintf.c
 tests/macros.h
 
 Depends-on:
+vasnprintf-extra-tests
 
 configure.ac:
 
 Makefile.am:
 TESTS += test-vasnprintf
 check_PROGRAMS += test-vasnprintf
-
diff --git a/tests/test-vasnprintf-big.c b/tests/test-vasnprintf-big.c
new file mode 100644
index 0000000000..e4bb28c95c
--- /dev/null
+++ b/tests/test-vasnprintf-big.c
@@ -0,0 +1,200 @@
+/* Test of [v]asnprintf() with big results.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2024.  */
+
+#include <config.h>
+
+#include "vasnprintf.h"
+
+#include "physmem.h"
+
+/* Get INT_MAX.  */
+#include <limits.h>
+
+/* Get PTRDIFF_MAX.  */
+#include <stdint.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#if HAVE_SETRLIMIT
+# include <sys/types.h>
+# include <sys/time.h>
+# include <sys/resource.h>
+#endif
+
+#include "macros.h"
+
+int
+main ()
+{
+#if PTRDIFF_MAX == INT_MAX
+  fputs ("Skipping test: ptrdiff_t is not 64-bits wide\n", stderr);
+  return 77;
+#else
+  bool skipped = false;
+  /* Disable core files that would be huge.  */
+# if HAVE_SETRLIMIT && defined RLIMIT_CORE
+  struct rlimit rl;
+  rl.rlim_cur = rl.rlim_max = 0;
+  setrlimit (RLIMIT_CORE, &rl);
+# endif
+  /* The test below needs about 12 GiB of memory:
+     3 GiB for the inputs and up to 9 GiB for temporary output buffers.  */
+  double needed = 12.0 * 1024 * 1024 * 1024;
+  double avail = physmem_claimable (1.0);
+  printf ("memory needed = %g MiB, available = %g MiB\n",
+          needed / 1024 / 1024, avail / 1024 / 1024);
+  if (avail >= needed)
+    {
+      /* Note: The malloc() calls can fail, due to ulimit of RLIMIT_DATA.
+         For example, on OpenBSD 7.5, the soft limit is 1.0 GiB or 1.5 GiB,
+         and you need "ulimit -d 15728640".  */
+
+      /* Verify that asnprintf() can return a string of size > 4 GiB.  */
+      {
+        size_t n1 = 3 * (INT_MAX / 4) + 10;
+        size_t n2 = 3 * (INT_MAX / 4) + 20;
+        char *s1;
+        char *s2;
+
+        s1 = (char *) malloc (n1 + 1);
+        if (s1 != NULL)
+          {
+            memset (s1, 'a', n1);
+            s1[n1] = '\0';
+
+            s2 = (char *) malloc (n2 + 1);
+            if (s2 != NULL)
+              {
+                memset (s2, 'b', n2);
+                s1[n1] = '\0';
+
+                size_t len;
+                char *s = asnprintf (NULL, &len, "x%sy%sz", s1, s2);
+                if (s == NULL)
+                  {
+                    ASSERT (errno == ENOMEM);
+                    skipped = true;
+                  }
+                else
+                  {
+                    ASSERT (strlen (s) == len);
+                    ASSERT (len == n1 + n2 + 3);
+                    size_t i;
+                    for (i = 0; i <= len; i++)
+                      s[i] = (i == 0 ? 'x' :
+                              i <= n1 ? 'a' :
+                              i == n1 + 1 ? 'y' :
+                              i <= n1 + n2 + 1 ? 'b' :
+                              i == n1 + n2 + 2 ? 'z' :
+                              '\0');
+                    free (s);
+                  }
+                free (s2);
+              }
+            free (s1);
+          }
+      }
+
+      /* Verify that asnprintf() can take a string of size > 2 GiB, < 4 GiB
+         as argument.  */
+      {
+        size_t n1 = 3 * (size_t) (INT_MAX / 2) + 10;
+        char *s1;
+
+        s1 = (char *) malloc (n1 + 1);
+        if (s1 != NULL)
+          {
+            memset (s1, 'a', n1);
+            s1[n1] = '\0';
+
+            size_t len;
+            char *s = asnprintf (NULL, &len, "x%sy", s1);
+            if (s == NULL)
+              {
+                ASSERT (errno == ENOMEM);
+                skipped = true;
+              }
+            else
+              {
+                ASSERT (strlen (s) == len);
+                ASSERT (len == n1 + 2);
+                size_t i;
+                for (i = 0; i <= len; i++)
+                  s[i] = (i == 0 ? 'x' :
+                          i <= n1 ? 'a' :
+                          i == n1 + 1 ? 'y' :
+                          '\0');
+                free (s);
+              }
+            free (s1);
+          }
+      }
+
+      /* Verify that asnprintf() can take a string of size > 4 GiB
+         as argument.  */
+      {
+        size_t n1 = 5 * (size_t) (INT_MAX / 2) + 10;
+        if (n1 > (size_t) INT_MAX)
+          {
+            char *s1;
+
+            s1 = (char *) malloc (n1 + 1);
+            if (s1 != NULL)
+              {
+                memset (s1, 'a', n1);
+                s1[n1] = '\0';
+
+                size_t len;
+                char *s = asnprintf (NULL, &len, "x%sy", s1);
+                if (s == NULL)
+                  {
+                    ASSERT (errno == ENOMEM);
+                    skipped = true;
+                  }
+                else
+                  {
+                    ASSERT (strlen (s) == len);
+                    ASSERT (len == n1 + 2);
+                    size_t i;
+                    for (i = 0; i <= len; i++)
+                      s[i] = (i == 0 ? 'x' :
+                              i <= n1 ? 'a' :
+                              i == n1 + 1 ? 'y' :
+                              '\0');
+                    free (s);
+                  }
+                free (s1);
+              }
+          }
+      }
+    }
+  else
+    skipped = true;
+
+  if (test_exit_status != EXIT_SUCCESS)
+    return test_exit_status;
+  if (skipped)
+    {
+      fputs ("Skipping test: not enough memory available\n", stderr);
+      return 77;
+    }
+  return 0;
+#endif
+}
-- 
2.34.1

>From 19660fa2c5d265b46d1f5b8910637f4b71cc3827 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 19 Jun 2024 20:56:17 +0200
Subject: [PATCH 3/4] u8-asnprintf tests: Add test of %U, %s directives with
 large arguments.

* tests/unistdio/test-u8-asnprintf-big.c: New file, based on
tests/test-vasnprintf-big.c.
* modules/unistdio/u8-asnprintf-extra-tests: New file.
* modules/unistdio/u8-asnprintf-tests (Depends-on): Depend on it.
---
 ChangeLog                                 |   6 +
 modules/unistdio/u8-asnprintf-extra-tests |  20 +++
 modules/unistdio/u8-asnprintf-tests       |   1 +
 tests/unistdio/test-u8-asnprintf-big.c    | 200 ++++++++++++++++++++++
 4 files changed, 227 insertions(+)
 create mode 100644 modules/unistdio/u8-asnprintf-extra-tests
 create mode 100644 tests/unistdio/test-u8-asnprintf-big.c

diff --git a/ChangeLog b/ChangeLog
index cc1671b2b5..cb05d354c2 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2024-06-19  Bruno Haible  <br...@clisp.org>
 
+	u8-asnprintf tests: Add test of %U, %s directives with large arguments.
+	* tests/unistdio/test-u8-asnprintf-big.c: New file, based on
+	tests/test-vasnprintf-big.c.
+	* modules/unistdio/u8-asnprintf-extra-tests: New file.
+	* modules/unistdio/u8-asnprintf-tests (Depends-on): Depend on it.
+
 	vasnprintf tests: Add test of %s directive with large arguments.
 	* tests/test-vasnprintf-big.c: New file.
 	* modules/vasnprintf-extra-tests: New file.
diff --git a/modules/unistdio/u8-asnprintf-extra-tests b/modules/unistdio/u8-asnprintf-extra-tests
new file mode 100644
index 0000000000..c4f7a18e19
--- /dev/null
+++ b/modules/unistdio/u8-asnprintf-extra-tests
@@ -0,0 +1,20 @@
+Status:
+longrunning-test
+
+Files:
+tests/unistdio/test-u8-asnprintf-big.c
+tests/macros.h
+
+Depends-on:
+stdbool
+stdint
+physmem
+
+configure.ac:
+AC_CHECK_FUNCS_ONCE([setrlimit])
+
+Makefile.am:
+TESTS += test-u8-asnprintf-big
+check_PROGRAMS += test-u8-asnprintf-big
+test_u8_asnprintf_big_SOURCES = unistdio/test-u8-asnprintf-big.c
+test_u8_asnprintf_big_LDADD = $(LDADD) $(LIBUNISTRING) @LIBICONV@
diff --git a/modules/unistdio/u8-asnprintf-tests b/modules/unistdio/u8-asnprintf-tests
index d39689feaa..6c46c112c5 100644
--- a/modules/unistdio/u8-asnprintf-tests
+++ b/modules/unistdio/u8-asnprintf-tests
@@ -5,6 +5,7 @@ tests/unistdio/test-u8-printf1.h
 tests/macros.h
 
 Depends-on:
+unistdio/u8-asnprintf-extra-tests
 
 configure.ac:
 
diff --git a/tests/unistdio/test-u8-asnprintf-big.c b/tests/unistdio/test-u8-asnprintf-big.c
new file mode 100644
index 0000000000..25a5880c0a
--- /dev/null
+++ b/tests/unistdio/test-u8-asnprintf-big.c
@@ -0,0 +1,200 @@
+/* Test of u8_asnprintf() with big results.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2024.  */
+
+#include <config.h>
+
+#include <unistdio.h>
+
+#include "physmem.h"
+
+/* Get INT_MAX.  */
+#include <limits.h>
+
+/* Get PTRDIFF_MAX.  */
+#include <stdint.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#if HAVE_SETRLIMIT
+# include <sys/types.h>
+# include <sys/time.h>
+# include <sys/resource.h>
+#endif
+
+#include "macros.h"
+
+int
+main ()
+{
+#if PTRDIFF_MAX == INT_MAX
+  fputs ("Skipping test: ptrdiff_t is not 64-bits wide\n", stderr);
+  return 77;
+#else
+  bool skipped = false;
+  /* Disable core files that would be huge.  */
+# if HAVE_SETRLIMIT && defined RLIMIT_CORE
+  struct rlimit rl;
+  rl.rlim_cur = rl.rlim_max = 0;
+  setrlimit (RLIMIT_CORE, &rl);
+# endif
+  /* The test below needs about 12 GiB of memory:
+     3 GiB for the inputs and up to 9 GiB for temporary output buffers.  */
+  double needed = 12.0 * 1024 * 1024 * 1024;
+  double avail = physmem_claimable (1.0);
+  printf ("memory needed = %g MiB, available = %g MiB\n",
+          needed / 1024 / 1024, avail / 1024 / 1024);
+  if (avail >= needed)
+    {
+      /* Note: The malloc() calls can fail, due to ulimit of RLIMIT_DATA.
+         For example, on OpenBSD 7.5, the soft limit is 1.0 GiB or 1.5 GiB,
+         and you need "ulimit -d 15728640".  */
+
+      /* Verify that u8_asnprintf() can return a string of size > 4 GiB.  */
+      {
+        size_t n1 = 3 * (INT_MAX / 4) + 10;
+        size_t n2 = 3 * (INT_MAX / 4) + 20;
+        char *s1;
+        char *s2;
+
+        s1 = (char *) malloc (n1 + 1);
+        if (s1 != NULL)
+          {
+            memset (s1, 'a', n1);
+            s1[n1] = '\0';
+
+            s2 = (char *) malloc (n2 + 1);
+            if (s2 != NULL)
+              {
+                memset (s2, 'b', n2);
+                s1[n1] = '\0';
+
+                size_t len;
+                uint8_t *s = u8_asnprintf (NULL, &len, "x%sy%sz", s1, s2);
+                if (s == NULL)
+                  {
+                    ASSERT (errno == ENOMEM);
+                    skipped = true;
+                  }
+                else
+                  {
+                    ASSERT (strlen (s) == len);
+                    ASSERT (len == n1 + n2 + 3);
+                    size_t i;
+                    for (i = 0; i <= len; i++)
+                      s[i] = (i == 0 ? 'x' :
+                              i <= n1 ? 'a' :
+                              i == n1 + 1 ? 'y' :
+                              i <= n1 + n2 + 1 ? 'b' :
+                              i == n1 + n2 + 2 ? 'z' :
+                              '\0');
+                    free (s);
+                  }
+                free (s2);
+              }
+            free (s1);
+          }
+      }
+
+      /* Verify that u8_asnprintf() can take a string of size > 2 GiB, < 4 GiB
+         as argument.  */
+      {
+        size_t n1 = 3 * (size_t) (INT_MAX / 2) + 10;
+        char *s1;
+
+        s1 = (char *) malloc (n1 + 1);
+        if (s1 != NULL)
+          {
+            memset (s1, 'a', n1);
+            s1[n1] = '\0';
+
+            size_t len;
+            uint8_t *s = u8_asnprintf (NULL, &len, "x%sy", s1);
+            if (s == NULL)
+              {
+                ASSERT (errno == ENOMEM);
+                skipped = true;
+              }
+            else
+              {
+                ASSERT (strlen (s) == len);
+                ASSERT (len == n1 + 2);
+                size_t i;
+                for (i = 0; i <= len; i++)
+                  s[i] = (i == 0 ? 'x' :
+                          i <= n1 ? 'a' :
+                          i == n1 + 1 ? 'y' :
+                          '\0');
+                free (s);
+              }
+            free (s1);
+          }
+      }
+
+      /* Verify that u8_asnprintf() can take a string of size > 4 GiB
+         as argument.  */
+      {
+        size_t n1 = 5 * (size_t) (INT_MAX / 2) + 10;
+        if (n1 > (size_t) INT_MAX)
+          {
+            char *s1;
+
+            s1 = (char *) malloc (n1 + 1);
+            if (s1 != NULL)
+              {
+                memset (s1, 'a', n1);
+                s1[n1] = '\0';
+
+                size_t len;
+                uint8_t *s = u8_asnprintf (NULL, &len, "x%sy", s1);
+                if (s == NULL)
+                  {
+                    ASSERT (errno == ENOMEM);
+                    skipped = true;
+                  }
+                else
+                  {
+                    ASSERT (strlen (s) == len);
+                    ASSERT (len == n1 + 2);
+                    size_t i;
+                    for (i = 0; i <= len; i++)
+                      s[i] = (i == 0 ? 'x' :
+                              i <= n1 ? 'a' :
+                              i == n1 + 1 ? 'y' :
+                              '\0');
+                    free (s);
+                  }
+                free (s1);
+              }
+          }
+      }
+    }
+  else
+    skipped = true;
+
+  if (test_exit_status != EXIT_SUCCESS)
+    return test_exit_status;
+  if (skipped)
+    {
+      fputs ("Skipping test: not enough memory available\n", stderr);
+      return 77;
+    }
+  return 0;
+#endif
+}
-- 
2.34.1

>From 3af57020d3bc3be38a60590a2ad4ff8fe2a1eeb3 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 19 Jun 2024 20:58:18 +0200
Subject: [PATCH 4/4] ulc-asnprintf tests: Add test of %U, %s directives with
 large arguments.

* tests/unistdio/test-ulc-asnprintf-big.c: New file, based on
tests/test-vasnprintf-big.c.
* modules/unistdio/ulc-asnprintf-extra-tests: New file.
* modules/unistdio/ulc-asnprintf-tests (Depends-on): Depend on it.
---
 ChangeLog                                  |   6 +
 modules/unistdio/ulc-asnprintf-extra-tests |  20 ++
 modules/unistdio/ulc-asnprintf-tests       |   1 +
 tests/unistdio/test-ulc-asnprintf-big.c    | 254 +++++++++++++++++++++
 4 files changed, 281 insertions(+)
 create mode 100644 modules/unistdio/ulc-asnprintf-extra-tests
 create mode 100644 tests/unistdio/test-ulc-asnprintf-big.c

diff --git a/ChangeLog b/ChangeLog
index cb05d354c2..fc37bc6d79 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2024-06-19  Bruno Haible  <br...@clisp.org>
 
+	ulc-asnprintf tests: Add test of %U, %s directives with large arguments.
+	* tests/unistdio/test-ulc-asnprintf-big.c: New file, based on
+	tests/test-vasnprintf-big.c.
+	* modules/unistdio/ulc-asnprintf-extra-tests: New file.
+	* modules/unistdio/ulc-asnprintf-tests (Depends-on): Depend on it.
+
 	u8-asnprintf tests: Add test of %U, %s directives with large arguments.
 	* tests/unistdio/test-u8-asnprintf-big.c: New file, based on
 	tests/test-vasnprintf-big.c.
diff --git a/modules/unistdio/ulc-asnprintf-extra-tests b/modules/unistdio/ulc-asnprintf-extra-tests
new file mode 100644
index 0000000000..4d3c1cd0c4
--- /dev/null
+++ b/modules/unistdio/ulc-asnprintf-extra-tests
@@ -0,0 +1,20 @@
+Status:
+longrunning-test
+
+Files:
+tests/unistdio/test-ulc-asnprintf-big.c
+tests/macros.h
+
+Depends-on:
+stdbool
+stdint
+physmem
+
+configure.ac:
+AC_CHECK_FUNCS_ONCE([setrlimit])
+
+Makefile.am:
+TESTS += test-ulc-asnprintf-big
+check_PROGRAMS += test-ulc-asnprintf-big
+test_ulc_asnprintf_big_SOURCES = unistdio/test-ulc-asnprintf-big.c
+test_ulc_asnprintf_big_LDADD = $(LDADD) $(LIBUNISTRING) @LIBICONV@ $(MBRTOWC_LIB)
diff --git a/modules/unistdio/ulc-asnprintf-tests b/modules/unistdio/ulc-asnprintf-tests
index 99a5851624..982f3279a4 100644
--- a/modules/unistdio/ulc-asnprintf-tests
+++ b/modules/unistdio/ulc-asnprintf-tests
@@ -5,6 +5,7 @@ tests/unistdio/test-ulc-printf1.h
 tests/macros.h
 
 Depends-on:
+unistdio/ulc-asnprintf-extra-tests
 
 configure.ac:
 
diff --git a/tests/unistdio/test-ulc-asnprintf-big.c b/tests/unistdio/test-ulc-asnprintf-big.c
new file mode 100644
index 0000000000..b73ff302be
--- /dev/null
+++ b/tests/unistdio/test-ulc-asnprintf-big.c
@@ -0,0 +1,254 @@
+/* Test of ulc_asnprintf() with big results.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2024.  */
+
+#include <config.h>
+
+#include <unistdio.h>
+
+#include "physmem.h"
+
+/* Get INT_MAX.  */
+#include <limits.h>
+
+/* Get PTRDIFF_MAX.  */
+#include <stdint.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#if HAVE_SETRLIMIT
+# include <sys/types.h>
+# include <sys/time.h>
+# include <sys/resource.h>
+#endif
+
+#include "macros.h"
+
+int
+main ()
+{
+#if PTRDIFF_MAX == INT_MAX
+  fputs ("Skipping test: ptrdiff_t is not 64-bits wide\n", stderr);
+  return 77;
+#else
+  bool skipped = false;
+  /* Disable core files that would be huge.  */
+# if HAVE_SETRLIMIT && defined RLIMIT_CORE
+  struct rlimit rl;
+  rl.rlim_cur = rl.rlim_max = 0;
+  setrlimit (RLIMIT_CORE, &rl);
+# endif
+  /* The test below needs about 12 GiB of memory:
+     3 GiB for the inputs and up to 9 GiB for temporary output buffers.  */
+  double needed = 12.0 * 1024 * 1024 * 1024;
+  double avail = physmem_claimable (1.0);
+  printf ("memory needed = %g MiB, available = %g MiB\n",
+          needed / 1024 / 1024, avail / 1024 / 1024);
+  if (avail >= needed)
+    {
+      /* Note: The malloc() calls can fail, due to ulimit of RLIMIT_DATA.
+         For example, on OpenBSD 7.5, the soft limit is 1.0 GiB or 1.5 GiB,
+         and you need "ulimit -d 15728640".  */
+
+      /* Verify that ulc_asnprintf() can return a string of size > 4 GiB.  */
+      {
+        size_t n1 = 3 * (INT_MAX / 4) + 10;
+        size_t n2 = 3 * (INT_MAX / 4) + 20;
+        char *s1;
+        char *s2;
+
+        s1 = (char *) malloc (n1 + 1);
+        if (s1 != NULL)
+          {
+            memset (s1, 'a', n1);
+            s1[n1] = '\0';
+
+            s2 = (char *) malloc (n2 + 1);
+            if (s2 != NULL)
+              {
+                memset (s2, 'b', n2);
+                s1[n1] = '\0';
+
+                size_t len;
+                char *s = ulc_asnprintf (NULL, &len, "x%sy%sz", s1, s2);
+                if (s == NULL)
+                  {
+                    ASSERT (errno == ENOMEM);
+                    skipped = true;
+                  }
+                else
+                  {
+                    ASSERT (strlen (s) == len);
+                    ASSERT (len == n1 + n2 + 3);
+                    size_t i;
+                    for (i = 0; i <= len; i++)
+                      s[i] = (i == 0 ? 'x' :
+                              i <= n1 ? 'a' :
+                              i == n1 + 1 ? 'y' :
+                              i <= n1 + n2 + 1 ? 'b' :
+                              i == n1 + n2 + 2 ? 'z' :
+                              '\0');
+                    free (s);
+                  }
+                free (s2);
+              }
+            free (s1);
+          }
+      }
+
+      /* Verify that ulc_asnprintf() can take a string of size > 2 GiB, < 4 GiB
+         as argument.  */
+      {
+        size_t n1 = 3 * (size_t) (INT_MAX / 2) + 10;
+        char *s1;
+
+        s1 = (char *) malloc (n1 + 1);
+        if (s1 != NULL)
+          {
+            memset (s1, 'a', n1);
+            s1[n1] = '\0';
+
+            /* Once with %U.  */
+            {
+              size_t len;
+              char *s = ulc_asnprintf (NULL, &len, "x%Uy", s1);
+              if (s == NULL)
+                {
+                  ASSERT (errno == ENOMEM);
+                  skipped = true;
+                }
+              else
+                {
+                  ASSERT (strlen (s) == len);
+                  ASSERT (len == n1 + 2);
+                  size_t i;
+                  for (i = 0; i <= len; i++)
+                    s[i] = (i == 0 ? 'x' :
+                            i <= n1 ? 'a' :
+                            i == n1 + 1 ? 'y' :
+                            '\0');
+                  free (s);
+                }
+            }
+
+            /* Once with %s.  */
+            {
+              size_t len;
+              char *s = ulc_asnprintf (NULL, &len, "x%sy", s1);
+              if (s == NULL)
+                {
+                  ASSERT (errno == ENOMEM);
+                  skipped = true;
+                }
+              else
+                {
+                  ASSERT (strlen (s) == len);
+                  ASSERT (len == n1 + 2);
+                  size_t i;
+                  for (i = 0; i <= len; i++)
+                    s[i] = (i == 0 ? 'x' :
+                            i <= n1 ? 'a' :
+                            i == n1 + 1 ? 'y' :
+                            '\0');
+                  free (s);
+                }
+            }
+
+            free (s1);
+          }
+      }
+
+      /* Verify that ulc_asnprintf() can take a string of size > 4 GiB
+         as argument.  */
+      {
+        size_t n1 = 5 * (size_t) (INT_MAX / 2) + 10;
+        if (n1 > (size_t) INT_MAX)
+          {
+            char *s1;
+
+            s1 = (char *) malloc (n1 + 1);
+            if (s1 != NULL)
+              {
+                memset (s1, 'a', n1);
+                s1[n1] = '\0';
+
+                /* Once with %U.  */
+                {
+                  size_t len;
+                  char *s = ulc_asnprintf (NULL, &len, "x%Uy", s1);
+                  if (s == NULL)
+                    {
+                      ASSERT (errno == ENOMEM);
+                      skipped = true;
+                    }
+                  else
+                    {
+                      ASSERT (strlen (s) == len);
+                      ASSERT (len == n1 + 2);
+                      size_t i;
+                      for (i = 0; i <= len; i++)
+                        s[i] = (i == 0 ? 'x' :
+                                i <= n1 ? 'a' :
+                                i == n1 + 1 ? 'y' :
+                                '\0');
+                      free (s);
+                    }
+                }
+
+                /* Once with %s.  */
+                {
+                  size_t len;
+                  char *s = ulc_asnprintf (NULL, &len, "x%sy", s1);
+                  if (s == NULL)
+                    {
+                      ASSERT (errno == ENOMEM);
+                      skipped = true;
+                    }
+                  else
+                    {
+                      ASSERT (strlen (s) == len);
+                      ASSERT (len == n1 + 2);
+                      size_t i;
+                      for (i = 0; i <= len; i++)
+                        s[i] = (i == 0 ? 'x' :
+                                i <= n1 ? 'a' :
+                                i == n1 + 1 ? 'y' :
+                                '\0');
+                      free (s);
+                    }
+                }
+
+                free (s1);
+              }
+          }
+      }
+    }
+  else
+    skipped = true;
+
+  if (test_exit_status != EXIT_SUCCESS)
+    return test_exit_status;
+  if (skipped)
+    {
+      fputs ("Skipping test: not enough memory available\n", stderr);
+      return 77;
+    }
+  return 0;
+#endif
+}
-- 
2.34.1

Reply via email to