Well, it's not.
I compiled the code with a debug enabled php version today and got dozens of memory leaks...
Therefore I took the call_user_function_ex function and removed all not needed for calling an
internal function.
int call_internal_function(zend_function *function, zval **retval_ptr_ptr, int param_count, zval **params[] TSRMLS_DC)
{
zval **original_return_value;
zend_function_state *original_function_state_ptr;
zend_execute_data execute_data;
zend_op temp_op;
int i;
if (! function || function->type != ZEND_INTERNAL_FUNCTION) {
return FAILURE;
}
/* Initialize execute_data */
EX(fbc) = NULL;
EX(object).ptr = NULL;
EX(ce) = NULL;
EX(Ts) = NULL;
EX(op_array) = NULL;
EX(opline) = NULL;
EX(function_state).function = function;
*retval_ptr_ptr = NULL;
original_function_state_ptr = EG(function_state_ptr);
for (i=0; i < param_count; i++)
{
zval *param;
if (EX(function_state).function->internal_function.arg_types
&& i < EX(function_state).function->internal_function.arg_types[0]
&& EX(function_state).function->internal_function.arg_types[i+1]==BYREF_FORCE
&& !PZVAL_IS_REF(*params[i])) {
if ((*params[i])->refcount>1) {
zval *new_zval;
ALLOC_ZVAL(new_zval);
*new_zval = **params[i];
zval_copy_ctor(new_zval);
new_zval->refcount = 1;
(*params[i])->refcount--;
*params[i] = new_zval;
}
(*params[i])->refcount++;
(*params[i])->is_ref = 1;
param = *params[i];
} else if (*params[i] != &EG(uninitialized_zval)) {
(*params[i])->refcount++;
param = *params[i];
} else {
ALLOC_ZVAL(param);
*param = **(params[i]);
INIT_PZVAL(param);
}
zend_ptr_stack_push(&EG(argument_stack), param);
}
zend_ptr_stack_n_push(&EG(argument_stack), 2, (void *) (long) param_count, NULL);
EG(function_state_ptr) = &EX(function_state);
EX(prev_execute_data) = EG(current_execute_data);
EG(current_execute_data) = &execute_data;
ALLOC_INIT_ZVAL(*retval_ptr_ptr);
temp_op.extended_value = param_count;
temp_op.result.u.var = 0;
temp_op.lineno = 0;
EX(opline) = &temp_op;
EX(Ts) = (temp_variable *) do_alloca(sizeof(temp_variable)*1);
EX(Ts)[EX(opline)->result.u.var].var.ptr = *retval_ptr_ptr;
if (!zend_execute_internal) {
((zend_internal_function *) EX(function_state).function)->handler(EX(opline)->extended_value, EX(Ts)[EX(opline)->result.u.var].var.ptr, EX(object).ptr, 1 TSRMLS_CC);
} else {
zend_execute_internal(&execute_data, 1 TSRMLS_CC);
}
INIT_PZVAL(*retval_ptr_ptr);
free_alloca(EX(Ts));
zend_ptr_stack_clear_multiple(TSRMLS_C);
EG(function_state_ptr) = original_function_state_ptr;
EG(current_execute_data) = EX(prev_execute_data);
return SUCCESS;
}
The example with mysql_escape_string now looks as follows:
ZEND_API zval *ext_mysql_escape_string(zval *str TSRMLS_DC)
{
zval *retval, **args[1];
zend_function function;
/* initialize internal function */
function.internal_function.type = ZEND_INTERNAL_FUNCTION;
function.internal_function.arg_types = NULL;
function.internal_function.function_name = "mysql_escape_string";
function.internal_function.handler = zif_mysql_escape_string;
args[0] =& str;
call_internal_function(&function, &retval, 1, args TSRMLS_CC);
return retval;
}
No memory leaks are reported this way, so I hope it's ok now.
Btw, take into account that if the internal function requires a parameter passed by reference,
you need to adapt function.internal_function.arg_types so that call_internal_function is able to
separate the zval.
Example with a function that takes one argument :
unsigned char args[] = {1, BYREF_FORCE};
/* initialize internal function */
function.internal_function.type = ZEND_INTERNAL_FUNCTION;
function.internal_function.arg_types = args;
function.internal_function.function_name = "some_function_name";
function.internal_function.handler = zif_some_function;
Please correct me if I'm wrong !
micha