Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1sgWIU-003PDf-9W for pgsql-hackers@arkaria.postgresql.org; Tue, 20 Aug 2024 21:21:46 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1sgWIR-003fNH-6i for pgsql-hackers@arkaria.postgresql.org; Tue, 20 Aug 2024 21:21:43 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1sgWIQ-003fN7-PA for pgsql-hackers@lists.postgresql.org; Tue, 20 Aug 2024 21:21:43 +0000 Received: from mail-ot1-x332.google.com ([2607:f8b0:4864:20::332]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.94.2) (envelope-from ) id 1sgWIK-000cxS-JT for pgsql-hackers@lists.postgresql.org; Tue, 20 Aug 2024 21:21:42 +0000 Received: by mail-ot1-x332.google.com with SMTP id 46e09a7af769-709428a9469so3807794a34.3 for ; Tue, 20 Aug 2024 14:21:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1724188896; x=1724793696; darn=lists.postgresql.org; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:from:to:cc:subject:date:message-id:reply-to; bh=DxbiagYwydO1o0yqz07gwBZYdYKRDf3DTTdtjw2/lt8=; b=a03TYunijJ7nbpzReXBodJwI/gWoHJSLqXsKQUkjbaR/bBbjYI2QBm140Mw4tNZQ8O 8FopQemLHp5pqIyGZKKj/7ZkdA+lWlaBEJ+jgwYuqG9w2bSq85a1YiAyx/Lye092c0QM T/u0n+KuJ2HhcxXi4GzqhKqu5+GU8CjhmaFDWeFnHjykc9uILRT9o4z/DBu5FBmjzXgB FW05P/uTcSJxCsXq6VoLKV0n3BvnlX0MW2C0MoMBG2bbBMf0Cn+qhmVSrPrFJRNE2A3+ ttJxXKXo3j7yt9SUhu5/Bc2Yg2oGsx2qz4RLBDhFMCSZ02yDMwjc2Ni2V7Z7ceTRHgVc ZZsw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724188896; x=1724793696; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=DxbiagYwydO1o0yqz07gwBZYdYKRDf3DTTdtjw2/lt8=; b=eR7gtkgAGf1Qfr3UqylYL6bLjKD2QGsVhXKGcRiSNPUEaQMxhybbaHGpRiX9zHzJO/ p34meM9MbJjfpcy1LAPxyCuO3EcAcu+CZusX8Kl/WNUv6UktbYy837cgQ5wW564W9y/h Fb0Fvq7MGrv2ZUpyNv6+ISavXkXBN3SbVokvm9HiFmcBVFQxnJAivhuUUbHz+CvHNF5E RDQT4TVjWH1+GtD9FPQ8etLwxQl7xWu54n7cT2yvKYKLxFuyKObh9tEISDqV2Bcc4uqM uJR3yB+hIaZV4Zyq/ugPG3ur6muD0Od1ddbKyGMdUbn8hfxOMvzVGdQrUmcWypw3Iv57 HGYg== X-Forwarded-Encrypted: i=1; AJvYcCWdz+bDN+c7IfGhzaBiDz1IkZoT4qvNWfB6KQajIpqgU7hAVOdGv247o3HJhUKv0o8/+ixpqsanX6Zf2gnV@lists.postgresql.org X-Gm-Message-State: AOJu0YyYX0x1k+A+1OL4pzVo2aRULbruCyGB0Zjb/vhmuaTKiTXKdb9D qB/2lObmueMTUqq92qU+kzlEjj3p97QXaYHyxzCiKy10l7lYeKh9 X-Google-Smtp-Source: AGHT+IHukX4q0TmJgXHGwHaHq0WQ17zLkeSJTvOq/Hh5KEd+p7mgUmeK16SBKrIJYog2LpsfTuDwZA== X-Received: by 2002:a05:6830:6589:b0:703:6ab8:1fe3 with SMTP id 46e09a7af769-70df87178d4mr388252a34.15.1724188895783; Tue, 20 Aug 2024 14:21:35 -0700 (PDT) Received: from nathan (162-195-168-172.lightspeed.stlsmo.sbcglobal.net. [162.195.168.172]) by smtp.gmail.com with ESMTPSA id 8926c6da1cb9f-4ccd6e93951sm4073967173.42.2024.08.20.14.21.34 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 20 Aug 2024 14:21:34 -0700 (PDT) Date: Tue, 20 Aug 2024 16:21:33 -0500 From: Nathan Bossart To: Alexander Lakhin Cc: Joseph Koshakow , jian he , Heikki Linnakangas , Matthew Kim , Tom Lane , PostgreSQL Hackers , Andres Freund Subject: Re: Remove dependence on integer wrapping Message-ID: References: MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="4Osgjv/K1pAePfca" Content-Disposition: inline In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --4Osgjv/K1pAePfca Content-Type: text/plain; charset=us-ascii Content-Disposition: inline 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 --4Osgjv/K1pAePfca Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="v26-0001-Remove-dependence-on-fwrapv-semantics-in-more-pl.patch" From 2364ba4028f879a22b9f69f999aee3ea9c013ec0 Mon Sep 17 00:00:00 2001 From: Nathan Bossart 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) --4Osgjv/K1pAePfca--