Support "j" length modifier in snprintf.c.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 9 Dec 2025 16:43:25 +0000 (11:43 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 9 Dec 2025 16:43:25 +0000 (11:43 -0500)
POSIX has for a long time defined the "j" length modifier for
printf conversions as meaning the size of intmax_t or uintmax_t.
We got away without supporting that so far, because we were not
using intmax_t anywhere.  However, commit e6be84356 re-introduced
upstream's use of intmax_t and PRIdMAX into zic.c.  It emerges
that on some platforms (at least FreeBSD and macOS), <inttypes.h>
defines PRIdMAX as "jd", so that snprintf.c falls over if that is
used.  (We hadn't noticed yet because it would only be apparent
if bad data is fed to zic, resulting in an error report, and even
then the only visible symptom is a missing line number in the
error message.)

We could revert that decision from our copy of zic.c, but
on the whole it seems better to update snprintf.c to support
this standard modifier.  There might well be extensions,
now or in future, that expect it to work.

I did this in the lazy man's way of translating "j" to either
"l" or "ll" depending on a compile-time sizeof() check, just
as was done long ago to support "z" for size_t.  One could
imagine promoting intmax_t to have full support in snprintf.c,
for example converting fmtint()'s value argument and internal
arithmetic to use [u]intmax_t not [unsigned] long long.  But
that'd be more work and I'm hesitant to do it anyway: if there
are any platforms out there where intmax_t is actually wider
than "long long", this would doubtless result in a noticeable
speed penalty to snprintf().  Let's not go there until we have
positive evidence that there's a reason to, and some way to
measure what size of penalty we're taking.

Author: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/3210703.1765236740@sss.pgh.pa.us

configure
configure.ac
meson.build
src/include/pg_config.h.in
src/port/snprintf.c

index ec35de5ba65709c45430826e552bee88f8e96895..3f91117f2457384a5114665c009b76bdcbaf693a 100755 (executable)
--- a/configure
+++ b/configure
@@ -16853,6 +16853,39 @@ cat >>confdefs.h <<_ACEOF
 _ACEOF
 
 
+# The cast to long int works around a bug in the HP C Compiler
+# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
+# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
+# This bug is HP SR number 8606223364.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking size of intmax_t" >&5
+$as_echo_n "checking size of intmax_t... " >&6; }
+if ${ac_cv_sizeof_intmax_t+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (intmax_t))" "ac_cv_sizeof_intmax_t"        "$ac_includes_default"; then :
+
+else
+  if test "$ac_cv_type_intmax_t" = yes; then
+     { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "cannot compute sizeof (intmax_t)
+See \`config.log' for more details" "$LINENO" 5; }
+   else
+     ac_cv_sizeof_intmax_t=0
+   fi
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_intmax_t" >&5
+$as_echo "$ac_cv_sizeof_intmax_t" >&6; }
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define SIZEOF_INTMAX_T $ac_cv_sizeof_intmax_t
+_ACEOF
+
+
 
 # Determine memory alignment requirements for the basic C data types.
 
index 7284f1ff6229cf7cbed14e1f150eb225cf38aec9..8f39002740331921a4c86b3a51d50305ee4b140c 100644 (file)
@@ -1983,6 +1983,7 @@ AC_CHECK_SIZEOF([void *])
 AC_CHECK_SIZEOF([size_t])
 AC_CHECK_SIZEOF([long])
 AC_CHECK_SIZEOF([long long])
+AC_CHECK_SIZEOF([intmax_t])
 
 # Determine memory alignment requirements for the basic C data types.
 
index 622598546ae81bae6ee9b6dca0b4e20f73c3777c..718150e3ac01f096d017dd70a26c3f112a1b7f49 100644 (file)
@@ -1776,6 +1776,8 @@ cdata.set('SIZEOF_LONG', cc.sizeof('long', args: test_c_args))
 cdata.set('SIZEOF_LONG_LONG', cc.sizeof('long long', args: test_c_args))
 cdata.set('SIZEOF_VOID_P', cc.sizeof('void *', args: test_c_args))
 cdata.set('SIZEOF_SIZE_T', cc.sizeof('size_t', args: test_c_args))
+cdata.set('SIZEOF_INTMAX_T', cc.sizeof('intmax_t', args: test_c_args,
+                                       prefix: '#include <stdint.h>'))
 
 
 # Check if __int128 is a working 128 bit integer type, and if so
index b0b0cfdaf79ba94b06957e36d266fc494f1eecb7..72434ce957edaae4e01b427a7aacaff366dc1871 100644 (file)
    RELSEG_SIZE requires an initdb. */
 #undef RELSEG_SIZE
 
+/* The size of `intmax_t', as computed by sizeof. */
+#undef SIZEOF_INTMAX_T
+
 /* The size of `long', as computed by sizeof. */
 #undef SIZEOF_LONG
 
index 6541182df6d168473145cc9a2f5aadfac25723b5..d914547fae273edae38e952b2b8228f8431e6846 100644 (file)
@@ -563,6 +563,15 @@ nextch2:
                else
                    longflag = 1;
                goto nextch2;
+           case 'j':
+#if SIZEOF_INTMAX_T == SIZEOF_LONG
+               longflag = 1;
+#elif SIZEOF_INTMAX_T == SIZEOF_LONG_LONG
+               longlongflag = 1;
+#else
+#error "cannot find integer type of the same size as intmax_t"
+#endif
+               goto nextch2;
            case 'z':
 #if SIZEOF_SIZE_T == SIZEOF_LONG
                longflag = 1;
@@ -826,6 +835,15 @@ nextch1:
                else
                    longflag = 1;
                goto nextch1;
+           case 'j':
+#if SIZEOF_INTMAX_T == SIZEOF_LONG
+               longflag = 1;
+#elif SIZEOF_INTMAX_T == SIZEOF_LONG_LONG
+               longlongflag = 1;
+#else
+#error "cannot find integer type of the same size as intmax_t"
+#endif
+               goto nextch1;
            case 'z':
 #if SIZEOF_SIZE_T == SIZEOF_LONG
                longflag = 1;