diff --git a/mysql-test/main/func_math.result b/mysql-test/main/func_math.result index 47de1170691..c3d7ee20f50 100644 --- a/mysql-test/main/func_math.result +++ b/mysql-test/main/func_math.result @@ -972,7 +972,8 @@ SELECT 9223372036854775808 DIV 1; SELECT 9223372036854775808 DIV -1; ERROR 22003: BIGINT UNSIGNED value is out of range in '9223372036854775808 DIV -1' SELECT -9223372036854775808 DIV 1; -ERROR 22003: BIGINT value is out of range in '-9223372036854775808 DIV 1' +-9223372036854775808 DIV 1 +-9223372036854775808 SELECT -9223372036854775808 DIV -1; ERROR 22003: BIGINT value is out of range in '-9223372036854775808 DIV -1' SELECT 9223372036854775808 MOD 1; @@ -3546,5 +3547,31 @@ t2 CREATE TABLE `t2` ( ) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci DROP TABLE t1,t2; # +# MDEV-30932 UBSAN: negation of -X cannot be represented in type 'long long int'; cast to an unsigned type to negate this value to itself in Item_func_mul::int_op and Item_func_round::int_op +# +SELECT (1 DIV(-1/POW(807,14))*1); +ERROR 22003: BIGINT value is out of range in '1 DIV (-1 / pow(807,14))' +DO((-9223372036854775808)*(1)); +SELECT (-9223372036854775808)*(1); +(-9223372036854775808)*(1) +-9223372036854775808 +SELECT (GET_FORMAT(TIME,'JIS'))DIV(POW(-40,65)DIV(1)*2); +ERROR 22003: BIGINT value is out of range in 'pow(-40,65) DIV 1' +SELECT -9223372036854775808 MOD 9223372036854775810; +-9223372036854775808 MOD 9223372036854775810 +-9223372036854775808 +CREATE TABLE t1 (c INT); +INSERT INTO t1 VALUES(TRUNCATE(0,-1.e+30)); +DROP TABLE t1; +SELECT TRUNCATE(0, -9223372036854775808); +TRUNCATE(0, -9223372036854775808) +0 +SELECT GET_FORMAT(TIME,'JIS') DIV ATAN (TRUNCATE (0,'2000000000000000' DIV SIN(1500)*NOW(5))); +GET_FORMAT(TIME,'JIS') DIV ATAN (TRUNCATE (0,'2000000000000000' DIV SIN(1500)*NOW(5))) +NULL +SELECT (GET_FORMAT(TIME,'JIS') DIV ATAN (TRUNCATE (0,'2000000000000000' DIV SIN(1500)*NOW(5))/ROUND(-1)))DIV(-1-LOG2(1))-(-1*POWER(-1,0)); +(GET_FORMAT(TIME,'JIS') DIV ATAN (TRUNCATE (0,'2000000000000000' DIV SIN(1500)*NOW(5))/ROUND(-1)))DIV(-1-LOG2(1))-(-1*POWER(-1,0)) +NULL +# # End of 10.4 tests # diff --git a/mysql-test/main/func_math.test b/mysql-test/main/func_math.test index 51452d3e6af..4007f81fbee 100644 --- a/mysql-test/main/func_math.test +++ b/mysql-test/main/func_math.test @@ -710,7 +710,6 @@ DROP TABLE t1; SELECT 9223372036854775808 DIV 1; --error ER_DATA_OUT_OF_RANGE SELECT 9223372036854775808 DIV -1; ---error ER_DATA_OUT_OF_RANGE SELECT -9223372036854775808 DIV 1; --error ER_DATA_OUT_OF_RANGE SELECT -9223372036854775808 DIV -1; @@ -1867,6 +1866,32 @@ SELECT * FROM t2; SHOW CREATE TABLE t2; DROP TABLE t1,t2; +--echo # +--echo # MDEV-30932 UBSAN: negation of -X cannot be represented in type 'long long int'; cast to an unsigned type to negate this value to itself in Item_func_mul::int_op and Item_func_round::int_op +--echo # + +--error ER_DATA_OUT_OF_RANGE +SELECT (1 DIV(-1/POW(807,14))*1); + +DO((-9223372036854775808)*(1)); + +SELECT (-9223372036854775808)*(1); + +--error ER_DATA_OUT_OF_RANGE +SELECT (GET_FORMAT(TIME,'JIS'))DIV(POW(-40,65)DIV(1)*2); + +SELECT -9223372036854775808 MOD 9223372036854775810; + +CREATE TABLE t1 (c INT); +INSERT INTO t1 VALUES(TRUNCATE(0,-1.e+30)); +DROP TABLE t1; +SELECT TRUNCATE(0, -9223372036854775808); + +--disable_warnings +SELECT GET_FORMAT(TIME,'JIS') DIV ATAN (TRUNCATE (0,'2000000000000000' DIV SIN(1500)*NOW(5))); +SELECT (GET_FORMAT(TIME,'JIS') DIV ATAN (TRUNCATE (0,'2000000000000000' DIV SIN(1500)*NOW(5))/ROUND(-1)))DIV(-1-LOG2(1))-(-1*POWER(-1,0)); +--enable_warnings + --echo # --echo # End of 10.4 tests --echo # diff --git a/sql/item_func.cc b/sql/item_func.cc index e0d30b94067..4575990a7a2 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -78,7 +78,7 @@ bool check_reserved_words(const LEX_CSTRING *name) */ static inline bool test_if_sum_overflows_ull(ulonglong arg1, ulonglong arg2) { - return ULONGLONG_MAX - arg1 < arg2; + return ULonglong::test_if_sum_overflows_ull(arg1, arg2); } @@ -1157,14 +1157,10 @@ longlong Item_func_plus::int_op() } } -#ifndef WITH_UBSAN - res= val0 + val1; -#else if (res_unsigned) res= (longlong) ((ulonglong) val0 + (ulonglong) val1); else res= val0+val1; -#endif /* WITH_UBSAN */ return check_integer_overflow(res, res_unsigned); @@ -1325,14 +1321,10 @@ longlong Item_func_minus::int_op() goto err; } } -#ifndef WITH_UBSAN - res= val0 - val1; -#else if (res_unsigned) res= (longlong) ((ulonglong) val0 - (ulonglong) val1); else res= val0 - val1; -#endif /* WITH_UBSAN */ return check_integer_overflow(res, res_unsigned); @@ -1375,79 +1367,23 @@ double Item_func_mul::real_op() longlong Item_func_mul::int_op() { DBUG_ASSERT(fixed == 1); - longlong a= args[0]->val_int(); - longlong b= args[1]->val_int(); - longlong res; - ulonglong res0, res1; - ulong a0, a1, b0, b1; - bool res_unsigned= FALSE; - bool a_negative= FALSE, b_negative= FALSE; - - if ((null_value= args[0]->null_value || args[1]->null_value)) - return 0; - /* - First check whether the result can be represented as a - (bool unsigned_flag, longlong value) pair, then check if it is compatible - with this Item's unsigned_flag by calling check_integer_overflow(). - - Let a = a1 * 2^32 + a0 and b = b1 * 2^32 + b0. Then - a * b = (a1 * 2^32 + a0) * (b1 * 2^32 + b0) = a1 * b1 * 2^64 + - + (a1 * b0 + a0 * b1) * 2^32 + a0 * b0; - We can determine if the above sum overflows the ulonglong range by - sequentially checking the following conditions: - 1. If both a1 and b1 are non-zero. - 2. Otherwise, if (a1 * b0 + a0 * b1) is greater than ULONG_MAX. - 3. Otherwise, if (a1 * b0 + a0 * b1) * 2^32 + a0 * b0 is greater than - ULONGLONG_MAX. - Since we also have to take the unsigned_flag for a and b into account, it is easier to first work with absolute values and set the correct sign later. */ - if (!args[0]->unsigned_flag && a < 0) - { - a_negative= TRUE; - a= -a; - } - if (!args[1]->unsigned_flag && b < 0) - { - b_negative= TRUE; - b= -b; - } + Longlong_hybrid_null ha= args[0]->to_longlong_hybrid_null(); + Longlong_hybrid_null hb= args[1]->to_longlong_hybrid_null(); - a0= 0xFFFFFFFFUL & a; - a1= ((ulonglong) a) >> 32; - b0= 0xFFFFFFFFUL & b; - b1= ((ulonglong) b) >> 32; + if ((null_value= ha.is_null() || hb.is_null())) + return 0; - if (a1 && b1) - goto err; + ULonglong_null ures= ULonglong_null::ullmul(ha.abs(), hb.abs()); + if (ures.is_null()) + return raise_integer_overflow(); - res1= (ulonglong) a1 * b0 + (ulonglong) a0 * b1; - if (res1 > 0xFFFFFFFFUL) - goto err; - - res1= res1 << 32; - res0= (ulonglong) a0 * b0; - - if (test_if_sum_overflows_ull(res1, res0)) - goto err; - res= res1 + res0; - - if (a_negative != b_negative) - { - if ((ulonglong) res > (ulonglong) LONGLONG_MIN + 1) - goto err; - res= -res; - } - else - res_unsigned= TRUE; - - return check_integer_overflow(res, res_unsigned); - -err: - return raise_integer_overflow(); + return check_integer_overflow(ULonglong_hybrid(ures.value(), + ha.neg() != hb.neg())); } @@ -1645,15 +1581,8 @@ longlong Item_func_int_div::val_int() return 0; } - bool res_negative= val0.neg() != val1.neg(); - ulonglong res= val0.abs() / val1.abs(); - if (res_negative) - { - if (res > (ulonglong) LONGLONG_MAX) - return raise_integer_overflow(); - res= (ulonglong) (-(longlong) res); - } - return check_integer_overflow(res, !res_negative); + return check_integer_overflow(ULonglong_hybrid(val0.abs() / val1.abs(), + val0.neg() != val1.neg())); } @@ -1687,9 +1616,8 @@ longlong Item_func_mod::int_op() LONGLONG_MIN by -1 generates SIGFPE, we calculate using unsigned values and then adjust the sign appropriately. */ - ulonglong res= val0.abs() % val1.abs(); - return check_integer_overflow(val0.neg() ? -(longlong) res : res, - !val0.neg()); + return check_integer_overflow(ULonglong_hybrid(val0.abs() % val1.abs(), + val0.neg())); } double Item_func_mod::real_op() @@ -2669,7 +2597,7 @@ longlong Item_func_round::int_op() if ((dec >= 0) || args[1]->unsigned_flag) return value; // integer have not digits after point - abs_dec= -dec; + abs_dec= Longlong(dec).abs(); // Avoid undefined behavior longlong tmp; if(abs_dec >= array_elements(log_10_int)) diff --git a/sql/item_func.h b/sql/item_func.h index 3afe0d00661..7828078ac7b 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -252,12 +252,23 @@ public: */ inline longlong check_integer_overflow(longlong value, bool val_unsigned) { - if ((unsigned_flag && !val_unsigned && value < 0) || - (!unsigned_flag && val_unsigned && - (ulonglong) value > (ulonglong) LONGLONG_MAX)) - return raise_integer_overflow(); - return value; + return check_integer_overflow(Longlong_hybrid(value, val_unsigned)); } + + // Check if the value is compatible with Item::unsigned_flag. + inline longlong check_integer_overflow(const Longlong_hybrid &sval) + { + Longlong_null res= sval.val_int(unsigned_flag); + return res.is_null() ? raise_integer_overflow() : res.value(); + } + + // Check if the value is compatible with Item::unsigned_flag. + longlong check_integer_overflow(const ULonglong_hybrid &uval) + { + Longlong_null res= uval.val_int(unsigned_flag); + return res.is_null() ? raise_integer_overflow() : res.value(); + } + /** Throw an error if the error code of a DECIMAL operation is E_DEC_OVERFLOW. */ diff --git a/sql/sql_type_int.h b/sql/sql_type_int.h index d3e9d0318cf..7bb9200c4e1 100644 --- a/sql/sql_type_int.h +++ b/sql/sql_type_int.h @@ -34,6 +34,12 @@ protected: public: longlong value() const { return m_value; } Longlong(longlong nr) :m_value(nr) { } + ulonglong abs() + { + if (m_value == LONGLONG_MIN) // avoid undefined behavior + return ((ulonglong) LONGLONG_MAX) + 1; + return m_value < 0 ? -m_value : m_value; + } }; @@ -46,6 +52,86 @@ public: }; +class ULonglong +{ +protected: + ulonglong m_value; +public: + ulonglong value() const { return m_value; } + explicit ULonglong(ulonglong nr) :m_value(nr) { } + + static bool test_if_sum_overflows_ull(ulonglong arg1, ulonglong arg2) + { + return ULONGLONG_MAX - arg1 < arg2; + } + + Longlong_null operator-() const + { + if (m_value > (ulonglong) LONGLONG_MAX) // Avoid undefined behaviour + { + return m_value == (ulonglong) LONGLONG_MAX + 1 ? + Longlong_null(LONGLONG_MIN, false) : + Longlong_null(0, true); + } + return Longlong_null(-(longlong) m_value, false); + } + + // Convert to Longlong_null with the range check + Longlong_null to_longlong_null() const + { + if (m_value > (ulonglong) LONGLONG_MAX) + return Longlong_null(0, true); + return Longlong_null((longlong) m_value, false); + } + +}; + + +class ULonglong_null: public ULonglong, public Null_flag +{ +public: + ULonglong_null(ulonglong nr, bool is_null) + :ULonglong(nr), Null_flag(is_null) + { } + + /* + Multiply two ulonglong values. + + Let a = a1 * 2^32 + a0 and b = b1 * 2^32 + b0. Then + a * b = (a1 * 2^32 + a0) * (b1 * 2^32 + b0) = a1 * b1 * 2^64 + + + (a1 * b0 + a0 * b1) * 2^32 + a0 * b0; + We can determine if the above sum overflows the ulonglong range by + sequentially checking the following conditions: + 1. If both a1 and b1 are non-zero. + 2. Otherwise, if (a1 * b0 + a0 * b1) is greater than ULONG_MAX. + 3. Otherwise, if (a1 * b0 + a0 * b1) * 2^32 + a0 * b0 is greater than + ULONGLONG_MAX. + */ + static ULonglong_null ullmul(ulonglong a, ulonglong b) + { + ulong a1= a >> 32; + ulong b1= b >> 32; + + if (a1 && b1) + return ULonglong_null(0, true); + + ulong a0= 0xFFFFFFFFUL & a; + ulong b0= 0xFFFFFFFFUL & b; + + ulonglong res1= (ulonglong) a1 * b0 + (ulonglong) a0 * b1; + if (res1 > 0xFFFFFFFFUL) + return ULonglong_null(0, true); + + res1= res1 << 32; + ulonglong res0= (ulonglong) a0 * b0; + + if (test_if_sum_overflows_ull(res1, res0)) + return ULonglong_null(0, true); + return ULonglong_null(res1 + res0, false); + } +}; + + // A longlong/ulonglong hybrid. Good to store results of val_int(). class Longlong_hybrid: public Longlong { @@ -74,9 +160,7 @@ public: { if (m_unsigned) return (ulonglong) m_value; - if (m_value == LONGLONG_MIN) // avoid undefined behavior - return ((ulonglong) LONGLONG_MAX) + 1; - return m_value < 0 ? -m_value : m_value; + return Longlong(m_value).abs(); } /* Convert to an unsigned number: @@ -94,6 +178,33 @@ public: { return (uint) to_ulonglong(upper_bound); } + + + Longlong_null val_int_signed() const + { + if (m_unsigned) + return ULonglong((ulonglong) m_value).to_longlong_null(); + return Longlong_null(m_value, false); + } + + Longlong_null val_int_unsigned() const + { + if (!m_unsigned && m_value < 0) + return Longlong_null(0, true); + return Longlong_null(m_value, false); + } + + /* + Return in Item compatible val_int() format: + - signed numbers as a straight longlong value + - unsigned numbers as a ulonglong value reinterpreted to longlong + */ + Longlong_null val_int(bool want_unsigned_value) const + { + return want_unsigned_value ? val_int_unsigned() : + val_int_signed(); + } + int cmp(const Longlong_hybrid& other) const { if (m_unsigned == other.m_unsigned) @@ -143,4 +254,50 @@ public: }; +/* + Stores the absolute value of a number, and the sign. + Value range: -ULONGLONG_MAX .. +ULONGLONG_MAX. + + Provides a wider range for negative numbers than Longlong_hybrid does. + Usefull to store intermediate results of an expression whose value + is further needed to be negated. For example, these methods: + - Item_func_mul::int_op() + - Item_func_int_div::val_int() + - Item_func_mod::int_op() + calculate the result of absolute values of the arguments, + then optionally negate the result. +*/ +class ULonglong_hybrid: public ULonglong +{ + bool m_neg; +public: + ULonglong_hybrid(ulonglong value, bool neg) + :ULonglong(value), m_neg(neg) + { + if (m_neg && !m_value) + m_neg= false; // convert -0 to +0 + } + Longlong_null val_int_unsigned() const + { + return m_neg ? Longlong_null(0, true) : + Longlong_null((longlong) m_value, false); + } + Longlong_null val_int_signed() const + { + return m_neg ? -ULonglong(m_value) : ULonglong::to_longlong_null(); + } + + /* + Return in Item compatible val_int() format: + - signed numbers as a straight longlong value + - unsigned numbers as a ulonglong value reinterpreted to longlong + */ + Longlong_null val_int(bool want_unsigned_value) const + { + return want_unsigned_value ? val_int_unsigned() : + val_int_signed(); + } +}; + + #endif // SQL_TYPE_INT_INCLUDED