Description:
https://dev.mysql.com/doc/extending-mysql/8.0/en/adding-loadable-function.html#loadable-fu...
Has numerous reference to char *error and char *is_null.
Code however lists both as unsigned char *:
ref: https://github.com/mysql/mysql-server/blob/6b6d3ed3d5c6591b446276184642d7d0504ecc86/includ...
char in the C standard is undefined as to if its signed or unsigned so different architectures (looking at you PPC64 and ARM64) aren't always consistent (which isn't a problem for plugins that are always of same arch as the server).
If you compiled MySQL under cmake -DWITH_UBSAN=ON the error would be:
/home/dan/repos/mysql-server-8.4/sql/item_func.cc:4821:22: runtime error: call to function myfunc_double through pointer to incorrect function type 'double (*)(UDF_INIT *, UDF_ARGS *, unsigned char *, unsigned char *)'
How to repeat:
Adjust MySQL example code conform to documented ABI:
diff --git a/sql/udf_example.cc b/sql/udf_example.cc
index 79027c40873..e8f03a3c470 100644
--- a/sql/udf_example.cc
+++ b/sql/udf_example.cc
@@ -248,8 +248,8 @@ static char codes[26] = {
#define NOGHTOF(x) (codes[(x) - 'A'] & 16) /* BDH */
extern "C" char *metaphon(UDF_INIT *, UDF_ARGS *args, char *result,
- unsigned long *length, unsigned char *is_null,
- unsigned char *) {
+ unsigned long *length, char *is_null,
+ char *) {
const char *word = args->args[0];
const char *w_end;
char *org_result;
@@ -471,7 +471,7 @@ extern "C" bool myfunc_double_init(UDF_INIT *initid, UDF_ARGS *args,
}
extern "C" double myfunc_double(UDF_INIT *, UDF_ARGS *args,
- unsigned char *is_null, unsigned char *) {
+ char *is_null, char *) {
unsigned long val = 0;
unsigned long v = 0;
unsigned i, j;
$ clang --version
clang version 19.1.7 (Fedora 19.1.7-3.fc41)
$ cmake -DWITH_UBSAN=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ ../mysql-server-8.4
$ cmake --build .
$ UBSAN_OPTIONS=print_stacktrace=1,halt_on_error=1 mysql-test/mtr udf
Logging: /home/dan/repos/mysql-server-8.4/mysql-test/mysql-test-run.pl udf
MySQL Version 8.4.4
Checking supported features
Using 'all' suites
Collecting tests
Checking leftover processes
Removing old var directory
Creating var directory '/home/dan/repos/build-mysql-server-8.4/mysql-test/var'
Installing system database
Using parallel: 1
==============================================================================
TEST NAME RESULT TIME (ms) COMMENT
------------------------------------------------------------------------------
[ 50%] main.udf [ fail ]
Test ended at 2025-04-08 16:57:15
CURRENT_TEST: main.udf
mysqltest: At line 53: Query 'select myfunc_double(1)' failed.
ERROR 2013 (HY000): Lost connection to MySQL server during query
The result from queries just before the failure was:
...
select myfunc_double();
ERROR HY000: Can't initialize function 'myfunc_double'; myfunc_double must have at least one argument
select myfunc_double(1);
safe_process[346005]: Child process: 346006, exit: 1
...
2025-04-08T06:57:15.368777Z 0 [Note] [MY-011243] [Server] Plugin mysqlx reported: 'Using OpenSSL for TLS connections'
2025-04-08T06:57:15.368925Z 0 [System] [MY-011323] [Server] X Plugin ready for connections. Bind-address: '::' port: 13020, socket: /home/dan/repos/build-mysql-server-8.4/mysql-test/var/tmp/mysqlx.1.sock
2025-04-08T06:57:15.368947Z 0 [System] [MY-010931] [Server] /home/dan/repos/build-mysql-server-8.4/runtime_output_directory/mysqld: ready for connections. Version: '8.4.4-ubsan' socket: '/home/dan/repos/build-mysql-server-8.4/mysql-test/var/tmp/mysqld.1.sock' port: 13000 Source distribution.
/home/dan/repos/mysql-server-8.4/sql/item_func.cc:4821:22: runtime error: call to function myfunc_double through pointer to incorrect function type 'double (*)(UDF_INIT *, UDF_ARGS *, unsigned char *, unsigned char *)'
/home/dan/repos/mysql-server-8.4/sql/udf_example.cc:474: note: myfunc_double defined here
#0 0x0000012bbefc in udf_handler::val_real(bool*) /home/dan/repos/mysql-server-8.4/sql/item_func.cc:4821:22
#1 0x0000012bd05b in Item_func_udf_float::val_real() /home/dan/repos/mysql-server-8.4/sql/item_func.cc:5061:14
#2 0x00000112f3af in Item::send(Protocol*, String*) /home/dan/repos/mysql-server-8.4/sql/item.cc:7597:25
#3 0x000000647dee in THD::send_result_set_row(mem_root_deque<Item*> const&) /home/dan/repos/mysql-server-8.4/sql/sql_class.cc:2897:15
#4 0x0000017217e0 in Query_result_send::send_data(THD*, mem_root_deque<Item*> const&) /home/dan/repos/mysql-server-8.4/sql/query_result.cc:103:12
#5 0x000000af68b7 in Query_expression::ExecuteIteratorQuery(THD*) /home/dan/repos/mysql-server-8.4/sql/sql_union.cc:1793:25
#6 0x000000af7867 in Query_expression::execute(THD*) /home/dan/repos/mysql-server-8.4/sql/sql_union.cc:1834:10
#7 0x00000096753d in Sql_cmd_dml::execute_inner(THD*) /home/dan/repos/mysql-server-8.4/sql/sql_select.cc:1059:15
#8 0x000000962fa1 in Sql_cmd_dml::execute(THD*) /home/dan/repos/mysql-server-8.4/sql/sql_select.cc:782:7
#9 0x000000807832 in mysql_execute_command(THD*, bool) /home/dan/repos/mysql-server-8.4/sql/sql_parse.cc:4737:29
#10 0x0000007fdc29 in dispatch_sql_command(THD*, Parser_state*) /home/dan/repos/mysql-server-8.4/sql/sql_parse.cc:5392:19
#11 0x0000007f0286 in dispatch_command(THD*, COM_DATA const*, enum_server_command) /home/dan/repos/mysql-server-8.4/sql/sql_parse.cc:2136:7
#12 0x0000007f9153 in do_command(THD*) /home/dan/repos/mysql-server-8.4/sql/sql_parse.cc:1465:18
#13 0x000000cd3363 in handle_connection(void*) /home/dan/repos/mysql-server-8.4/sql/conn_handler/connection_handler_per_thread.cc:304:13
#14 0x000003d61e95 in pfs_spawn_thread(void*) /home/dan/repos/mysql-server-8.4/storage/perfschema/pfs.cc:3061:3
#15 0x7f3f70b97ba7 in start_thread (/lib64/libc.so.6+0x70ba7) (BuildId: f83d43b9b4b0ed5c2bd0a1613bf33e08ee054c93)
#16 0x7f3f70c1bb8b in __GI___clone3 (/lib64/libc.so.6+0xf4b8b) (BuildId: f83d43b9b4b0ed5c2bd0a1613bf33e08ee054c93)
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /home/dan/repos/mysql-server-8.4/sql/item_func.cc:4821:22
safe_process[345951]: Child process: 345952, exit: 1
----------SERVER LOG END-------------
- the logfile can be found in '/home/dan/repos/build-mysql-server-8.4/mysql-test/var/log/main.udf/udf.log'
[100%] shutdown_report [ pass ]
------------------------------------------------------------------------------
The servers were restarted 0 times
The servers were reinitialized 0 times
Spent 0.000 of 9 seconds executing testcases
Completed: Failed 1/2 tests, 50.00% were successful.
Failing test(s): main.udf
The log files in var/log may give you some hint of what went wrong.
If you want to report this error, please read first the documentation
at http://dev.mysql.com/doc/mysql/en/mysql-test-suite.html
mysql-test-run: *** ERROR: there were failing test cases
Suggested fix:
Update documentation to refer to "unsigned char *" as the type for is_null and error arguments.
The proliferation of the unsigned char * as a stable ABI has propagated beyond MySQL:
https://github.com/pluots/sql-udf/blob/35a9bf5a122ee138ae29a0b5aae013f5035dee22/udf-macros...
https://github.com/pluots/sql-udf/blob/35a9bf5a122ee138ae29a0b5aae013f5035dee22/udf/src/wr...
For those that haven't noticed, probably because the error /is_null really only get pushed boolean matters it won't change.
For those engaged with type safe wrappers and checking who would have moved to unsigned char, and change of ABI would be noticed. So safest just to update documentation.