Skip to content

Fix GH-9090: Support assigning function pointers in FFI #9107

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions ext/ffi/ffi.c
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ static ZEND_FUNCTION(ffi_trampoline);
static ZEND_COLD void zend_ffi_return_unsupported(zend_ffi_type *type);
static ZEND_COLD void zend_ffi_pass_unsupported(zend_ffi_type *type);
static ZEND_COLD void zend_ffi_assign_incompatible(zval *arg, zend_ffi_type *type);
static bool zend_ffi_is_compatible_type(zend_ffi_type *dst_type, zend_ffi_type *src_type);

#if FFI_CLOSURES
static void *zend_ffi_create_callback(zend_ffi_type *type, zval *value);
Expand Down Expand Up @@ -255,6 +256,49 @@ static zend_object *zend_ffi_cdata_new(zend_class_entry *class_type) /* {{{ */
}
/* }}} */

static bool zend_ffi_func_ptr_are_compatible(zend_ffi_type *dst_type, zend_ffi_type *src_type) /* {{{ */
{
uint32_t dst_argc, src_argc, i;
zend_ffi_type *dst_arg, *src_arg;

ZEND_ASSERT(dst_type->kind == ZEND_FFI_TYPE_FUNC);
ZEND_ASSERT(src_type->kind == ZEND_FFI_TYPE_FUNC);

/* Ensure calling convention matches */
if (dst_type->func.abi != src_type->func.abi) {
return 0;
}

/* Ensure variadic attr matches */
if ((dst_type->attr & ZEND_FFI_ATTR_VARIADIC) != (src_type->attr & ZEND_FFI_ATTR_VARIADIC)) {
return 0;
}

/* Ensure same arg count */
dst_argc = dst_type->func.args ? zend_hash_num_elements(dst_type->func.args) : 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need a check for C varags? Either way, a test for that would be nice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, added a variadic to the test.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should check

(src_type->attr & ZEND_FFI_ATTR_VARIADIC) == (dst_type->attr & ZEND_FFI_ATTR_VARIADIC)

Also it's make sense to add check for calling convention

src_type->func.abi == dst_type->func.abi

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added those checks. Thank you.

src_argc = src_type->func.args ? zend_hash_num_elements(src_type->func.args) : 0;
if (dst_argc != src_argc) {
return 0;
}

/* Ensure compatible ret_type */
if (!zend_ffi_is_compatible_type(dst_type->func.ret_type, src_type->func.ret_type)) {
return 0;
}

/* Ensure compatible args */
for (i = 0; i < dst_argc; i++) {
dst_arg = zend_hash_index_find_ptr(dst_type->func.args, i);
src_arg = zend_hash_index_find_ptr(src_type->func.args, i);
if (!zend_ffi_is_compatible_type(ZEND_FFI_TYPE(dst_arg), ZEND_FFI_TYPE(src_arg))) {
return 0;
}
}

return 1;
}
/* }}} */

static bool zend_ffi_is_compatible_type(zend_ffi_type *dst_type, zend_ffi_type *src_type) /* {{{ */
{
while (1) {
Expand All @@ -269,6 +313,9 @@ static bool zend_ffi_is_compatible_type(zend_ffi_type *dst_type, zend_ffi_type *
if (dst_type->kind == ZEND_FFI_TYPE_VOID ||
src_type->kind == ZEND_FFI_TYPE_VOID) {
return 1;
} else if (dst_type->kind == ZEND_FFI_TYPE_FUNC &&
src_type->kind == ZEND_FFI_TYPE_FUNC) {
return zend_ffi_func_ptr_are_compatible(dst_type, src_type);
}
} else if (dst_type->kind == ZEND_FFI_TYPE_ARRAY &&
(dst_type->array.length == src_type->array.length ||
Expand Down
80 changes: 80 additions & 0 deletions ext/ffi/tests/bug_gh9090.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
--TEST--
GH-9090 (Support assigning function pointers via FFI)
--EXTENSIONS--
ffi
zend_test
--FILE--
<?php

require_once 'utils.inc';
$h = <<<'EOD'
void (*bug_gh9090_void_none_ptr)();
void (*bug_gh9090_void_int_char_ptr)(int, char *);
void (*bug_gh9090_void_int_char_var_ptr)(int, char *, ...);
void (*bug_gh9090_void_char_int_ptr)(char *, int);
int (*bug_gh9090_int_int_char_ptr)(int, char *);

void bug_gh9090_void_none();
void bug_gh9090_void_int_char(int i, char *s);
void bug_gh9090_void_int_char_var(int i, char *fmt, ...);
EOD;

if (PHP_OS_FAMILY !== 'Windows') {
$ffi = FFI::cdef($h);
} else {
try {
$ffi = FFI::cdef($h, 'php_zend_test.dll');
} catch (FFI\Exception $ex) {
$ffi = FFI::cdef($h, ffi_get_php_dll_name());
}
}

$func_ptrs = [
'bug_gh9090_void_none_ptr',
'bug_gh9090_void_int_char_ptr',
'bug_gh9090_void_int_char_var_ptr',
'bug_gh9090_void_char_int_ptr',
'bug_gh9090_int_int_char_ptr',
];

$func_argvs = [
[ 'bug_gh9090_void_none', [ ] ],
[ 'bug_gh9090_void_int_char', [ 42, "hello" ] ],
[ 'bug_gh9090_void_int_char_var', [ 42, "d=%d s=%s", -1, "ok" ] ],
];

foreach ($func_ptrs as $func_ptr) {
foreach ($func_argvs as $func_argv) {
[ $func, $argv ] = $func_argv;

$ok = true;
try {
$ffi->$func_ptr = $ffi->$func;
call_user_func_array($ffi->$func_ptr, $argv);
} catch (FFI\Exception $e) {
$ok = false;
}

printf("%-36s = %-36s ? %s\n", $func_ptr, $func, $ok ? 'yes' : 'no');
}
}
?>
--EXPECT--
bug_gh9090_none
bug_gh9090_void_none_ptr = bug_gh9090_void_none ? yes
bug_gh9090_void_none_ptr = bug_gh9090_void_int_char ? no
bug_gh9090_void_none_ptr = bug_gh9090_void_int_char_var ? no
bug_gh9090_void_int_char_ptr = bug_gh9090_void_none ? no
bug_gh9090_int_char 42 hello
bug_gh9090_void_int_char_ptr = bug_gh9090_void_int_char ? yes
bug_gh9090_void_int_char_ptr = bug_gh9090_void_int_char_var ? no
bug_gh9090_void_int_char_var_ptr = bug_gh9090_void_none ? no
bug_gh9090_void_int_char_var_ptr = bug_gh9090_void_int_char ? no
bug_gh9090_void_int_char_var d=-1 s=ok
bug_gh9090_void_int_char_var_ptr = bug_gh9090_void_int_char_var ? yes
bug_gh9090_void_char_int_ptr = bug_gh9090_void_none ? no
bug_gh9090_void_char_int_ptr = bug_gh9090_void_int_char ? no
bug_gh9090_void_char_int_ptr = bug_gh9090_void_int_char_var ? no
bug_gh9090_int_int_char_ptr = bug_gh9090_void_none ? no
bug_gh9090_int_int_char_ptr = bug_gh9090_void_int_char ? no
bug_gh9090_int_int_char_ptr = bug_gh9090_void_int_char_var ? no
27 changes: 27 additions & 0 deletions ext/zend_test/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -847,3 +847,30 @@ PHP_ZEND_TEST_API bug80847_02 ffi_bug80847(bug80847_02 s) {
s.a.c -= 10.0;
return s;
}

PHP_ZEND_TEST_API void (*bug_gh9090_void_none_ptr)(void) = NULL;
PHP_ZEND_TEST_API void (*bug_gh9090_void_int_char_ptr)(int, char *) = NULL;
PHP_ZEND_TEST_API void (*bug_gh9090_void_int_char_var_ptr)(int, char *, ...) = NULL;
PHP_ZEND_TEST_API void (*bug_gh9090_void_char_int_ptr)(char *, int) = NULL;
PHP_ZEND_TEST_API int (*bug_gh9090_int_int_char_ptr)(int, char *) = NULL;

PHP_ZEND_TEST_API void bug_gh9090_void_none(void) {
php_printf("bug_gh9090_none\n");
}

PHP_ZEND_TEST_API void bug_gh9090_void_int_char(int i, char *s) {
php_printf("bug_gh9090_int_char %d %s\n", i, s);
}

PHP_ZEND_TEST_API void bug_gh9090_void_int_char_var(int i, char *fmt, ...) {
va_list args;
char *buffer;

va_start(args, fmt);

zend_vspprintf(&buffer, 0, fmt, args);
php_printf("bug_gh9090_void_int_char_var %s\n", buffer);
efree(buffer);

va_end(args);
}