Skip to content

Commit 44c3558

Browse files
committed
Fix GH-9090: Support assigning function pointers in FFI
1 parent eff9aed commit 44c3558

File tree

3 files changed

+145
-0
lines changed

3 files changed

+145
-0
lines changed

ext/ffi/ffi.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ static ZEND_FUNCTION(ffi_trampoline);
214214
static ZEND_COLD void zend_ffi_return_unsupported(zend_ffi_type *type);
215215
static ZEND_COLD void zend_ffi_pass_unsupported(zend_ffi_type *type);
216216
static ZEND_COLD void zend_ffi_assign_incompatible(zval *arg, zend_ffi_type *type);
217+
static bool zend_ffi_is_compatible_type(zend_ffi_type *dst_type, zend_ffi_type *src_type);
217218

218219
#if FFI_CLOSURES
219220
static void *zend_ffi_create_callback(zend_ffi_type *type, zval *value);
@@ -255,6 +256,39 @@ static zend_object *zend_ffi_cdata_new(zend_class_entry *class_type) /* {{{ */
255256
}
256257
/* }}} */
257258

259+
static bool zend_ffi_func_ptr_are_compatible(zend_ffi_type *dst_type, zend_ffi_type *src_type) /* {{{ */
260+
{
261+
uint32_t dst_argc, src_argc, i;
262+
zend_ffi_type *dst_arg, *src_arg;
263+
264+
ZEND_ASSERT(dst_type->kind == ZEND_FFI_TYPE_FUNC);
265+
ZEND_ASSERT(src_type->kind == ZEND_FFI_TYPE_FUNC);
266+
267+
/* Ensure compatible ret_type */
268+
if (!zend_ffi_is_compatible_type(dst_type->func.ret_type, src_type->func.ret_type)) {
269+
return 0;
270+
}
271+
272+
/* Ensure same arg count */
273+
dst_argc = dst_type->func.args ? zend_hash_num_elements(dst_type->func.args) : 0;
274+
src_argc = src_type->func.args ? zend_hash_num_elements(src_type->func.args) : 0;
275+
if (dst_argc != src_argc) {
276+
return 0;
277+
}
278+
279+
/* Ensure compatible args */
280+
for (i = 0; i < dst_argc; i++) {
281+
dst_arg = zend_hash_index_find_ptr(dst_type->func.args, i);
282+
src_arg = zend_hash_index_find_ptr(src_type->func.args, i);
283+
if (!zend_ffi_is_compatible_type(ZEND_FFI_TYPE(dst_arg), ZEND_FFI_TYPE(src_arg))) {
284+
return 0;
285+
}
286+
}
287+
288+
return 1;
289+
}
290+
/* }}} */
291+
258292
static bool zend_ffi_is_compatible_type(zend_ffi_type *dst_type, zend_ffi_type *src_type) /* {{{ */
259293
{
260294
while (1) {
@@ -269,6 +303,9 @@ static bool zend_ffi_is_compatible_type(zend_ffi_type *dst_type, zend_ffi_type *
269303
if (dst_type->kind == ZEND_FFI_TYPE_VOID ||
270304
src_type->kind == ZEND_FFI_TYPE_VOID) {
271305
return 1;
306+
} else if (dst_type->kind == ZEND_FFI_TYPE_FUNC &&
307+
src_type->kind == ZEND_FFI_TYPE_FUNC) {
308+
return zend_ffi_func_ptr_are_compatible(dst_type, src_type);
272309
}
273310
} else if (dst_type->kind == ZEND_FFI_TYPE_ARRAY &&
274311
(dst_type->array.length == src_type->array.length ||

ext/ffi/tests/bug_gh9090.phpt

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
--TEST--
2+
GH-9090 (Support assigning function pointers via FFI)
3+
--EXTENSIONS--
4+
ffi
5+
zend_test
6+
--FILE--
7+
<?php
8+
9+
require_once 'utils.inc';
10+
$h = <<<'EOD'
11+
void (*bug_gh9090_void_none_ptr)();
12+
void (*bug_gh9090_void_int_char_ptr)(int, char *);
13+
void (*bug_gh9090_void_int_char_var_ptr)(int, char *, ...);
14+
void (*bug_gh9090_void_char_int_ptr)(char *, int);
15+
int (*bug_gh9090_int_int_char_ptr)(int, char *);
16+
17+
void bug_gh9090_void_none();
18+
void bug_gh9090_void_int_char(int i, char *s);
19+
void bug_gh9090_void_int_char_var(int i, char *fmt, ...);
20+
EOD;
21+
22+
if (PHP_OS_FAMILY !== 'Windows') {
23+
$ffi = FFI::cdef($h);
24+
} else {
25+
try {
26+
$ffi = FFI::cdef($h, 'php_zend_test.dll');
27+
} catch (FFI\Exception $ex) {
28+
$ffi = FFI::cdef($h, ffi_get_php_dll_name());
29+
}
30+
}
31+
32+
$func_ptrs = [
33+
'bug_gh9090_void_none_ptr',
34+
'bug_gh9090_void_int_char_ptr',
35+
'bug_gh9090_void_int_char_var_ptr',
36+
'bug_gh9090_void_char_int_ptr',
37+
'bug_gh9090_int_int_char_ptr',
38+
];
39+
40+
$func_argvs = [
41+
[ 'bug_gh9090_void_none', [ ] ],
42+
[ 'bug_gh9090_void_int_char', [ 42, "hello" ] ],
43+
[ 'bug_gh9090_void_int_char_var', [ 42, "d=%d s=%s", -1, "ok" ] ],
44+
];
45+
46+
foreach ($func_ptrs as $func_ptr) {
47+
foreach ($func_argvs as $func_argv) {
48+
[ $func, $argv ] = $func_argv;
49+
50+
$ok = true;
51+
try {
52+
$ffi->$func_ptr = $ffi->$func;
53+
call_user_func_array($ffi->$func_ptr, $argv);
54+
} catch (FFI\Exception $e) {
55+
$ok = false;
56+
}
57+
58+
printf("%-36s = %-36s ? %s\n", $func_ptr, $func, $ok ? 'yes' : 'no');
59+
}
60+
}
61+
?>
62+
--EXPECT--
63+
bug_gh9090_none
64+
bug_gh9090_void_none_ptr = bug_gh9090_void_none ? yes
65+
bug_gh9090_void_none_ptr = bug_gh9090_void_int_char ? no
66+
bug_gh9090_void_none_ptr = bug_gh9090_void_int_char_var ? no
67+
bug_gh9090_void_int_char_ptr = bug_gh9090_void_none ? no
68+
bug_gh9090_int_char 42 hello
69+
bug_gh9090_void_int_char_ptr = bug_gh9090_void_int_char ? yes
70+
bug_gh9090_void_int_char_ptr = bug_gh9090_void_int_char_var ? no
71+
bug_gh9090_void_int_char_var_ptr = bug_gh9090_void_none ? no
72+
bug_gh9090_int_char 42 hello
73+
bug_gh9090_void_int_char_var_ptr = bug_gh9090_void_int_char ? yes
74+
bug_gh9090_void_int_char_var d=-1 s=ok
75+
bug_gh9090_void_int_char_var_ptr = bug_gh9090_void_int_char_var ? yes
76+
bug_gh9090_void_char_int_ptr = bug_gh9090_void_none ? no
77+
bug_gh9090_void_char_int_ptr = bug_gh9090_void_int_char ? no
78+
bug_gh9090_void_char_int_ptr = bug_gh9090_void_int_char_var ? no
79+
bug_gh9090_int_int_char_ptr = bug_gh9090_void_none ? no
80+
bug_gh9090_int_int_char_ptr = bug_gh9090_void_int_char ? no
81+
bug_gh9090_int_int_char_ptr = bug_gh9090_void_int_char_var ? no

ext/zend_test/test.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,3 +847,30 @@ PHP_ZEND_TEST_API bug80847_02 ffi_bug80847(bug80847_02 s) {
847847
s.a.c -= 10.0;
848848
return s;
849849
}
850+
851+
PHP_ZEND_TEST_API void (*bug_gh9090_void_none_ptr)(void) = NULL;
852+
PHP_ZEND_TEST_API void (*bug_gh9090_void_int_char_ptr)(int, char *) = NULL;
853+
PHP_ZEND_TEST_API void (*bug_gh9090_void_int_char_var_ptr)(int, char *, ...) = NULL;
854+
PHP_ZEND_TEST_API void (*bug_gh9090_void_char_int_ptr)(char *, int) = NULL;
855+
PHP_ZEND_TEST_API int (*bug_gh9090_int_int_char_ptr)(int, char *) = NULL;
856+
857+
PHP_ZEND_TEST_API void bug_gh9090_void_none(void) {
858+
php_printf("bug_gh9090_none\n");
859+
}
860+
861+
PHP_ZEND_TEST_API void bug_gh9090_void_int_char(int i, char *s) {
862+
php_printf("bug_gh9090_int_char %d %s\n", i, s);
863+
}
864+
865+
PHP_ZEND_TEST_API void bug_gh9090_void_int_char_var(int i, char *fmt, ...) {
866+
va_list args;
867+
char *buffer;
868+
869+
va_start(args, fmt);
870+
871+
zend_vspprintf(&buffer, 0, fmt, args);
872+
php_printf("bug_gh9090_void_int_char_var %s\n", buffer);
873+
efree(buffer);
874+
875+
va_end(args);
876+
}

0 commit comments

Comments
 (0)