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.