Subject: Re: bug#74427: tests/printf/printf-indexed failure on macOS

From: Paul Eggert <[email-masked]>
Date: Tue, 19 Nov 2024 08:47:03 -0800

On 2024-11-19 07:10, Pádraig Brady wrote:
> I've not got access to a macos system to test currently,
> but the attached should address this.

That patch addresses only the empty string, not other strings that 
cannot be converted (e.g., a string consisting of a single space). This 
turns out to be a longstanding bug in printf. I installed the attached 
fancier patch which should address the more-general problem, and which 
adds some test cases. I tested this on cfarm104.cfarm.net which is 
running macOS 12.6.

From 91e95f1f86977a66ac0d6165f6db554eff174128 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Mon, 18 Nov 2024 18:19:54 -0800
Subject: [PATCH] printf: diagnose empty args correctly
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Also, port better to macOS.
* src/printf.c (verify_numeric): Don’t assume that when s == end
then errno is zero; it is EINVAL on macOS, and POSIX allows this.
(print_direc): Treat missing arg as zero for numeric conversions,
and as an empty string for the others.
(print_formatted): Use null pointer, not an empty string,
to represent missing arg.
* tests/printf/printf.sh: Test empty and space widths and precisions.
---
 src/printf.c           | 33 ++++++++++++++++++++-------------
 tests/printf/printf.sh |  9 +++++++++
 2 files changed, 29 insertions(+), 13 deletions(-)

diff --git a/src/printf.c b/src/printf.c
index 488e8e006..d7730ae6c 100644
--- a/src/printf.c
+++ b/src/printf.c
@@ -104,17 +104,19 @@ ARGUMENTs converted to proper type first.  Variable widths are handled.\n\
 static void
 verify_numeric (char const *s, char const *end)
 {
-  if (errno)
+  if (s == end)
+    {
+      error (0, 0, _("%s: expected a numeric value"), quote (s));
+      exit_status = EXIT_FAILURE;
+    }
+  else if (errno)
     {
       error (0, errno, "%s", quote (s));
       exit_status = EXIT_FAILURE;
     }
   else if (*end)
     {
-      if (s == end)
-        error (0, 0, _("%s: expected a numeric value"), quote (s));
-      else
-        error (0, 0, _("%s: value not completely converted"), quote (s));
+      error (0, 0, _("%s: value not completely converted"), quote (s));
       exit_status = EXIT_FAILURE;
     }
 }
@@ -344,7 +346,7 @@ print_direc (char const *start, char conversion,
     case 'd':
     case 'i':
       {
-        intmax_t arg = vstrtoimax (argument);
+        intmax_t arg = argument ? vstrtoimax (argument) : 0;
         if (!have_field_width)
           {
             if (!have_precision)
@@ -367,7 +369,7 @@ print_direc (char const *start, char conversion,
     case 'x':
     case 'X':
       {
-        uintmax_t arg = vstrtoumax (argument);
+        uintmax_t arg = argument ? vstrtoumax (argument) : 0;
         if (!have_field_width)
           {
             if (!have_precision)
@@ -394,7 +396,7 @@ print_direc (char const *start, char conversion,
     case 'g':
     case 'G':
       {
-        long double arg = vstrtold (argument);
+        long double arg = argument ? vstrtold (argument) : 0;
         if (!have_field_width)
           {
             if (!have_precision)
@@ -413,13 +415,18 @@ print_direc (char const *start, char conversion,
       break;
 
     case 'c':
-      if (!have_field_width)
-        xprintf (p, *argument);
-      else
-        xprintf (p, field_width, *argument);
+      {
+        char c = argument ? *argument : '\0';
+        if (!have_field_width)
+          xprintf (p, c);
+        else
+          xprintf (p, field_width, c);
+      }
       break;
 
     case 's':
+      if (!argument)
+        argument = "";
       if (!have_field_width)
         {
           if (!have_precision)
@@ -662,7 +669,7 @@ print_formatted (char const *format, int argc, char **argv)
           print_direc (direc, *ac.f,
                        have_field_width, field_width,
                        have_precision, precision,
-                       argc <= ac.curr_arg ? "" : argv[ac.curr_arg]);
+                       ac.curr_arg < argc ? argv[ac.curr_arg] : nullptr);
 
           break;
 
diff --git a/tests/printf/printf.sh b/tests/printf/printf.sh
index 7e489161e..a411b9d9f 100755
--- a/tests/printf/printf.sh
+++ b/tests/printf/printf.sh
@@ -73,6 +73,11 @@ returns_ 1 $prog '%.*dx\n' $INT_OFLOW 0 >>out 2> /dev/null || fail=1
 
 $prog '11 %*c\n' 2 x >>out || fail=1
 
+returns_ 1 $prog '12 %*s\n' '' 'empty width' >>out 2>/dev/null || fail=1
+returns_ 1 $prog '13 %*s\n' ' ' 'space width' >>out 2>/dev/null || fail=1
+returns_ 1 $prog '14 %.*sx\n' '' 'empty precision' >>out 2>/dev/null || fail=1
+returns_ 1 $prog '15 %.*sx\n' ' ' 'space precision' >>out 2>/dev/null || fail=1
+
 returns_ 1 $prog '%#d\n' 0 >>out 2> /dev/null || fail=1
 
 returns_ 1 $prog '%0s\n' 0 >>out 2> /dev/null || fail=1
@@ -93,6 +98,10 @@ cat <<\EOF > exp
 9 0 x
 10 0x
 11  x
+12 empty width
+13 space width
+14 x
+15 x
 EOF
 
 compare exp out || fail=1
-- 
2.43.0