Description:
UDF's are called in this sequence according to the docs:
xxx_init()
xxx()
xxx_deinit()
When an UDF is used in a prepared statement the calling sequence is:
PREPARE... xxx_init()
EXECUTE... xxx_init()
xxx()
...
DEALLOCATE... xxx_deinit()
As you can see the execution of the prepared statement erroneously calls xxx_init()
which leads to severe memory leaks. This is also valid if the udf doesn't allocate
memory itself.
How to repeat:
The below sequence uses the udf source code appended below.
The udf function appends to /tmp/testudf.log which should be
viewed:
1. after select...
testudf_init 15205
testudf 15205
testudf 15205
testudf_deinit 15205
2. after prepare...
testudf_init 15205
testudf 15205
testudf 15205
testudf_deinit 15205
testudf_init 15205
3. after every execute...
testudf_init 15205
testudf 15205
testudf 15205
testudf_deinit 15205
testudf_init 15205
testudf_init 15205 <======
testudf 15205
testudf 15205
testudf_init 15205
testudf 15205
testudf 15205
testudf_deinit 15205
testudf_init 15205
testudf_init 15205 <=======
testudf 15205
testudf 15205
testudf_init 15205 <=======
testudf 15205
testudf 15205
4. after deallocate...
testudf_init 15205
testudf 15205
testudf 15205
testudf_deinit 15205
testudf_init 15205
testudf_init 15205 <=======
testudf 15205
testudf 15205
testudf_init 15205 <=======
testudf 15205
testudf 15205
testudf_deinit 15205
=============================================
create function testudf returns string soname 'testudf.so';
create database udftest;
use udftest;
create table udftest(v1 integer,v2 text);
insert into udftest values(1,'b'),(2,'a');
select v1,v2 from udftest order by testudf(v2);
prepare tst from 'select v1,v2 from udftest order by testudf(v2)';
execute tst;
execute tst;
deallocate prepare tst;
=============================================
#ifdef STANDARD
#include <stdio.h>
#include <string.h>
#ifdef __WIN__
typedef unsigned __int64 ulonglong; /* Microsofts 64 bit types */
typedef __int64 longlong;
#else
typedef unsigned long long ulonglong;
typedef long long longlong;
#endif /*__WIN__*/
#else
#include <my_global.h>
#include <my_sys.h>
#endif
#include <mysql.h>
#include <m_string.h>
#ifdef HAVE_DLOPEN
#ifdef __cplusplus
extern "C" {
#endif
my_bool testudf_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
void testudf_deinit(UDF_INIT *initid);
char *testudf(UDF_INIT *initid, UDF_ARGS *args, char *result,
unsigned long *length, char *is_null, char *error);
#ifdef __cplusplus
}
#endif
#define DOLOG
my_bool testudf_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
{
#ifdef DOLOG
FILE *fp;
fp=fopen("/tmp/testudf.log","a");
fprintf(fp,"testudf_init %d\n",getpid());
fclose(fp);
#endif
if (args->arg_count != 1 || args->arg_type[0] != STRING_RESULT)
{
strcpy(message,"Wrong arguments to testudf");
return 1;
}
return 0;
}
void testudf_deinit(UDF_INIT *initid)
{
#ifdef DOLOG
FILE *fp;
fp=fopen("/tmp/testudf.log","a");
fprintf(fp,"testudf_deinit %d\n",getpid());
fclose(fp);
#endif
}
char *testudf(UDF_INIT *initid, UDF_ARGS *args, char *result,
unsigned long *length, char *is_null, char *error)
{
int l=args->lengths[0];
int n=0;
int len;
char *s=args->args[0];
char *d=result;
unsigned int v;
#ifdef DOLOG
FILE *fp;
fp=fopen("/tmp/testudf.log","a");
fprintf(fp,"testudf %d\n",getpid());
fclose(fp);
#endif
memcpy(result,args->args[0],args->lengths[0]);
*length=args->lengths[0];
return result;
}
#endif /* HAVE_DLOPEN */
Suggested fix:
Assert that udf_init is called only once when using prepared statements.
Description: UDF's are called in this sequence according to the docs: xxx_init() xxx() xxx_deinit() When an UDF is used in a prepared statement the calling sequence is: PREPARE... xxx_init() EXECUTE... xxx_init() xxx() ... DEALLOCATE... xxx_deinit() As you can see the execution of the prepared statement erroneously calls xxx_init() which leads to severe memory leaks. This is also valid if the udf doesn't allocate memory itself. How to repeat: The below sequence uses the udf source code appended below. The udf function appends to /tmp/testudf.log which should be viewed: 1. after select... testudf_init 15205 testudf 15205 testudf 15205 testudf_deinit 15205 2. after prepare... testudf_init 15205 testudf 15205 testudf 15205 testudf_deinit 15205 testudf_init 15205 3. after every execute... testudf_init 15205 testudf 15205 testudf 15205 testudf_deinit 15205 testudf_init 15205 testudf_init 15205 <====== testudf 15205 testudf 15205 testudf_init 15205 testudf 15205 testudf 15205 testudf_deinit 15205 testudf_init 15205 testudf_init 15205 <======= testudf 15205 testudf 15205 testudf_init 15205 <======= testudf 15205 testudf 15205 4. after deallocate... testudf_init 15205 testudf 15205 testudf 15205 testudf_deinit 15205 testudf_init 15205 testudf_init 15205 <======= testudf 15205 testudf 15205 testudf_init 15205 <======= testudf 15205 testudf 15205 testudf_deinit 15205 ============================================= create function testudf returns string soname 'testudf.so'; create database udftest; use udftest; create table udftest(v1 integer,v2 text); insert into udftest values(1,'b'),(2,'a'); select v1,v2 from udftest order by testudf(v2); prepare tst from 'select v1,v2 from udftest order by testudf(v2)'; execute tst; execute tst; deallocate prepare tst; ============================================= #ifdef STANDARD #include <stdio.h> #include <string.h> #ifdef __WIN__ typedef unsigned __int64 ulonglong; /* Microsofts 64 bit types */ typedef __int64 longlong; #else typedef unsigned long long ulonglong; typedef long long longlong; #endif /*__WIN__*/ #else #include <my_global.h> #include <my_sys.h> #endif #include <mysql.h> #include <m_string.h> #ifdef HAVE_DLOPEN #ifdef __cplusplus extern "C" { #endif my_bool testudf_init(UDF_INIT *initid, UDF_ARGS *args, char *message); void testudf_deinit(UDF_INIT *initid); char *testudf(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char *error); #ifdef __cplusplus } #endif #define DOLOG my_bool testudf_init(UDF_INIT *initid, UDF_ARGS *args, char *message) { #ifdef DOLOG FILE *fp; fp=fopen("/tmp/testudf.log","a"); fprintf(fp,"testudf_init %d\n",getpid()); fclose(fp); #endif if (args->arg_count != 1 || args->arg_type[0] != STRING_RESULT) { strcpy(message,"Wrong arguments to testudf"); return 1; } return 0; } void testudf_deinit(UDF_INIT *initid) { #ifdef DOLOG FILE *fp; fp=fopen("/tmp/testudf.log","a"); fprintf(fp,"testudf_deinit %d\n",getpid()); fclose(fp); #endif } char *testudf(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char *error) { int l=args->lengths[0]; int n=0; int len; char *s=args->args[0]; char *d=result; unsigned int v; #ifdef DOLOG FILE *fp; fp=fopen("/tmp/testudf.log","a"); fprintf(fp,"testudf %d\n",getpid()); fclose(fp); #endif memcpy(result,args->args[0],args->lengths[0]); *length=args->lengths[0]; return result; } #endif /* HAVE_DLOPEN */ Suggested fix: Assert that udf_init is called only once when using prepared statements.