mirror of
https://github.com/MariaDB/server.git
synced 2025-01-15 19:42:28 +01:00
MDEV-30931 UBSAN: negation of -X cannot be represented in type 'long long int'; cast to an unsigned type to negate this value to itself in get_interval_value on SELECT
- Fixing the code in get_interval_value() to use Longlong_hybrid_null. This allows to handle correctly: - Signed and unsigned arguments (the old code assumed the argument to be signed) - Avoid undefined negation behavior the corner case with LONGLONG_MIN This fixes the UBSAN warning: negation of -9223372036854775808 cannot be represented in type 'long long int'; - Fixing the code in get_interval_value() to avoid overflow in the INTERVAL_QUARTER and INTERVAL_WEEK branches. This fixes the UBSAN warning: signed integer overflow: -9223372036854775808 * 7 cannot be represented in type 'long long int' - Fixing the INTERVAL_WEEK branch in date_add_interval() to handle huge numbers correctly. Before the change, huge positive numeber were treated as their negative complements. Note, some other branches still can be affected by this problem and should also be fixed eventually.
This commit is contained in:
parent
44b23bb184
commit
7925326183
5 changed files with 83 additions and 17 deletions
|
@ -200,3 +200,34 @@ select 30 + (20010101 + interval 2 day), x from v1;
|
|||
20010133 20010133
|
||||
drop view v1;
|
||||
End of 10.2 tests
|
||||
#
|
||||
# Start of 10.5 tests
|
||||
#
|
||||
#
|
||||
# MDEV-30931 UBSAN: negation of -X cannot be represented in type 'long long int'; cast to an unsigned type to negate this value to itself in get_interval_value on SELECT
|
||||
#
|
||||
SELECT DATE_ADD('01-01-23',INTERVAL '9223372036854775808-02' WEEK);
|
||||
DATE_ADD('01-01-23',INTERVAL '9223372036854775808-02' WEEK)
|
||||
NULL
|
||||
Warnings:
|
||||
Warning 1292 Truncated incorrect INTEGER value: '9223372036854775808-02'
|
||||
Warning 1441 Datetime function: datetime field overflow
|
||||
SELECT DATE_ADD('01-01-23',INTERVAL -9223372036854775807 WEEK);
|
||||
DATE_ADD('01-01-23',INTERVAL -9223372036854775807 WEEK)
|
||||
NULL
|
||||
Warnings:
|
||||
Warning 1441 Datetime function: datetime field overflow
|
||||
SELECT DATE_ADD('01-01-23',INTERVAL -9223372036854775808 WEEK);
|
||||
DATE_ADD('01-01-23',INTERVAL -9223372036854775808 WEEK)
|
||||
NULL
|
||||
Warnings:
|
||||
Warning 1441 Datetime function: datetime field overflow
|
||||
SELECT DATE_ADD('01-01-23',INTERVAL -9223372036854775809 WEEK);
|
||||
DATE_ADD('01-01-23',INTERVAL -9223372036854775809 WEEK)
|
||||
NULL
|
||||
Warnings:
|
||||
Warning 1916 Got overflow when converting '-9223372036854775809' to INT. Value truncated
|
||||
Warning 1441 Datetime function: datetime field overflow
|
||||
#
|
||||
# End of 10.5 tests
|
||||
#
|
||||
|
|
|
@ -169,3 +169,20 @@ select 30 + (20010101 + interval 2 day), x from v1;
|
|||
drop view v1;
|
||||
|
||||
--echo End of 10.2 tests
|
||||
|
||||
--echo #
|
||||
--echo # Start of 10.5 tests
|
||||
--echo #
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-30931 UBSAN: negation of -X cannot be represented in type 'long long int'; cast to an unsigned type to negate this value to itself in get_interval_value on SELECT
|
||||
--echo #
|
||||
|
||||
SELECT DATE_ADD('01-01-23',INTERVAL '9223372036854775808-02' WEEK);
|
||||
SELECT DATE_ADD('01-01-23',INTERVAL -9223372036854775807 WEEK);
|
||||
SELECT DATE_ADD('01-01-23',INTERVAL -9223372036854775808 WEEK);
|
||||
SELECT DATE_ADD('01-01-23',INTERVAL -9223372036854775809 WEEK);
|
||||
|
||||
--echo #
|
||||
--echo # End of 10.5 tests
|
||||
--echo #
|
||||
|
|
|
@ -1306,6 +1306,15 @@ my_decimal *Item_func_time_to_sec::decimal_op(my_decimal* buf)
|
|||
}
|
||||
|
||||
|
||||
static inline
|
||||
uint32 adjust_interval_field_uint32(ulonglong value, int32 multiplier)
|
||||
{
|
||||
return value > ((ulonglong) (uint32) (UINT_MAX32)) / multiplier ?
|
||||
(uint32) UINT_MAX32 :
|
||||
(uint32) (value * multiplier);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Convert a string to a interval value.
|
||||
|
||||
|
@ -1316,7 +1325,7 @@ bool get_interval_value(THD *thd, Item *args,
|
|||
interval_type int_type, INTERVAL *interval)
|
||||
{
|
||||
ulonglong array[5];
|
||||
longlong UNINIT_VAR(value);
|
||||
ulonglong UNINIT_VAR(value);
|
||||
const char *UNINIT_VAR(str);
|
||||
size_t UNINIT_VAR(length);
|
||||
CHARSET_INFO *UNINIT_VAR(cs);
|
||||
|
@ -1343,14 +1352,17 @@ bool get_interval_value(THD *thd, Item *args,
|
|||
}
|
||||
else if ((int) int_type <= INTERVAL_MICROSECOND)
|
||||
{
|
||||
value= args->val_int();
|
||||
if (args->null_value)
|
||||
return 1;
|
||||
if (value < 0)
|
||||
{
|
||||
interval->neg=1;
|
||||
value= -value;
|
||||
}
|
||||
/*
|
||||
Let's use Longlong_hybrid_null to handle correctly:
|
||||
- signed and unsigned values
|
||||
- the corner case with LONGLONG_MIN
|
||||
(avoid undefined behavior with its negation)
|
||||
*/
|
||||
const Longlong_hybrid_null nr= args->to_longlong_hybrid_null();
|
||||
if (nr.is_null())
|
||||
return true;
|
||||
value= nr.abs();
|
||||
interval->neg= nr.neg() ? 1 : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1377,13 +1389,13 @@ bool get_interval_value(THD *thd, Item *args,
|
|||
interval->year= (ulong) value;
|
||||
break;
|
||||
case INTERVAL_QUARTER:
|
||||
interval->month= (ulong)(value*3);
|
||||
interval->month= adjust_interval_field_uint32(value, 3);
|
||||
break;
|
||||
case INTERVAL_MONTH:
|
||||
interval->month= (ulong) value;
|
||||
break;
|
||||
case INTERVAL_WEEK:
|
||||
interval->day= (ulong)(value*7);
|
||||
interval->day= adjust_interval_field_uint32(value, 7);
|
||||
break;
|
||||
case INTERVAL_DAY:
|
||||
interval->day= (ulong) value;
|
||||
|
|
|
@ -930,7 +930,7 @@ void make_truncated_value_warning(THD *thd,
|
|||
bool date_add_interval(THD *thd, MYSQL_TIME *ltime, interval_type int_type,
|
||||
const INTERVAL &interval, bool push_warn)
|
||||
{
|
||||
long period, sign;
|
||||
long sign;
|
||||
|
||||
sign= (interval.neg == (bool)ltime->neg ? 1 : -1);
|
||||
|
||||
|
@ -1001,13 +1001,17 @@ bool date_add_interval(THD *thd, MYSQL_TIME *ltime, interval_type int_type,
|
|||
break;
|
||||
}
|
||||
case INTERVAL_WEEK:
|
||||
period= (calc_daynr(ltime->year,ltime->month,ltime->day) +
|
||||
sign * (long) interval.day);
|
||||
{
|
||||
longlong period= calc_daynr(ltime->year, ltime->month, ltime->day) +
|
||||
(longlong) sign * (longlong) interval.day;
|
||||
if (period < 0 || period > 0x7FFFFFFF)
|
||||
goto invalid_date;
|
||||
/* Daynumber from year 0 to 9999-12-31 */
|
||||
if (get_date_from_daynr((long) period,<ime->year,<ime->month,
|
||||
<ime->day))
|
||||
goto invalid_date;
|
||||
break;
|
||||
}
|
||||
case INTERVAL_YEAR:
|
||||
ltime->year+= sign * (long) interval.year;
|
||||
if ((ulong) ltime->year >= 10000L)
|
||||
|
@ -1019,8 +1023,9 @@ bool date_add_interval(THD *thd, MYSQL_TIME *ltime, interval_type int_type,
|
|||
case INTERVAL_YEAR_MONTH:
|
||||
case INTERVAL_QUARTER:
|
||||
case INTERVAL_MONTH:
|
||||
period= (ltime->year*12 + sign * (long) interval.year*12 +
|
||||
ltime->month-1 + sign * (long) interval.month);
|
||||
{
|
||||
long period= (ltime->year*12 + sign * (long) interval.year*12 +
|
||||
ltime->month-1 + sign * (long) interval.month);
|
||||
if ((ulong) period >= 120000L)
|
||||
goto invalid_date;
|
||||
ltime->year= (uint) (period / 12);
|
||||
|
@ -1033,6 +1038,7 @@ bool date_add_interval(THD *thd, MYSQL_TIME *ltime, interval_type int_type,
|
|||
ltime->day++; // Leap-year
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
goto null_date;
|
||||
}
|
||||
|
|
|
@ -241,7 +241,7 @@ end4:
|
|||
*endptr= (char*) s;
|
||||
if (negative)
|
||||
{
|
||||
if (li >= MAX_NEGATIVE_NUMBER)
|
||||
if (li >= MAX_NEGATIVE_NUMBER) // Avoid undefined behavior
|
||||
goto overflow;
|
||||
return -((longlong) li);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue