Description:
A loadable function that takes its argument as STRING_RESULT is handed bytes
that another row has partly overwritten. In udf_handler::get_and_convert_string
(sql/item_func.cc), the argument buffer handed to the function shares its bytes
with the result of CONCAT, and the next row's CAST writes into the same memory,
so each row's number lands on the first bytes of its own result.
With a function f(str) that returns its argument unchanged:
SELECT n, f(CONCAT('customer-', CAST(n AS CHAR))) AS got
FROM (SELECT 1 AS n UNION ALL SELECT 2 UNION ALL SELECT 3) AS t;
the function receives '2ustomer-2' for the row that holds 'customer-2'.
Affected: 8.4.0 through 8.4.11. Not affected: 8.0.40, 8.0.44, 9.0, and
MariaDB 11.4 (9.0 copies the argument into its own buffer; the others do not
share the buffer).
How to repeat:
1. Build a loadable function f(str) that returns the string argument it
received, unchanged. In its init function leave the argument type as
STRING_RESULT (do not change args->arg_type[0]).
2. CREATE FUNCTION f RETURNS STRING SONAME '<the library>';
3. Run:
SELECT n, f(CONCAT('customer-', CAST(n AS CHAR))) AS got
FROM (SELECT 1 AS n UNION ALL SELECT 2 UNION ALL SELECT 3) AS t;
Expected: each row's got equals customer-<n>.
Observed on 8.4.0 through 8.4.11: the function is handed '2ustomer-2' for
the row holding 'customer-2'; the row's number overwrote the first byte of
its own string.
Workaround that shows the cause: declaring the argument as DECIMAL_RESULT in
the init function (args->arg_type[0] = DECIMAL_RESULT) makes the server pass
the value's own bytes and the function sees 'customer-2'. For text the
server reads the argument with the same call either way and never parses it
as a number, so the only difference is the buffer.
Suggested fix:
In udf_handler::get_and_convert_string, copy the String result into a buffer
the handler owns before handing it to the function, as 9.0 does, instead of
pointing the argument at the item's result buffer that CONCAT and the next
row's CAST share.
Description: A loadable function that takes its argument as STRING_RESULT is handed bytes that another row has partly overwritten. In udf_handler::get_and_convert_string (sql/item_func.cc), the argument buffer handed to the function shares its bytes with the result of CONCAT, and the next row's CAST writes into the same memory, so each row's number lands on the first bytes of its own result. With a function f(str) that returns its argument unchanged: SELECT n, f(CONCAT('customer-', CAST(n AS CHAR))) AS got FROM (SELECT 1 AS n UNION ALL SELECT 2 UNION ALL SELECT 3) AS t; the function receives '2ustomer-2' for the row that holds 'customer-2'. Affected: 8.4.0 through 8.4.11. Not affected: 8.0.40, 8.0.44, 9.0, and MariaDB 11.4 (9.0 copies the argument into its own buffer; the others do not share the buffer). How to repeat: 1. Build a loadable function f(str) that returns the string argument it received, unchanged. In its init function leave the argument type as STRING_RESULT (do not change args->arg_type[0]). 2. CREATE FUNCTION f RETURNS STRING SONAME '<the library>'; 3. Run: SELECT n, f(CONCAT('customer-', CAST(n AS CHAR))) AS got FROM (SELECT 1 AS n UNION ALL SELECT 2 UNION ALL SELECT 3) AS t; Expected: each row's got equals customer-<n>. Observed on 8.4.0 through 8.4.11: the function is handed '2ustomer-2' for the row holding 'customer-2'; the row's number overwrote the first byte of its own string. Workaround that shows the cause: declaring the argument as DECIMAL_RESULT in the init function (args->arg_type[0] = DECIMAL_RESULT) makes the server pass the value's own bytes and the function sees 'customer-2'. For text the server reads the argument with the same call either way and never parses it as a number, so the only difference is the buffer. Suggested fix: In udf_handler::get_and_convert_string, copy the String result into a buffer the handler owns before handing it to the function, as 9.0 does, instead of pointing the argument at the item's result buffer that CONCAT and the next row's CAST share.