Skip to content

Commit 105ce4b

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

File tree

3 files changed

+117
-0
lines changed

3 files changed

+117
-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: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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_char_int_ptr)(char *, int);
14+
int (*bug_gh9090_int_int_char_ptr)(int, char *);
15+
16+
void bug_gh9090_void_none();
17+
void bug_gh9090_void_int_char(int i, char *s);
18+
EOD;
19+
20+
if (PHP_OS_FAMILY !== 'Windows') {
21+
$ffi = FFI::cdef($h);
22+
} else {
23+
try {
24+
$ffi = FFI::cdef($h, 'php_zend_test.dll');
25+
} catch (FFI\Exception $ex) {
26+
$ffi = FFI::cdef($h, ffi_get_php_dll_name());
27+
}
28+
}
29+
30+
foreach ([
31+
'void_none_ptr' => 'bug_gh9090_void_none_ptr',
32+
'void_int_char_ptr' => 'bug_gh9090_void_int_char_ptr',
33+
'void_char_int_ptr' => 'bug_gh9090_void_char_int_ptr',
34+
'int_int_char_ptr' => 'bug_gh9090_int_int_char_ptr',
35+
] as $fp => $ffi_func_ptr_name) {
36+
37+
foreach ([
38+
'void_none' => [ $ffi->bug_gh9090_void_none, [] ],
39+
'void_int_char' => [ $ffi->bug_gh9090_void_int_char, [ 42, "hello" ] ],
40+
] as $f => $ffi_func_args) {
41+
42+
[ $ffi_func, $args ] = $ffi_func_args;
43+
44+
$ok = true;
45+
try {
46+
$ffi->$ffi_func_ptr_name = $ffi_func;
47+
call_user_func_array($ffi->$ffi_func_ptr_name, $args);
48+
} catch (FFI\Exception $e) {
49+
$ok = false;
50+
}
51+
52+
printf("%-20s = %-20s ? %s\n", $fp, $f, $ok ? 'yes' : 'no');
53+
}
54+
55+
}
56+
?>
57+
--EXPECT--
58+
bug_gh9090_none
59+
void_none_ptr = void_none ? yes
60+
void_none_ptr = void_int_char ? no
61+
void_int_char_ptr = void_none ? no
62+
bug_gh9090_int_char 42 hello
63+
void_int_char_ptr = void_int_char ? yes
64+
void_char_int_ptr = void_none ? no
65+
void_char_int_ptr = void_int_char ? no
66+
int_int_char_ptr = void_none ? no
67+
int_int_char_ptr = void_int_char ? no

ext/zend_test/test.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,3 +847,16 @@ 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_char_int_ptr)(char *, int) = NULL;
854+
PHP_ZEND_TEST_API int (*bug_gh9090_int_int_char_ptr)(int, char *) = NULL;
855+
856+
PHP_ZEND_TEST_API void bug_gh9090_void_none(void) {
857+
php_printf("bug_gh9090_none\n");
858+
}
859+
860+
PHP_ZEND_TEST_API void bug_gh9090_void_int_char(int i, char *s) {
861+
php_printf("bug_gh9090_int_char %d %s\n", i, s);
862+
}

0 commit comments

Comments
 (0)