Skip to content

Commit 8cf9c2f

Browse files
adsrcmb69
authored andcommitted
Fix phpGH-9090: Support assigning function pointers in FFI
Closes phpGH-9107.
1 parent 099b168 commit 8cf9c2f

File tree

4 files changed

+158
-0
lines changed

4 files changed

+158
-0
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ PHP NEWS
1717
. Fixed LMDB driver memory leak on DB creation failure (Girgias)
1818
. Fixed GH-8856 (dba: lmdb: allow to override the MDB_NOSUBDIR flag). (Girgias)
1919

20+
- FFI:
21+
. Fixed bug GH-9090 (Support assigning function pointers in FFI). (Adam
22+
Saponara)
23+
2024
- Random:
2125
. Fixed bug GH-9067 (random extension is not thread safe). (cmb)
2226
. Fixed bug GH-9055 (segmentation fault if user engine throws). (timwolla)

ext/ffi/ffi.c

Lines changed: 47 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,49 @@ 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 calling convention matches */
268+
if (dst_type->func.abi != src_type->func.abi) {
269+
return 0;
270+
}
271+
272+
/* Ensure variadic attr matches */
273+
if ((dst_type->attr & ZEND_FFI_ATTR_VARIADIC) != (src_type->attr & ZEND_FFI_ATTR_VARIADIC)) {
274+
return 0;
275+
}
276+
277+
/* Ensure same arg count */
278+
dst_argc = dst_type->func.args ? zend_hash_num_elements(dst_type->func.args) : 0;
279+
src_argc = src_type->func.args ? zend_hash_num_elements(src_type->func.args) : 0;
280+
if (dst_argc != src_argc) {
281+
return 0;
282+
}
283+
284+
/* Ensure compatible ret_type */
285+
if (!zend_ffi_is_compatible_type(dst_type->func.ret_type, src_type->func.ret_type)) {
286+
return 0;
287+
}
288+
289+
/* Ensure compatible args */
290+
for (i = 0; i < dst_argc; i++) {
291+
dst_arg = zend_hash_index_find_ptr(dst_type->func.args, i);
292+
src_arg = zend_hash_index_find_ptr(src_type->func.args, i);
293+
if (!zend_ffi_is_compatible_type(ZEND_FFI_TYPE(dst_arg), ZEND_FFI_TYPE(src_arg))) {
294+
return 0;
295+
}
296+
}
297+
298+
return 1;
299+
}
300+
/* }}} */
301+
258302
static bool zend_ffi_is_compatible_type(zend_ffi_type *dst_type, zend_ffi_type *src_type) /* {{{ */
259303
{
260304
while (1) {
@@ -269,6 +313,9 @@ static bool zend_ffi_is_compatible_type(zend_ffi_type *dst_type, zend_ffi_type *
269313
if (dst_type->kind == ZEND_FFI_TYPE_VOID ||
270314
src_type->kind == ZEND_FFI_TYPE_VOID) {
271315
return 1;
316+
} else if (dst_type->kind == ZEND_FFI_TYPE_FUNC &&
317+
src_type->kind == ZEND_FFI_TYPE_FUNC) {
318+
return zend_ffi_func_ptr_are_compatible(dst_type, src_type);
272319
}
273320
} else if (dst_type->kind == ZEND_FFI_TYPE_ARRAY &&
274321
(dst_type->array.length == src_type->array.length ||

ext/ffi/tests/bug_gh9090.phpt

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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_void_int_char_var_ptr = bug_gh9090_void_int_char ? no
73+
bug_gh9090_void_int_char_var d=-1 s=ok
74+
bug_gh9090_void_int_char_var_ptr = bug_gh9090_void_int_char_var ? yes
75+
bug_gh9090_void_char_int_ptr = bug_gh9090_void_none ? no
76+
bug_gh9090_void_char_int_ptr = bug_gh9090_void_int_char ? no
77+
bug_gh9090_void_char_int_ptr = bug_gh9090_void_int_char_var ? no
78+
bug_gh9090_int_int_char_ptr = bug_gh9090_void_none ? no
79+
bug_gh9090_int_int_char_ptr = bug_gh9090_void_int_char ? no
80+
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)