Thanks for the bug report. This bug is entertaining, as it comes from GCC now being so smart that it optimizes away a memset that cleared padding bits. We added the memset in coreutils 8.14 (2011) to try to fix the sort -g infinite loop bug (introduced in 1999), but the memset isn't guaranteed to fix the bug because the memset can be optimized away.

If the padding bits happen to be clear already sort is OK, but if not the results can be inconsistent when you compare two NaNs to each other, and inconsistent results can make sort infloop.

The C standard allows this level of intelligence in the compiler, so it's a bug in GNU 'sort'.

I installed the attached patch; please give it a try. For now I'll boldly close the bug report; we can easily reopen it if this patch doesn't actually fix the problem.
From 2f56f5a42033dc6db15d8963e54566f01fa0d61d Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Sun, 1 May 2022 22:46:21 -0700
Subject: [PATCH] sort: fix sort -g infloop again

Problem reported by Giulio Genovese (Bug#55212).
* src/sort.c (nan_compare): To compare NaNs, simply printf+strcmp.
This avoids the problem of padding bits and unspecified behavior.
Args are now long double instead of char *; caller changed.
---
 NEWS       |  6 ++++++
 src/sort.c | 21 ++++++---------------
 2 files changed, 12 insertions(+), 15 deletions(-)

diff --git a/NEWS b/NEWS
index 26eb52ca0..3a9148637 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,12 @@ GNU coreutils NEWS                                    -*- outline -*-
   'mv --backup=simple f d/' no longer mistakenly backs up d/f to f~.
   [bug introduced in coreutils-9.1]
 
+  'sort -g' no longer infloops when given multiple NaNs on platforms
+  like x86-64 where 'long double' has padding bits in memory.
+  Although the fix alters sort -g's NaN ordering, that ordering has
+  long been documented to be platform-dependent.
+  [bug introduced 1999-05-02 and only partly fixed in coreutils-8.14]
+
 
 * Noteworthy changes in release 9.1 (2022-04-15) [stable]
 
diff --git a/src/sort.c b/src/sort.c
index 3b775d6bb..b2a465cf5 100644
--- a/src/sort.c
+++ b/src/sort.c
@@ -2003,22 +2003,13 @@ numcompare (char const *a, char const *b)
   return strnumcmp (a, b, decimal_point, thousands_sep);
 }
 
-/* Work around a problem whereby the long double value returned by glibc's
-   strtold ("NaN", ...) contains uninitialized bits: clear all bytes of
-   A and B before calling strtold.  FIXME: remove this function if
-   gnulib guarantees that strtold's result is always well defined.  */
 static int
-nan_compare (char const *sa, char const *sb)
+nan_compare (long double a, long double b)
 {
-  long double a;
-  memset (&a, 0, sizeof a);
-  a = strtold (sa, NULL);
-
-  long double b;
-  memset (&b, 0, sizeof b);
-  b = strtold (sb, NULL);
-
-  return memcmp (&a, &b, sizeof a);
+  char buf[2][sizeof "-nan()" + CHAR_BIT * sizeof a];
+  snprintf (buf[0], sizeof buf[0], "%Lf", a);
+  snprintf (buf[1], sizeof buf[1], "%Lf", b);
+  return strcmp (buf[0], buf[1]);
 }
 
 static int
@@ -2046,7 +2037,7 @@ general_numcompare (char const *sa, char const *sb)
           : a == b ? 0
           : b == b ? -1
           : a == a ? 1
-          : nan_compare (sa, sb));
+          : nan_compare (a, b));
 }
 
 /* Return an integer in 1..12 of the month name MONTH.
-- 
2.34.1

Reply via email to