I've combined all the current proposed changes into one patch.  I've also
introduced signed versions of the negation functions into int.h to avoid
relying on multiplication.

-- 
nathan
>From 2364ba4028f879a22b9f69f999aee3ea9c013ec0 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nat...@postgresql.org>
Date: Tue, 20 Aug 2024 16:12:39 -0500
Subject: [PATCH v26 1/1] Remove dependence on -fwrapv semantics in more
 places.

---
 src/backend/nodes/bitmapset.c          |  2 +-
 src/backend/utils/adt/date.c           |  6 +++-
 src/backend/utils/adt/formatting.c     | 28 +++++++++++++--
 src/backend/utils/hash/dynahash.c      |  6 ++--
 src/include/common/int.h               | 48 ++++++++++++++++++++++++++
 src/test/regress/expected/date.out     |  2 ++
 src/test/regress/expected/horology.out |  4 +++
 src/test/regress/expected/union.out    | 43 +++++++++++++++++++++++
 src/test/regress/sql/date.sql          |  1 +
 src/test/regress/sql/horology.sql      |  2 ++
 src/test/regress/sql/union.sql         |  9 +++++
 11 files changed, 143 insertions(+), 8 deletions(-)

diff --git a/src/backend/nodes/bitmapset.c b/src/backend/nodes/bitmapset.c
index cd05c642b0..d37a997c0e 100644
--- a/src/backend/nodes/bitmapset.c
+++ b/src/backend/nodes/bitmapset.c
@@ -67,7 +67,7 @@
  * we get zero.
  *----------
  */
-#define RIGHTMOST_ONE(x) ((signedbitmapword) (x) & -((signedbitmapword) (x)))
+#define RIGHTMOST_ONE(x) ((bitmapword) (x) & (~((bitmapword) (x)) + 1))
 
 #define HAS_MULTIPLE_ONES(x)   ((bitmapword) RIGHTMOST_ONE(x) != (x))
 
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 9c854e0e5c..0782e84776 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -257,7 +257,11 @@ make_date(PG_FUNCTION_ARGS)
        if (tm.tm_year < 0)
        {
                bc = true;
-               tm.tm_year = -tm.tm_year;
+               if (pg_neg_s32_overflow(tm.tm_year, &tm.tm_year))
+                       ereport(ERROR,
+                                       
(errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
+                                        errmsg("date field value out of range: 
%d-%02d-%02d",
+                                                       tm.tm_year, tm.tm_mon, 
tm.tm_mday)));
        }
 
        dterr = ValidateDate(DTK_DATE_M, false, false, bc, &tm);
diff --git a/src/backend/utils/adt/formatting.c 
b/src/backend/utils/adt/formatting.c
index 68069fcfd3..76bb3a79b5 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -77,6 +77,7 @@
 
 #include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
+#include "common/int.h"
 #include "common/unicode_case.h"
 #include "common/unicode_category.h"
 #include "mb/pg_wchar.h"
@@ -3809,7 +3810,12 @@ DCH_from_char(FormatNode *node, const char *in, 
TmFromChar *out,
                                                ereturn(escontext,,
                                                                
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
                                                                 
errmsg("invalid input string for \"Y,YYY\"")));
-                                       years += (millennia * 1000);
+                                       if (pg_mul_s32_overflow(millennia, 
1000, &millennia) ||
+                                               pg_add_s32_overflow(years, 
millennia, &years))
+                                               ereturn(escontext,,
+                                                               
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+                                                                
errmsg("invalid input string for \"Y,YYY\"")));
+
                                        if (!from_char_set_int(&out->year, 
years, n, escontext))
                                                return;
                                        out->yysz = 4;
@@ -4797,11 +4803,27 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, 
bool std,
                if (tmfc.bc)
                        tmfc.cc = -tmfc.cc;
                if (tmfc.cc >= 0)
+               {
                        /* +1 because 21st century started in 2001 */
-                       tm->tm_year = (tmfc.cc - 1) * 100 + 1;
+                       /* tm->tm_year = (tmfc.cc - 1) * 100 + 1 */
+                       if (pg_mul_s32_overflow(tmfc.cc - 1, 100, &tm->tm_year) 
||
+                               pg_add_s32_overflow(tm->tm_year, 1, 
&tm->tm_year))
+                               ereport(ERROR,
+                                               
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                                errmsg("date out of range: 
\"%s\"",
+                                                               
text_to_cstring(date_txt))));
+               }
                else
+               {
                        /* +1 because year == 599 is 600 BC */
-                       tm->tm_year = tmfc.cc * 100 + 1;
+                       /* tm->tm_year = tmfc.cc * 100 + 1 */
+                       if (pg_mul_s32_overflow(tmfc.cc, 100, &tm->tm_year) ||
+                               pg_add_s32_overflow(tm->tm_year, 1, 
&tm->tm_year))
+                               ereport(ERROR,
+                                               
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                                errmsg("date out of range: 
\"%s\"",
+                                                               
text_to_cstring(date_txt))));
+               }
                fmask |= DTK_M(YEAR);
        }
 
diff --git a/src/backend/utils/hash/dynahash.c 
b/src/backend/utils/hash/dynahash.c
index 5d9c62b652..ee2bbe5dc8 100644
--- a/src/backend/utils/hash/dynahash.c
+++ b/src/backend/utils/hash/dynahash.c
@@ -717,7 +717,7 @@ init_htab(HTAB *hashp, long nelem)
                nbuckets <<= 1;
 
        hctl->max_bucket = hctl->low_mask = nbuckets - 1;
-       hctl->high_mask = (nbuckets << 1) - 1;
+       hctl->high_mask = ((uint32) nbuckets << 1) - 1;
 
        /*
         * Figure number of directory segments needed, round up to a power of 2
@@ -1819,8 +1819,8 @@ next_pow2_long(long num)
 static int
 next_pow2_int(long num)
 {
-       if (num > INT_MAX / 2)
-               num = INT_MAX / 2;
+       if (num > INT_MAX / 2 + 1)
+               num = INT_MAX / 2 + 1;
        return 1 << my_log2(num);
 }
 
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 3b1590d676..6b50aa67b9 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -117,6 +117,22 @@ pg_mul_s16_overflow(int16 a, int16 b, int16 *result)
 #endif
 }
 
+static inline bool
+pg_neg_s16_overflow(int16 a, int16 *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+       return __builtin_sub_overflow(0, a, result);
+#else
+       if (unlikely(a == PG_INT16_MIN))
+       {
+               *result = 0x5EED;               /* to avoid spurious warnings */
+               return true;
+       }
+       *result = -a;
+       return false;
+#endif
+}
+
 static inline uint16
 pg_abs_s16(int16 a)
 {
@@ -185,6 +201,22 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
 #endif
 }
 
+static inline bool
+pg_neg_s32_overflow(int32 a, int32 *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+       return __builtin_sub_overflow(0, a, result);
+#else
+       if (unlikely(a == PG_INT32_MIN))
+       {
+               *result = 0x5EED;               /* to avoid spurious warnings */
+               return true;
+       }
+       *result = -a;
+       return false;
+#endif
+}
+
 static inline uint32
 pg_abs_s32(int32 a)
 {
@@ -300,6 +332,22 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
 #endif
 }
 
+static inline bool
+pg_neg_s64_overflow(int64 a, int64 *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+       return __builtin_sub_overflow(0, a, result);
+#else
+       if (unlikely(a == PG_INT64_MIN))
+       {
+               *result = 0x5EED;               /* to avoid spurious warnings */
+               return true;
+       }
+       *result = -a;
+       return false;
+#endif
+}
+
 static inline uint64
 pg_abs_s64(int64 a)
 {
diff --git a/src/test/regress/expected/date.out 
b/src/test/regress/expected/date.out
index f5949f3d17..c8f76c205d 100644
--- a/src/test/regress/expected/date.out
+++ b/src/test/regress/expected/date.out
@@ -1532,3 +1532,5 @@ select make_time(10, 55, 100.1);
 ERROR:  time field value out of range: 10:55:100.1
 select make_time(24, 0, 2.1);
 ERROR:  time field value out of range: 24:00:2.1
+SELECT make_date(-2147483648, 1, 1);
+ERROR:  date field value out of range: -2147483648-01-01
diff --git a/src/test/regress/expected/horology.out 
b/src/test/regress/expected/horology.out
index 241713cc51..df02d268c0 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -3448,6 +3448,8 @@ SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 
'YYYY-MM-DD HH24:MI:SS.FF'
 
 SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD 
HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
 ERROR:  date/time field value out of range: "2018-11-02 12:34:56.123456789"
+SELECT to_timestamp('1000000000,999', 'Y,YYY');
+ERROR:  invalid input string for "Y,YYY"
 SELECT to_date('1 4 1902', 'Q MM YYYY');  -- Q is ignored
   to_date   
 ------------
@@ -3778,6 +3780,8 @@ SELECT to_date('0000-02-01','YYYY-MM-DD');  -- allowed, 
though it shouldn't be
  02-01-0001 BC
 (1 row)
 
+SELECT to_date('100000000', 'CC');
+ERROR:  date out of range: "100000000"
 -- to_char's TZ format code produces zone abbrev if known
 SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');
          to_char         
diff --git a/src/test/regress/expected/union.out 
b/src/test/regress/expected/union.out
index 0fd0e1c38b..444744848e 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -59,6 +59,49 @@ SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
  2.2
 (2 rows)
 
+SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
+SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
+SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
+SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
+SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
+SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
+SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
+SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1;
+ ?column? 
+----------
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+(31 rows)
+
 -- Mixed types
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
  two 
diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql
index 1c58ff6966..9a4e5832b9 100644
--- a/src/test/regress/sql/date.sql
+++ b/src/test/regress/sql/date.sql
@@ -373,3 +373,4 @@ select make_date(2013, 13, 1);
 select make_date(2013, 11, -1);
 select make_time(10, 55, 100.1);
 select make_time(24, 0, 2.1);
+SELECT make_date(-2147483648, 1, 1);
diff --git a/src/test/regress/sql/horology.sql 
b/src/test/regress/sql/horology.sql
index e5cf12ff63..db532ee3c0 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -558,6 +558,7 @@ SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 
'YYYY-MM-DD HH24:MI:SS.FF' ||
 SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' 
|| i) FROM generate_series(1, 6) i;
 SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD 
HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
 SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD 
HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT to_timestamp('1000000000,999', 'Y,YYY');
 
 SELECT to_date('1 4 1902', 'Q MM YYYY');  -- Q is ignored
 SELECT to_date('3 4 21 01', 'W MM CC YY');
@@ -660,6 +661,7 @@ SELECT to_date('2016 365', 'YYYY DDD');  -- ok
 SELECT to_date('2016 366', 'YYYY DDD');  -- ok
 SELECT to_date('2016 367', 'YYYY DDD');
 SELECT to_date('0000-02-01','YYYY-MM-DD');  -- allowed, though it shouldn't be
+SELECT to_date('100000000', 'CC');
 
 -- to_char's TZ format code produces zone abbrev if known
 SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');
diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql
index f8826514e4..e1954a92d2 100644
--- a/src/test/regress/sql/union.sql
+++ b/src/test/regress/sql/union.sql
@@ -20,6 +20,15 @@ SELECT 1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 
1;
 
 SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
 
+SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
+SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
+SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
+SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
+SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
+SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
+SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
+SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1;
+
 -- Mixed types
 
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
-- 
2.39.3 (Apple Git-146)

Reply via email to