Description:
The manual says of CONV(N, from_base, to_base): "The minimum base is 2 and
the maximum base is 36", and CONV() returns NULL for a base outside that
range, e.g. 37. But both bases are first assigned to an int, so the range
check sees only the low 32 bits:
- from_base or to_base = 4294967298 (2^32 + 2) is accepted as base 2;
- -4294967294 is accepted as base 2 too;
- to_base = 4294967286 (2^32 - 10) becomes -10, i.e. a *negative* base,
which CONV() treats as "signed output": CONV('-1', 10, 4294967286)
returns '-1', the result for to_base -10, where to_base 10 gives
18446744073709551615;
- a BIGINT column holding 4294967312 (2^32 + 16) is used as base 16.
No warning is given. A value that does not fit in BIGINT at all
(18446744073709551618) does return NULL with a warning, so it is only the
range 2^31 .. 2^63-1 (and its negative) that is silently narrowed.
Cause: sql/item_strfunc.cc, Item_func_conv::val_str(), trunk 3b99be40:
const int from_base = args[1]->val_int();
...
const int to_base = args[2]->val_int();
...
if (from_base == INT_MIN || to_base == INT_MIN || abs(to_base) > 36 ||
abs(to_base) < 2 || abs(from_base) > 36 || abs(from_base) < 2 ||
val_int() returns a longlong; the implicit conversion to int keeps the low
32 bits, and the range check is applied to that.
How to repeat:
SELECT CONV('11', 2, 10) AS b2, CONV('11', 4294967298, 10) AS b2p32_2,
CONV('11', -4294967294, 10) AS bm2p32_2, CONV('11', 37, 10) AS b37;
+------+---------+----------+------+
| b2 | b2p32_2 | bm2p32_2 | b37 |
+------+---------+----------+------+
| 3 | 3 | 3 | NULL |
+------+---------+----------+------+
SELECT CONV('3', 10, 2) AS t2, CONV('3', 10, 4294967298) AS t2p32_2,
CONV('3', 10, 37) AS t37;
+------+---------+------+
| t2 | t2p32_2 | t37 |
+------+---------+------+
| 11 | 11 | NULL |
+------+---------+------+
SELECT CONV('-1', 10, 10) AS unsigned_out, CONV('-1', 10, -10) AS signed_out,
CONV('-1', 10, 4294967286) AS t2p32_m10;
+----------------------+------------+-----------+
| unsigned_out | signed_out | t2p32_m10 |
+----------------------+------------+-----------+
| 18446744073709551615 | -1 | -1 |
+----------------------+------------+-----------+
CREATE TABLE t015 (b BIGINT);
INSERT INTO t015 VALUES (16), (4294967312);
SELECT b, CONV('ff', b, 10) AS v FROM t015 ORDER BY b;
+------------+------+
| b | v |
+------------+------+
| 16 | 255 |
| 4294967312 | 255 |
+------------+------+
Expected: NULL for every base whose absolute value is outside 2..36, as for
37. For 4294967298, -4294967294, 4294967286 and 4294967312 the result
should be NULL.
Suggested fix:
Read both bases as longlong and range-check them before narrowing:
const longlong from_base = args[1]->val_int();
const longlong to_base = args[2]->val_int();
if (from_base < -36 || from_base > 36 || (from_base > -2 && from_base < 2) ||
to_base < -36 || to_base > 36 || (to_base > -2 && to_base < 2) || ...)
(an unsigned argument above LLONG_MAX also needs args[n]->unsigned_flag, or
it would wrap into the negative range).
Description: The manual says of CONV(N, from_base, to_base): "The minimum base is 2 and the maximum base is 36", and CONV() returns NULL for a base outside that range, e.g. 37. But both bases are first assigned to an int, so the range check sees only the low 32 bits: - from_base or to_base = 4294967298 (2^32 + 2) is accepted as base 2; - -4294967294 is accepted as base 2 too; - to_base = 4294967286 (2^32 - 10) becomes -10, i.e. a *negative* base, which CONV() treats as "signed output": CONV('-1', 10, 4294967286) returns '-1', the result for to_base -10, where to_base 10 gives 18446744073709551615; - a BIGINT column holding 4294967312 (2^32 + 16) is used as base 16. No warning is given. A value that does not fit in BIGINT at all (18446744073709551618) does return NULL with a warning, so it is only the range 2^31 .. 2^63-1 (and its negative) that is silently narrowed. Cause: sql/item_strfunc.cc, Item_func_conv::val_str(), trunk 3b99be40: const int from_base = args[1]->val_int(); ... const int to_base = args[2]->val_int(); ... if (from_base == INT_MIN || to_base == INT_MIN || abs(to_base) > 36 || abs(to_base) < 2 || abs(from_base) > 36 || abs(from_base) < 2 || val_int() returns a longlong; the implicit conversion to int keeps the low 32 bits, and the range check is applied to that. How to repeat: SELECT CONV('11', 2, 10) AS b2, CONV('11', 4294967298, 10) AS b2p32_2, CONV('11', -4294967294, 10) AS bm2p32_2, CONV('11', 37, 10) AS b37; +------+---------+----------+------+ | b2 | b2p32_2 | bm2p32_2 | b37 | +------+---------+----------+------+ | 3 | 3 | 3 | NULL | +------+---------+----------+------+ SELECT CONV('3', 10, 2) AS t2, CONV('3', 10, 4294967298) AS t2p32_2, CONV('3', 10, 37) AS t37; +------+---------+------+ | t2 | t2p32_2 | t37 | +------+---------+------+ | 11 | 11 | NULL | +------+---------+------+ SELECT CONV('-1', 10, 10) AS unsigned_out, CONV('-1', 10, -10) AS signed_out, CONV('-1', 10, 4294967286) AS t2p32_m10; +----------------------+------------+-----------+ | unsigned_out | signed_out | t2p32_m10 | +----------------------+------------+-----------+ | 18446744073709551615 | -1 | -1 | +----------------------+------------+-----------+ CREATE TABLE t015 (b BIGINT); INSERT INTO t015 VALUES (16), (4294967312); SELECT b, CONV('ff', b, 10) AS v FROM t015 ORDER BY b; +------------+------+ | b | v | +------------+------+ | 16 | 255 | | 4294967312 | 255 | +------------+------+ Expected: NULL for every base whose absolute value is outside 2..36, as for 37. For 4294967298, -4294967294, 4294967286 and 4294967312 the result should be NULL. Suggested fix: Read both bases as longlong and range-check them before narrowing: const longlong from_base = args[1]->val_int(); const longlong to_base = args[2]->val_int(); if (from_base < -36 || from_base > 36 || (from_base > -2 && from_base < 2) || to_base < -36 || to_base > 36 || (to_base > -2 && to_base < 2) || ...) (an unsigned argument above LLONG_MAX also needs args[n]->unsigned_flag, or it would wrap into the negative range).