]> granicus.if.org Git - postgresql/commitdiff
Allow btree comparison functions to return INT_MIN.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 5 Oct 2018 20:01:30 +0000 (16:01 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 5 Oct 2018 20:01:30 +0000 (16:01 -0400)
Historically we forbade datatype-specific comparison functions from
returning INT_MIN, so that it would be safe to invert the sort order
just by negating the comparison result.  However, this was never
really safe for comparison functions that directly return the result
of memcmp(), strcmp(), etc, as POSIX doesn't place any such restriction
on those library functions.  Buildfarm results show that at least on
recent Linux on s390x, memcmp() actually does return INT_MIN sometimes,
causing sort failures.

The agreed-on answer is to remove this restriction and fix relevant
call sites to not make such an assumption; code such as "res = -res"
should be replaced by "INVERT_COMPARE_RESULT(res)".  The same is needed
in a few places that just directly negated the result of memcmp or
strcmp.

To help find places having this problem, I've also added a compile option
to nbtcompare.c that causes some of the commonly used comparators to
return INT_MIN/INT_MAX instead of their usual -1/+1.  It'd likely be
a good idea to have at least one buildfarm member running with
"-DSTRESS_SORT_INT_MIN".  That's far from a complete test of course,
but it should help to prevent fresh introductions of such bugs.

This is a longstanding portability hazard, so back-patch to all supported
branches.

Discussion: https://postgr.es/m/20180928185215.ffoq2xrq5d3pafna@alap3.anarazel.de

contrib/ltree/ltree_op.c
contrib/pgcrypto/imath.c
src/backend/access/nbtree/nbtcompare.c
src/backend/access/nbtree/nbtsearch.c
src/backend/access/nbtree/nbtutils.c
src/backend/executor/nodeIndexscan.c
src/backend/executor/nodeMergeAppend.c
src/bin/pg_rewind/filemap.c
src/include/access/nbtree.h
src/include/c.h
src/include/utils/sortsupport.h

index cb59d21ced4a7916f5c8fdcf16bf4635bcd22a85..d1067f8146567beee33990fa7f3b2ee85d3115de 100644 (file)
@@ -45,17 +45,24 @@ ltree_compare(const ltree *a, const ltree *b)
        ltree_level *bl = LTREE_FIRST(b);
        int                     an = a->numlevel;
        int                     bn = b->numlevel;
-       int                     res = 0;
 
        while (an > 0 && bn > 0)
        {
+               int                     res;
+
                if ((res = memcmp(al->name, bl->name, Min(al->len, bl->len))) == 0)
                {
                        if (al->len != bl->len)
                                return (al->len - bl->len) * 10 * (an + 1);
                }
                else
+               {
+                       if (res < 0)
+                               res = -1;
+                       else
+                               res = 1;
                        return res * 10 * (an + 1);
+               }
 
                an--;
                bn--;
@@ -146,7 +153,7 @@ inner_isparent(const ltree *c, const ltree *p)
        {
                if (cl->len != pl->len)
                        return false;
-               if (memcmp(cl->name, pl->name, cl->len))
+               if (memcmp(cl->name, pl->name, cl->len) != 0)
                        return false;
 
                pn--;
index 61a01e2b71002e16fb0a27bc6201dd041f6a55cd..6449dd34b0b524b89887ca7b13be1aefc075894e 100644 (file)
@@ -1254,11 +1254,9 @@ mp_int_compare(mp_int a, mp_int b)
                 * If they're both zero or positive, the normal comparison applies; if
                 * both negative, the sense is reversed.
                 */
-               if (sa == MP_ZPOS)
-                       return cmp;
-               else
-                       return -cmp;
-
+               if (sa != MP_ZPOS)
+                       INVERT_COMPARE_RESULT(cmp);
+               return cmp;
        }
        else
        {
@@ -1314,10 +1312,9 @@ mp_int_compare_value(mp_int z, int value)
        {
                cmp = s_vcmp(z, value);
 
-               if (vsign == MP_ZPOS)
-                       return cmp;
-               else
-                       return -cmp;
+               if (vsign != MP_ZPOS)
+                       INVERT_COMPARE_RESULT(cmp);
+               return cmp;
        }
        else
        {
index 0d60da61cc8b2f4e0a776fdc0514f601efc164e9..20de61b130efac369160b476c91beeaf4b829ac4 100644 (file)
  *
  *     The result is always an int32 regardless of the input datatype.
  *
- *     Although any negative int32 (except INT_MIN) is acceptable for reporting
- *     "<", and any positive int32 is acceptable for reporting ">", routines
+ *     Although any negative int32 is acceptable for reporting "<",
+ *     and any positive int32 is acceptable for reporting ">", routines
  *     that work on 32-bit or wider datatypes can't just return "a - b".
- *     That could overflow and give the wrong answer.  Also, one must not
- *     return INT_MIN to report "<", since some callers will negate the result.
+ *     That could overflow and give the wrong answer.
  *
  *     NOTE: it is critical that the comparison function impose a total order
  *     on all non-NULL values of the data type, and that the datatype's
  *     during an index access won't be recovered till end of query.  This
  *     primarily affects comparison routines for toastable datatypes;
  *     they have to be careful to free any detoasted copy of an input datum.
+ *
+ *     NOTE: we used to forbid comparison functions from returning INT_MIN,
+ *     but that proves to be too error-prone because some platforms' versions
+ *     of memcmp() etc can return INT_MIN.  As a means of stress-testing
+ *     callers, this file can be compiled with STRESS_SORT_INT_MIN defined
+ *     to cause many of these functions to return INT_MIN or INT_MAX instead of
+ *     their customary -1/+1.  For production, though, that's not a good idea
+ *     since users or third-party code might expect the traditional results.
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
+#include <limits.h>
+
 #include "utils/builtins.h"
 #include "utils/sortsupport.h"
 
+#ifdef STRESS_SORT_INT_MIN
+#define A_LESS_THAN_B          INT_MIN
+#define A_GREATER_THAN_B       INT_MAX
+#else
+#define A_LESS_THAN_B          (-1)
+#define A_GREATER_THAN_B       1
+#endif
+
 
 Datum
 btboolcmp(PG_FUNCTION_ARGS)
@@ -95,11 +112,11 @@ btint4cmp(PG_FUNCTION_ARGS)
        int32           b = PG_GETARG_INT32(1);
 
        if (a > b)
-               PG_RETURN_INT32(1);
+               PG_RETURN_INT32(A_GREATER_THAN_B);
        else if (a == b)
                PG_RETURN_INT32(0);
        else
-               PG_RETURN_INT32(-1);
+               PG_RETURN_INT32(A_LESS_THAN_B);
 }
 
 static int
@@ -109,11 +126,11 @@ btint4fastcmp(Datum x, Datum y, SortSupport ssup)
        int32           b = DatumGetInt32(y);
 
        if (a > b)
-               return 1;
+               return A_GREATER_THAN_B;
        else if (a == b)
                return 0;
        else
-               return -1;
+               return A_LESS_THAN_B;
 }
 
 Datum
@@ -132,11 +149,11 @@ btint8cmp(PG_FUNCTION_ARGS)
        int64           b = PG_GETARG_INT64(1);
 
        if (a > b)
-               PG_RETURN_INT32(1);
+               PG_RETURN_INT32(A_GREATER_THAN_B);
        else if (a == b)
                PG_RETURN_INT32(0);
        else
-               PG_RETURN_INT32(-1);
+               PG_RETURN_INT32(A_LESS_THAN_B);
 }
 
 static int
@@ -146,11 +163,11 @@ btint8fastcmp(Datum x, Datum y, SortSupport ssup)
        int64           b = DatumGetInt64(y);
 
        if (a > b)
-               return 1;
+               return A_GREATER_THAN_B;
        else if (a == b)
                return 0;
        else
-               return -1;
+               return A_LESS_THAN_B;
 }
 
 Datum
@@ -169,11 +186,11 @@ btint48cmp(PG_FUNCTION_ARGS)
        int64           b = PG_GETARG_INT64(1);
 
        if (a > b)
-               PG_RETURN_INT32(1);
+               PG_RETURN_INT32(A_GREATER_THAN_B);
        else if (a == b)
                PG_RETURN_INT32(0);
        else
-               PG_RETURN_INT32(-1);
+               PG_RETURN_INT32(A_LESS_THAN_B);
 }
 
 Datum
@@ -183,11 +200,11 @@ btint84cmp(PG_FUNCTION_ARGS)
        int32           b = PG_GETARG_INT32(1);
 
        if (a > b)
-               PG_RETURN_INT32(1);
+               PG_RETURN_INT32(A_GREATER_THAN_B);
        else if (a == b)
                PG_RETURN_INT32(0);
        else
-               PG_RETURN_INT32(-1);
+               PG_RETURN_INT32(A_LESS_THAN_B);
 }
 
 Datum
@@ -197,11 +214,11 @@ btint24cmp(PG_FUNCTION_ARGS)
        int32           b = PG_GETARG_INT32(1);
 
        if (a > b)
-               PG_RETURN_INT32(1);
+               PG_RETURN_INT32(A_GREATER_THAN_B);
        else if (a == b)
                PG_RETURN_INT32(0);
        else
-               PG_RETURN_INT32(-1);
+               PG_RETURN_INT32(A_LESS_THAN_B);
 }
 
 Datum
@@ -211,11 +228,11 @@ btint42cmp(PG_FUNCTION_ARGS)
        int16           b = PG_GETARG_INT16(1);
 
        if (a > b)
-               PG_RETURN_INT32(1);
+               PG_RETURN_INT32(A_GREATER_THAN_B);
        else if (a == b)
                PG_RETURN_INT32(0);
        else
-               PG_RETURN_INT32(-1);
+               PG_RETURN_INT32(A_LESS_THAN_B);
 }
 
 Datum
@@ -225,11 +242,11 @@ btint28cmp(PG_FUNCTION_ARGS)
        int64           b = PG_GETARG_INT64(1);
 
        if (a > b)
-               PG_RETURN_INT32(1);
+               PG_RETURN_INT32(A_GREATER_THAN_B);
        else if (a == b)
                PG_RETURN_INT32(0);
        else
-               PG_RETURN_INT32(-1);
+               PG_RETURN_INT32(A_LESS_THAN_B);
 }
 
 Datum
@@ -239,11 +256,11 @@ btint82cmp(PG_FUNCTION_ARGS)
        int16           b = PG_GETARG_INT16(1);
 
        if (a > b)
-               PG_RETURN_INT32(1);
+               PG_RETURN_INT32(A_GREATER_THAN_B);
        else if (a == b)
                PG_RETURN_INT32(0);
        else
-               PG_RETURN_INT32(-1);
+               PG_RETURN_INT32(A_LESS_THAN_B);
 }
 
 Datum
@@ -253,11 +270,11 @@ btoidcmp(PG_FUNCTION_ARGS)
        Oid                     b = PG_GETARG_OID(1);
 
        if (a > b)
-               PG_RETURN_INT32(1);
+               PG_RETURN_INT32(A_GREATER_THAN_B);
        else if (a == b)
                PG_RETURN_INT32(0);
        else
-               PG_RETURN_INT32(-1);
+               PG_RETURN_INT32(A_LESS_THAN_B);
 }
 
 static int
@@ -267,11 +284,11 @@ btoidfastcmp(Datum x, Datum y, SortSupport ssup)
        Oid                     b = DatumGetObjectId(y);
 
        if (a > b)
-               return 1;
+               return A_GREATER_THAN_B;
        else if (a == b)
                return 0;
        else
-               return -1;
+               return A_LESS_THAN_B;
 }
 
 Datum
@@ -299,9 +316,9 @@ btoidvectorcmp(PG_FUNCTION_ARGS)
                if (a->values[i] != b->values[i])
                {
                        if (a->values[i] > b->values[i])
-                               PG_RETURN_INT32(1);
+                               PG_RETURN_INT32(A_GREATER_THAN_B);
                        else
-                               PG_RETURN_INT32(-1);
+                               PG_RETURN_INT32(A_LESS_THAN_B);
                }
        }
        PG_RETURN_INT32(0);
index 28d0e6e7734ec894d092276cb51a5dee6d2d123d..4a14c62ac1e460ca40205135f93c73ec4a566be3 100644 (file)
@@ -494,7 +494,7 @@ _bt_compare(Relation rel,
                                                                                                         scankey->sk_argument));
 
                        if (!(scankey->sk_flags & SK_BT_DESC))
-                               result = -result;
+                               INVERT_COMPARE_RESULT(result);
                }
 
                /* if the keys are unequal, return the difference */
index 88cf9b59c9f85c451495f3af21a7ac96fc41ac70..d4f20ada37c1d508be136800b54051f79be43473 100644 (file)
@@ -507,7 +507,7 @@ _bt_compare_array_elements(const void *a, const void *b, void *arg)
                                                                                          cxt->collation,
                                                                                          da, db));
        if (cxt->reverse)
-               compare = -compare;
+               INVERT_COMPARE_RESULT(compare);
        return compare;
 }
 
@@ -1636,7 +1636,7 @@ _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, TupleDesc tupdesc,
                                                                                                        subkey->sk_argument));
 
                if (subkey->sk_flags & SK_BT_DESC)
-                       cmpresult = -cmpresult;
+                       INVERT_COMPARE_RESULT(cmpresult);
 
                /* Done comparing if unequal, else advance to next column */
                if (cmpresult != 0)
index 3143bd94ec4499fba94b41693538b785c4b32e6c..38cfbac0d91c664ac3fa88fc4d8437d4e5e2cf45 100644 (file)
@@ -413,9 +413,10 @@ reorderqueue_cmp(const pairingheap_node *a, const pairingheap_node *b,
        ReorderTuple *rtb = (ReorderTuple *) b;
        IndexScanState *node = (IndexScanState *) arg;
 
-       return -cmp_orderbyvals(rta->orderbyvals, rta->orderbynulls,
-                                                       rtb->orderbyvals, rtb->orderbynulls,
-                                                       node);
+       /* exchange argument order to invert the sort order */
+       return cmp_orderbyvals(rtb->orderbyvals, rtb->orderbynulls,
+                                                  rta->orderbyvals, rta->orderbynulls,
+                                                  node);
 }
 
 /*
index e271927077b8bec79d7ad71eb0384f74f770fe8d..d127ba8eab6aa6478054aba29b33d7706a2c9856 100644 (file)
@@ -250,7 +250,10 @@ heap_compare_slots(Datum a, Datum b, void *arg)
                                                                          datum2, isNull2,
                                                                          sortKey);
                if (compare != 0)
-                       return -compare;
+               {
+                       INVERT_COMPARE_RESULT(compare);
+                       return compare;
+               }
        }
        return 0;
 }
index 213b2e4fed0b5c5de77b5a6d9922b78b84aa1b1e..69d0fd3f2fab66ba20d1ad96df955c1ed57c9e12 100644 (file)
@@ -684,7 +684,7 @@ final_filemap_cmp(const void *a, const void *b)
                return -1;
 
        if (fa->action == FILE_ACTION_REMOVE)
-               return -strcmp(fa->path, fb->path);
+               return strcmp(fb->path, fa->path);
        else
                return strcmp(fa->path, fb->path);
 }
index c580f51f7faf2de0fd6bf5a54d9ba19514804773..a2bd73de9a58cc3a9d14068d3264b56ee8187dce 100644 (file)
@@ -444,8 +444,7 @@ typedef struct xl_btree_newroot
  *     When a new operator class is declared, we require that the user
  *     supply us with an amproc procedure (BTORDER_PROC) for determining
  *     whether, for two keys a and b, a < b, a = b, or a > b.  This routine
- *     must return < 0, 0, > 0, respectively, in these three cases.  (It must
- *     not return INT_MIN, since we may negate the result before using it.)
+ *     must return < 0, 0, > 0, respectively, in these three cases.
  *
  *     To facilitate accelerated sorting, an operator class may choose to
  *     offer a second procedure (BTSORTSUPPORT_PROC).  For full details, see
index 81d8e642da2119eab49306eabc82447e9b7da74c..3199be3054e624dc5325dfed8f5928f72a3f525c 100644 (file)
@@ -949,6 +949,14 @@ typedef NameData *Name;
  * ----------------------------------------------------------------
  */
 
+/*
+ * Invert the sign of a qsort-style comparison result, ie, exchange negative
+ * and positive integer values, being careful not to get the wrong answer
+ * for INT_MIN.  The argument should be an integral variable.
+ */
+#define INVERT_COMPARE_RESULT(var) \
+       ((var) = ((var) < 0) ? 1 : -(var))
+
 /*
  * Use this, not "char buf[BLCKSZ]", to declare a field or local variable
  * holding a page buffer, if that page might be accessed as a page and not
index 0ef76c8ccfddb172774fcaf8ed51c686de23f66f..4e866fd3beb38001d38fe9069902d2ea31898e76 100644 (file)
@@ -96,8 +96,7 @@ typedef struct SortSupportData
         * Comparator function has the same API as the traditional btree
         * comparison function, ie, return <0, 0, or >0 according as x is less
         * than, equal to, or greater than y.  Note that x and y are guaranteed
-        * not null, and there is no way to return null either.  Do not return
-        * INT_MIN, as callers are allowed to negate the result before using it.
+        * not null, and there is no way to return null either.
         *
         * This may be either the authoritative comparator, or the abbreviated
         * comparator.  Core code may switch this over the initial preference of
@@ -224,7 +223,7 @@ ApplySortComparator(Datum datum1, bool isNull1,
        {
                compare = (*ssup->comparator) (datum1, datum2, ssup);
                if (ssup->ssup_reverse)
-                       compare = -compare;
+                       INVERT_COMPARE_RESULT(compare);
        }
 
        return compare;
@@ -262,7 +261,7 @@ ApplySortAbbrevFullComparator(Datum datum1, bool isNull1,
        {
                compare = (*ssup->abbrev_full_comparator) (datum1, datum2, ssup);
                if (ssup->ssup_reverse)
-                       compare = -compare;
+                       INVERT_COMPARE_RESULT(compare);
        }
 
        return compare;