Skip to content

Commit 0b9702c

Browse files
committed
Implement GH-11934: Allow to pass CData into struct and/or union fields
Co-authored-by: KapitanOczywisty <[email protected]> Closes GH-11935.
1 parent 223fb08 commit 0b9702c

File tree

6 files changed

+223
-8
lines changed

6 files changed

+223
-8
lines changed

NEWS

+4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ PHP NEWS
1515
. Fixed GH-11952 (Confusing warning when blocking entity loading via
1616
libxml_set_external_entity_loader). (nielsdos)
1717

18+
- FFI:
19+
. Implement GH-11934 (Allow to pass CData into struct and/or union fields).
20+
(nielsdos, KapitanOczywisty)
21+
1822
- FPM:
1923
. Fixed bug #76067 (system() function call leaks php-fpm listening sockets).
2024
(Mikhail Galanin, Jakub Zelenka)

UPGRADING

+4
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ PHP 8.3 UPGRADE NOTES
126126
- CLI
127127
. It is now possible to lint multiple files.
128128

129+
- FFI
130+
. It is now possible to assign CData to other CData. This means you can
131+
now assign CData to structs and fields.
132+
129133
- Opcache
130134
. opcache_get_status()['scripts'][n]['revalidate'] now contains a Unix
131135
timestamp of when the next revalidation of the scripts timestamp is due,

ext/ffi/ffi.c

+11-8
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,16 @@ static zend_always_inline zend_result zend_ffi_zval_to_cdata(void *ptr, zend_ffi
739739
zend_string *str;
740740
zend_ffi_type_kind kind = type->kind;
741741

742+
/* Pointer type has special handling of CData */
743+
if (kind != ZEND_FFI_TYPE_POINTER && Z_TYPE_P(value) == IS_OBJECT && Z_OBJCE_P(value) == zend_ffi_cdata_ce) {
744+
zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(value);
745+
if (zend_ffi_is_compatible_type(type, ZEND_FFI_TYPE(cdata->type)) &&
746+
type->size == ZEND_FFI_TYPE(cdata->type)->size) {
747+
memcpy(ptr, cdata->ptr, type->size);
748+
return SUCCESS;
749+
}
750+
}
751+
742752
again:
743753
switch (kind) {
744754
case ZEND_FFI_TYPE_FLOAT:
@@ -848,14 +858,7 @@ static zend_always_inline zend_result zend_ffi_zval_to_cdata(void *ptr, zend_ffi
848858
case ZEND_FFI_TYPE_STRUCT:
849859
case ZEND_FFI_TYPE_ARRAY:
850860
default:
851-
if (Z_TYPE_P(value) == IS_OBJECT && Z_OBJCE_P(value) == zend_ffi_cdata_ce) {
852-
zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(value);
853-
if (zend_ffi_is_compatible_type(type, ZEND_FFI_TYPE(cdata->type)) &&
854-
type->size == ZEND_FFI_TYPE(cdata->type)->size) {
855-
memcpy(ptr, cdata->ptr, type->size);
856-
return SUCCESS;
857-
}
858-
}
861+
/* Incompatible types, because otherwise the CData check at the entry point would've succeeded. */
859862
zend_ffi_assign_incompatible(value, type);
860863
return FAILURE;
861864
}

ext/ffi/tests/gh11934.phpt

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
--TEST--
2+
Feature GH-11934 (Allow to pass CData into struct and/or union fields)
3+
--EXTENSIONS--
4+
ffi
5+
--INI--
6+
ffi.enable=1
7+
--FILE--
8+
<?php
9+
10+
$cdef = FFI::cdef();
11+
12+
echo "--- Primitive types ---\n";
13+
14+
// Choose integer numbers off the maximum to test copy
15+
$types = [
16+
'uint8_t' => 200,
17+
'uint16_t' => 16000,
18+
'uint32_t' => 1000_000,
19+
'uint64_t' => PHP_INT_MAX - 100,
20+
'int8_t' => -100,
21+
'int16_t' => -16000,
22+
'int32_t' => -1000_000,
23+
'int64_t' => PHP_INT_MIN + 100,
24+
'char' => 'F',
25+
'bool' => false,
26+
'float' => 1.00,
27+
'double' => -1.00,
28+
];
29+
30+
// Positive test
31+
foreach ($types as $type => $value) {
32+
$source = $cdef->new($type);
33+
$source->cdata = $value;
34+
$struct = $cdef->new("struct { $type field; }");
35+
$struct->field = $source;
36+
echo "Positive test $type: ";
37+
var_dump($struct->field === $value);
38+
}
39+
40+
// Negative test
41+
$dummy = $cdef->new("int[2]");
42+
foreach ($types as $type => $value) {
43+
$struct = $cdef->new("struct { int field; }");
44+
$struct->field = $dummy;
45+
}
46+
47+
// Enum test
48+
$enum_definition = "enum { A, B, C, D }";
49+
$source = $cdef->new($enum_definition);
50+
$source->cdata = 2;
51+
$struct = $cdef->new("struct { $enum_definition field; }");
52+
$struct->field = $source;
53+
echo "Positive test enum: ";
54+
var_dump($struct->field === $source->cdata);
55+
$struct->field = $dummy;
56+
57+
echo "--- Struct ---\n";
58+
59+
// Nested structs
60+
$cdef = FFI::cdef("
61+
struct inner_struct {
62+
int data[1];
63+
};
64+
struct my_struct {
65+
int foo;
66+
int bar;
67+
struct inner_struct inner;
68+
};
69+
struct my_nesting_struct {
70+
struct my_struct field;
71+
};");
72+
$source = $cdef->new("struct my_struct");
73+
$source->foo = 123;
74+
$source->bar = 456;
75+
$source->inner->data[0] = 789;
76+
$struct = $cdef->new("struct my_nesting_struct");
77+
$struct->field = $source;
78+
var_dump($struct->field->foo === 123 && $struct->field->bar === 456 && $struct->field->inner->data[0] === 789);
79+
80+
echo "--- Callback return type ---\n";
81+
82+
$ffi = FFI::cdef('
83+
typedef uint32_t (*test_callback)();
84+
typedef struct {
85+
test_callback call_me;
86+
} my_struct;
87+
');
88+
89+
$struct = $ffi->new('my_struct');
90+
$struct->call_me = function () use ($ffi) {
91+
$int = $ffi->new('uint32_t');
92+
$int->cdata = 42;
93+
return $int;
94+
};
95+
96+
var_dump(($struct->call_me)());
97+
98+
echo "--- Other FFI\CData assignment ---\n";
99+
100+
$ffi = FFI::cdef('');
101+
102+
$source = $ffi->new('uint32_t');
103+
$source->cdata = 123;
104+
$target = $ffi->new('uint32_t');
105+
$target->cdata = $source;
106+
107+
var_dump($target->cdata);
108+
109+
echo "--- Array element ---\n";
110+
111+
$ffi = FFI::cdef('');
112+
113+
$source = $ffi->new('uint32_t');
114+
$source->cdata = 123;
115+
$target = $ffi->new('uint32_t[1]');
116+
$target[0] = $source;
117+
118+
var_dump($target[0]);
119+
120+
?>
121+
--EXPECTF--
122+
--- Primitive types ---
123+
Positive test uint8_t: bool(true)
124+
Positive test uint16_t: bool(true)
125+
Positive test uint32_t: bool(true)
126+
Positive test uint64_t: bool(true)
127+
Positive test int8_t: bool(true)
128+
Positive test int16_t: bool(true)
129+
Positive test int32_t: bool(true)
130+
Positive test int64_t: bool(true)
131+
Positive test char: bool(true)
132+
Positive test bool: bool(true)
133+
Positive test float: bool(true)
134+
Positive test double: bool(true)
135+
136+
Warning: Object of class FFI\CData could not be converted to int in %s on line %d
137+
138+
Warning: Object of class FFI\CData could not be converted to int in %s on line %d
139+
140+
Warning: Object of class FFI\CData could not be converted to int in %s on line %d
141+
142+
Warning: Object of class FFI\CData could not be converted to int in %s on line %d
143+
144+
Warning: Object of class FFI\CData could not be converted to int in %s on line %d
145+
146+
Warning: Object of class FFI\CData could not be converted to int in %s on line %d
147+
148+
Warning: Object of class FFI\CData could not be converted to int in %s on line %d
149+
150+
Warning: Object of class FFI\CData could not be converted to int in %s on line %d
151+
152+
Warning: Object of class FFI\CData could not be converted to int in %s on line %d
153+
154+
Warning: Object of class FFI\CData could not be converted to int in %s on line %d
155+
156+
Warning: Object of class FFI\CData could not be converted to int in %s on line %d
157+
158+
Warning: Object of class FFI\CData could not be converted to int in %s on line %d
159+
Positive test enum: bool(true)
160+
161+
Warning: Object of class FFI\CData could not be converted to int in %s on line %d
162+
--- Struct ---
163+
bool(true)
164+
--- Callback return type ---
165+
int(42)
166+
--- Other FFI\CData assignment ---
167+
int(123)
168+
--- Array element ---
169+
int(123)

ext/ffi/tests/gh11934b.phpt

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
Feature GH-11934 (Allow to pass CData into C variables)
3+
--EXTENSIONS--
4+
ffi
5+
zend_test
6+
--FILE--
7+
<?php
8+
require_once __DIR__ . '/utils.inc';
9+
$header = <<<HEADER
10+
extern int gh11934b_ffi_var_test_cdata;
11+
HEADER;
12+
13+
if (PHP_OS_FAMILY !== 'Windows') {
14+
$ffi = FFI::cdef($header);
15+
} else {
16+
try {
17+
$ffi = FFI::cdef($header, 'php_zend_test.dll');
18+
} catch (FFI\Exception $ex) {
19+
$ffi = FFI::cdef($header, ffi_get_php_dll_name());
20+
}
21+
}
22+
23+
$ffi->gh11934b_ffi_var_test_cdata->cdata = 2;
24+
var_dump($ffi->gh11934b_ffi_var_test_cdata);
25+
$source = $ffi->new('int');
26+
$source->cdata = 31;
27+
$ffi->gh11934b_ffi_var_test_cdata = $source;
28+
var_dump($ffi->gh11934b_ffi_var_test_cdata);
29+
30+
?>
31+
--EXPECT--
32+
int(2)
33+
int(31)

ext/zend_test/test.c

+2
Original file line numberDiff line numberDiff line change
@@ -1233,6 +1233,8 @@ PHP_ZEND_TEST_API void bug_gh9090_void_int_char_var(int i, char *fmt, ...) {
12331233
va_end(args);
12341234
}
12351235

1236+
PHP_ZEND_TEST_API int gh11934b_ffi_var_test_cdata;
1237+
12361238
#ifdef HAVE_COPY_FILE_RANGE
12371239
/**
12381240
* This function allows us to simulate early return of copy_file_range by setting the limit_copy_file_range ini setting.

0 commit comments

Comments
 (0)