[PATCH v7 1/3] Add [v]aprintf(3)

Alejandro Colomar alx@kernel.org
Wed Jun 3 20:37:47 GMT 2026


Hi Adhemerval,

On 2026-06-03T13:45:44-0300, Adhemerval Zanella Netto wrote:
> > -  We don't need to fail with EOVERFLOW.  This initial implementation
> >    still fails with EOVERFLOW, because it would require a lot of work
> >    implementing it in a way that doesn't have such a failure point, but
> >    the API has no inherent reasons to fail with EOVERFLOW.
> 
> I think it should be disable by either extending __vasprintf_internal 
> (with a flag that returns -1/EOVERFLOW to use on the current interface or 
> functions where it should return int) or adding a new 
> __vasprintf_internal_ex that:
> 
> 1. Calls a new function simila to __printf_buffer_done (include/printf_buffer.h:195) 
>    that return ptrdiff_t (or int64_).
> 
> 2. Calls a new __printf_buffer_flush_asprintf (libio/vasprintf.c) that does 
>    not bails early with EOVERFLOW once current_pos >= INT_MAX.
> 
> But I do not think this is a hard requirement, nor a blocker for this patch 
> (I am not sure if Florian agrees, so I would check with him).

He recently said he wants to block it for this, IIUC.  I'll work on
a patch that applies on top of this one, after this one looks good to
you.

> But at the same 
> time I think it would be worth exploring whether we can avoid this limitation.
> 
> For the overflow case, I think we should properly document this in the manual
> entry.

[...]

> Let's try to disintagle this patch.

Thanks for the review!

>  I saw a build failure for alpha,
> powerpc (ppc64, ppc64le, ppc32), s390x, and sparcv9:
> 
> $ alpha-glibc-linux-gnu-gcc ../sysdeps/ieee754/ldbl-opt/nldbl-compat.c [...]
> ../sysdeps/ieee754/ldbl-opt/nldbl-compat.c:60:1: error: conflicting types for ‘__nldbl___aprintf’; have ‘int(const char *, ...)’
>    60 | __nldbl___aprintf (const char *fmt, ...)
>       | ^~~~~~~~~~~~~~~~~
[...]
> 
> The __nldbl___aprintf has the wrong return type — int instead of char *.

Ok.

	diff --git c/sysdeps/ieee754/ldbl-opt/nldbl-compat.c i/sysdeps/ieee754/ldbl-opt/nldbl-compat.c
	index f5c69f7ff4d5..bfa0514b18f1 100644
	--- c/sysdeps/ieee754/ldbl-opt/nldbl-compat.c
	+++ i/sysdeps/ieee754/ldbl-opt/nldbl-compat.c
	@@ -55,7 +55,7 @@ libc_hidden_proto (__nldbl___isoc23_vfwscanf)
	    we don't need to split this into one file per function for the
	    sake of statically linked programs.  */
	 
	-int
	+char *
	 attribute_compat_text_section
	 __nldbl___aprintf (const char *fmt, ...)
	 {

[...]
> > diff --git a/debug/aprintf_chk.c b/debug/aprintf_chk.c
> > new file mode 100644
> > index 000000000000..30f7811f20da
> > --- /dev/null
> > +++ b/debug/aprintf_chk.c
> > @@ -0,0 +1,51 @@
[...]
> > +/* Write formatted output from FORMAT to a string allocated with malloc.  */
> > +char *
> > +___aprintf_chk (int flag, const char *fmt, ...)
> > +{
> > +  /* For flag > 0 (i.e. __USE_FORTIFY_LEVEL > 1) request that %n
> > +     can only come from read-only format strings.  */
> > +  unsigned int mode = (flag > 0) ? PRINTF_FORTIFY : 0;
> > +  va_list ap;
> > +  char *p;
> > +
> > +  va_start (ap, fmt);
> > +  if (__vasprintf_internal (&p, fmt, ap, mode) < 0)
> > +    p = NULL;
> > +  va_end (ap);
> > +
> > +  return p;
> > +}
> 
> Adds some tests on debug/tst-fortify.c.

Ok.  Does this look good to you?

	diff --git c/debug/tst-fortify.c i/debug/tst-fortify.c
	index ee83fca22467..2bc3e5eb1c47 100644
	--- c/debug/tst-fortify.c
	+++ i/debug/tst-fortify.c
	@@ -1026,34 +1026,48 @@ do_test (void)
	   strcpy (buf2 + 2, "%n%s%n");
	   /* When the format string is writable and contains %n,
	      with -D_FORTIFY_SOURCE=2 it causes __chk_fail.  */
	   CHK_FAIL2_START
	   if (asprintf (&my_ptr, buf2, str4, &n1, str5, &n1) != 14)
	     FAIL ();
	   else
	     free (my_ptr);
	   CHK_FAIL2_END
	 
	+  CHK_FAIL2_START
	+  my_ptr = aprintf (buf2, str4, &n1, str5, &n1);
	+  if (my_ptr == NULL)
	+    FAIL ();
	+  else
	+    free (my_ptr);
	+  CHK_FAIL2_END
	+
	   struct obstack obs;
	   obstack_init (&obs);
	   CHK_FAIL2_START
	   if (obstack_printf (&obs, buf2, str4, &n1, str5, &n1) != 14)
	     FAIL ();
	   CHK_FAIL2_END
	   obstack_free (&obs, NULL);
	 
	   my_ptr = NULL;
	   if (asprintf (&my_ptr, "%s%n%s%n", str4, &n1, str5, &n1) != 14)
	     FAIL ();
	   else
	     free (my_ptr);
	 
	+  my_ptr = aprintf ("%s%n%s%n", str4, &n1, str5, &n1);
	+  if (my_ptr == NULL)
	+    FAIL ();
	+  else
	+    free (my_ptr);
	+
	   obstack_init (&obs);
	   if (obstack_printf (&obs, "%s%n%s%n", str4, &n1, str5, &n1) != 14)
	     FAIL ();
	   obstack_free (&obs, NULL);
	 #endif
	 
	   if (freopen (temp_filename, "r", stdin) == NULL)
	     {
	       puts ("could not open temporary file");
	       exit (1);

[...]
> > diff --git a/libio/stdio.h b/libio/stdio.h
> > index 3bf6a1f63203..81d7176c4535 100644
> > --- a/libio/stdio.h
> > +++ b/libio/stdio.h
> > @@ -412,6 +412,19 @@ extern int asprintf (char **__restrict __ptr,
> >       __THROWNL __attribute__ ((__format__ (__printf__, 2, 3))) __wur;
> >  #endif
> >  
> > +#ifdef __USE_GNU
> > +/* Write formatted output to a string dynamically allocated with `malloc'.  */
> > +extern char *vaprintf (const char *__restrict __fmt, __gnuc_va_list __ap)
> > +     __THROWNL __attribute__ ((__format__ (__printf__, 1, 0)))
> > +     __attribute_malloc__;
> 
> I think we should add __attr_dealloc_free, that one of the main advertises for
> this symbol. Smae for asprintf and the __chk variants.

Agree.  I probably looked at strdup(3) for imitating it, and it seems it
is lacking it.  Should we also add it to strdup(3) in string/string.h?

Anyway, here's a diff for aprintf(3):

	diff --git i/include/stdio.h w/include/stdio.h
	index cfdec3681d51..721101a3ad04 100644
	--- i/include/stdio.h
	+++ w/include/stdio.h
	@@ -78,9 +78,9 @@ stdio_hidden_ldbl_proto (__, vfprintf_chk)
	 extern char *__fgets_unlocked_chk (char *buf, size_t size, int n, FILE *fp);
	 extern char *__fgets_chk (char *buf, size_t size, int n, FILE *fp);
	 extern char *__aprintf_chk (int, const char *, ...)
	-     __THROW __attribute_malloc__;
	+     __THROW __attribute_malloc__ __attr_dealloc_free;
	 extern char *__vaprintf_chk (int, const char *, __gnuc_va_list)
	-     __THROW __attribute_malloc__;
	+     __THROW __attribute_malloc__ __attr_dealloc_free;
	 stdio_hidden_ldbl_proto (__, vaprintf_chk)
	 extern int __asprintf_chk (char **, int, const char *, ...) __THROW;
	 extern int __vasprintf_chk (char **, int, const char *, __gnuc_va_list) __THROW;
	diff --git i/libio/bits/stdio2-decl.h w/libio/bits/stdio2-decl.h
	index cf52c46c31f6..d8d99fa1a9f3 100644
	--- i/libio/bits/stdio2-decl.h
	+++ w/libio/bits/stdio2-decl.h
	@@ -69,11 +69,11 @@ extern int __vdprintf_chk (int __fd, int __flag,
	 extern char *__aprintf_chk (int __flag,
				   const char *__restrict __fmt, ...)
	      __THROW __attribute__ ((__format__ (__printf__, 2, 3)))
	-     __attribute_malloc__;
	+     __attribute_malloc__ __attr_dealloc_free;
	 extern char *__vaprintf_chk (int __flag,
				    const char *__restrict __fmt, __gnuc_va_list __ap)
	      __THROW __attribute__ ((__format__ (__printf__, 2, 0)))
	-     __attribute_malloc__;
	+     __attribute_malloc__ __attr_dealloc_free;
	 extern int __asprintf_chk (char **__restrict __ptr, int __flag,
				   const char *__restrict __fmt, ...)
	      __THROW __attribute__ ((__format__ (__printf__, 3, 4))) __wur;
	diff --git i/libio/stdio.h w/libio/stdio.h
	index 81d7176c4535..8aa583eff8d5 100644
	--- i/libio/stdio.h
	+++ w/libio/stdio.h
	@@ -416,13 +416,13 @@ extern int asprintf (char **__restrict __ptr,
	 /* Write formatted output to a string dynamically allocated with `malloc'.  */
	 extern char *vaprintf (const char *__restrict __fmt, __gnuc_va_list __ap)
	      __THROWNL __attribute__ ((__format__ (__printf__, 1, 0)))
	-     __attribute_malloc__;
	+     __attribute_malloc__ __attr_dealloc_free;
	 extern char *__aprintf (const char *__restrict __fmt, ...)
	      __THROWNL __attribute__ ((__format__ (__printf__, 1, 2)))
	-     __attribute_malloc__;
	+     __attribute_malloc__ __attr_dealloc_free;
	 extern char *aprintf (const char *__restrict __fmt, ...)
	      __THROWNL __attribute__ ((__format__ (__printf__, 1, 2)))
	-     __attribute_malloc__;
	+     __attribute_malloc__ __attr_dealloc_free;
	 #endif
	 
	 #ifdef __USE_XOPEN2K8
	diff --git i/sysdeps/ieee754/ldbl-opt/nldbl-compat.h w/sysdeps/ieee754/ldbl-opt/nldbl-compat.h
	index 9da1c2d241b4..3a3c7baf0e21 100644
	--- i/sysdeps/ieee754/ldbl-opt/nldbl-compat.h
	+++ w/sysdeps/ieee754/ldbl-opt/nldbl-compat.h
	@@ -130,7 +130,7 @@ extern int __nldbl___vswprintf_chk (wchar_t *__restrict, size_t, int, size_t,
					    const wchar_t *__restrict, __gnuc_va_list)
	   __THROW;
	 extern char *__nldbl___vaprintf_chk (int, const char *, __gnuc_va_list)
	-  __THROW __attribute_malloc__;
	+  __THROW __attribute_malloc__ __attr_dealloc_free;
	 extern int __nldbl___vasprintf_chk (char **, int, const char *, __gnuc_va_list)
	   __THROW;
	 extern int __nldbl___vdprintf_chk (int, int, const char *, __gnuc_va_list);

> 
> > +extern char *__aprintf (const char *__restrict __fmt, ...)
> > +     __THROWNL __attribute__ ((__format__ (__printf__, 1, 2)))
> > +     __attribute_malloc__;
> 
> I think there is no need to export the __aprintf for a new symbol, we did it
> on other symbols (like scanf) we can asm alias depending of the C standard
> being used. Since this is a new symbol, we can just use the aprintf.

Can we use aprintf(3) internally within glibc?  Users might have
defined aprintf() themselves, since it's non-standard.

> 
> > +extern char *aprintf (const char *__restrict __fmt, ...)
> > +     __THROWNL __attribute__ ((__format__ (__printf__, 1, 2)))
> > +     __attribute_malloc__;
> > +#endif
> > +
> >  #ifdef __USE_XOPEN2K8
> >  /* Write formatted output to a file descriptor.  */
> >  extern int vdprintf (int __fd, const char *__restrict __fmt,
> > diff --git a/libio/tst-aprintf.c b/libio/tst-aprintf.c
> > new file mode 100644
> > index 000000000000..0c7d7a8024c3
> > --- /dev/null
> > +++ b/libio/tst-aprintf.c
> > @@ -0,0 +1,73 @@
> > +/* Test aprintf.
> > +   Copyright (C) 2026 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 <stdlib.h>
> > +#include <stdio.h>
> > +#include <support/check.h>
> > +#include <sys/resource.h>
> > +#include <libc-diag.h>
> > +
> > +static int
> > +do_test (void)
> > +{
> > +  char *buf;
> > +
> > +  /* Success */
> > +  buf = aprintf ("foo %d", 42);
> > +  TEST_COMPARE_STRING (buf, "foo 42");
> > +  free(buf);
> > +
> > +  {
> > +    /* -Wformat-overflow warns that the format would produce more than
> > +       INT_MAX bytes; however, this function does not share this
> > +       inherent limitation of other printf-like functions.  The current
> > +       implementation fails with EOVERFLOW, because it's a trivial
> > +       wrapper; however, in the future we should improve the function so
> > +       that this doesn't fail anymore with EOVERFLOW.  When that is
> > +       implemented, this test will stop failing.  */
> > +    DIAG_PUSH_NEEDS_COMMENT;
> > +    DIAG_IGNORE_NEEDS_COMMENT (8, "-Wformat-overflow=");
> > +    buf = aprintf ("%2000000000d %2000000000d", 1, 2);
> 
> Does it work on ILP32 ABIs (x32 and mips64-n32)?

It would probably ENOMEM instead of EOVERFLOW, I suspect.

I've done

	diff --git i/libio/tst-aprintf.c w/libio/tst-aprintf.c
	index 0c7d7a8024c3..1db26505ee03 100644
	--- i/libio/tst-aprintf.c
	+++ w/libio/tst-aprintf.c
	@@ -53,7 +53,8 @@ do_test (void)
		that happens, this test will fail.  Just remove it and enable the
		test under '#if 0'.  */
	     TEST_VERIFY (buf == NULL);
	-    TEST_VERIFY (errno == EOVERFLOW);
	+    if (errno != ENOMEM)
	+      TEST_VERIFY (errno == EOVERFLOW);
	 #endif
	   }

(as is done is the asprintf(3) tests.)

> > +    DIAG_POP_NEEDS_COMMENT;
> > +#if 0
> > +    if (buf == NULL)
> > +      TEST_VERIFY (errno == ENOMEM);
> > +#else
> 
> Plase remove the '#if 0' path here.

Hmmm, okay.

> > +    /* We should eventually not fail with EOVERFLOW from aprintf.  When
> > +       that happens, this test will fail.  Just remove it and enable the
> > +       test under '#if 0'.  */
> > +    TEST_VERIFY (buf == NULL);
> > +    TEST_VERIFY (errno == EOVERFLOW);
> > +#endif

[...]
> > diff --git a/stdio-common/aprintf.c b/stdio-common/aprintf.c
> > new file mode 100644
> > index 000000000000..bee4966707ae
> > --- /dev/null
> > +++ b/stdio-common/aprintf.c
> > @@ -0,0 +1,40 @@
> > +/* Copyright (C) 2026 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/>.  */
> 
> Should we add a LGPL linking-exception clause, as other printf symbols?

I copied this from stdio-common/asprintf.c, IIRC.  I don't see the
exception in the other files:

	$ find stdio-common/ -type f \
		| grep printf \
		| xargs grep -i exception;
	$

The only file within stdio-common/ where I can see the exception is this
one:

	$ find stdio-common/ -type f \
		| grep -v Makefile \
		| xargs grep -i exception;
	stdio-common/isoc99_vsscanf.c:   As a special exception, if you link the code in this file with
	stdio-common/isoc99_vsscanf.c:   the GNU Lesser General Public License.  This exception does not
	stdio-common/isoc99_vsscanf.c:   This exception applies to code released by its copyright holders
	stdio-common/isoc99_vsscanf.c:   in files containing the exception.  */

[...]
> > diff --git a/sysdeps/ieee754/ldbl-opt/nldbl-compat.c b/sysdeps/ieee754/ldbl-opt/nldbl-compat.c
> > index e239cd5bc5c4..f5c69f7ff4d5 100644
> > --- a/sysdeps/ieee754/ldbl-opt/nldbl-compat.c
> > +++ b/sysdeps/ieee754/ldbl-opt/nldbl-compat.c
[...]
> > @@ -1342,3 +1396,8 @@ compat_symbol (libc, __nldbl___fprintf_chk, __fprintf_chk, GLIBC_2_3_4);
> >  compat_symbol (libc, __nldbl___vprintf_chk, __vprintf_chk, GLIBC_2_3_4);
> >  compat_symbol (libc, __nldbl___vfprintf_chk, __vfprintf_chk, GLIBC_2_3_4);
> >  #endif
> > +#if LONG_DOUBLE_COMPAT(libc, GLIBC_2_4_4)
> > +compat_symbol (libc, __nldbl_aprintf, aprintf, GLIBC_2_4_4);
> > +compat_symbol (libc, __nldbl_vaprintf, vaprintf, GLIBC_2_4_4);
> > +compat_symbol (libc, __nldbl___aprintf, __aprintf, GLIBC_2_4_4);
> > +#endif
> 
> I think you meant *2_44* here. But this whole block should not be required,
> aprintf is a new symbol, so it has no historic ldbl == double ABI to be 
> compatible with. 

So, should I drop all changes to
sysdeps/ieee754/ldbl-opt/nldbl-compat.c?

> New symbols do not get a compat_symbol at an older version (and compat_symbol 
> at the current dev version is illegal anyway). Check the __isoc23_* additions, 
> which use libc_hidden_def + the normal versioned export.

Would you mind naming a few __isoc23_* names?  I don't know those
functions.


Have a lovely night!
Alex

-- 
<https://www.alejandro-colomar.es>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL: <https://sourceware.org/pipermail/libc-alpha/attachments/20260603/13181fc9/attachment.sig>


More information about the Libc-alpha mailing list