Skip to content

Commit cd56856

Browse files
committed
feature #46773 [VarDumper] Add FFI\CData and FFI\CType types (SerafimArts)
This PR was squashed before being merged into the 6.2 branch. Discussion ---------- [VarDumper] Add `FFI\CData` and `FFI\CType` types | Q | A | ------------- | --- | Branch? | 6.2 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - Added support of FFI: ```php $ffi = \FFI::cdef(<<<'CPP' typedef struct { int x; int y; } Point; typedef struct Example { uint8_t array[32]; long longVal; __extension__ union { __extension__ struct { short shortVal; }; struct { Point point; float e; }; }; bool boolValue; int (*func)(struct __sub *h); } Example; CPP); $struct = $ffi->new('Example'); $struct->func = (static fn (object $ptr) => 42); dump($struct); ``` **Before** ``` FFI\CData {#2} ``` **After** ``` FFI\CData<struct Example> size 64 align 8 {#2 +array: FFI\CData<uint8_t[32]> size 32 align 1 {#18} +int32_t longVal: 0 +int16_t shortVal: 0 +point: FFI\CData<struct <anonymous>> size 8 align 4 {#17 +int32_t x: 0 +int32_t y: 0 } +float e: 0.0 +bool boolValue: false +func: [cdecl] callable(struct __sub*): int32_t {#20 returnType: FFI\CType<int32_t> size 4 align 4 {#25} } } ``` P.S. I apologize for the multiple force pushes, errors in tests and codestyle have been fixed. ## Review And TODOs - Pointers - [x] Pointer to scalar tests. - [x] Pointer to struct tests. - [x] "Special" pointer to `char*` tests (with `\0` and without `\0`). - Possible Errors - [x] Do not dump union fields with pointer references (possible SIGSEGV). Commits ------- 1c7dc52abb [VarDumper] Add `FFI\CData` and `FFI\CType` types
2 parents 4386618 + 09fae1d commit cd56856

File tree

4 files changed

+640
-0
lines changed

4 files changed

+640
-0
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
6.2
5+
---
6+
7+
* Add support for `FFI\CData` and `FFI\CType`
8+
49
5.4
510
---
611

Caster/FFICaster.php

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\VarDumper\Caster;
13+
14+
use FFI\CData;
15+
use FFI\CType;
16+
use Symfony\Component\VarDumper\Cloner\Stub;
17+
18+
/**
19+
* Casts FFI extension classes to array representation.
20+
*
21+
* @author Nesmeyanov Kirill <[email protected]>
22+
*/
23+
final class FFICaster
24+
{
25+
/**
26+
* In case of "char*" contains a string, the length of which depends on
27+
* some other parameter, then during the generation of the string it is
28+
* possible to go beyond the allowable memory area.
29+
*
30+
* This restriction serves to ensure that processing does not take
31+
* up the entire allowable PHP memory limit.
32+
*/
33+
private const MAX_STRING_LENGTH = 255;
34+
35+
public static function castCTypeOrCData(CData|CType $data, array $args, Stub $stub): array
36+
{
37+
if ($data instanceof CType) {
38+
$type = $data;
39+
$data = null;
40+
} else {
41+
$type = \FFI::typeof($data);
42+
}
43+
44+
$stub->class = sprintf('%s<%s> size %d align %d', ($data ?? $type)::class, $type->getName(), $type->getSize(), $type->getAlignment());
45+
46+
return match ($type->getKind()) {
47+
CType::TYPE_FLOAT,
48+
CType::TYPE_DOUBLE,
49+
\defined('\FFI\CType::TYPE_LONGDOUBLE') ? CType::TYPE_LONGDOUBLE : -1,
50+
CType::TYPE_UINT8,
51+
CType::TYPE_SINT8,
52+
CType::TYPE_UINT16,
53+
CType::TYPE_SINT16,
54+
CType::TYPE_UINT32,
55+
CType::TYPE_SINT32,
56+
CType::TYPE_UINT64,
57+
CType::TYPE_SINT64,
58+
CType::TYPE_BOOL,
59+
CType::TYPE_CHAR,
60+
CType::TYPE_ENUM => null !== $data ? [Caster::PREFIX_VIRTUAL.'cdata' => $data->cdata] : [],
61+
CType::TYPE_POINTER => self::castFFIPointer($stub, $type, $data),
62+
CType::TYPE_STRUCT => self::castFFIStructLike($type, $data),
63+
CType::TYPE_FUNC => self::castFFIFunction($stub, $type),
64+
default => $args,
65+
};
66+
}
67+
68+
private static function castFFIFunction(Stub $stub, CType $type): array
69+
{
70+
$arguments = [];
71+
72+
for ($i = 0, $count = $type->getFuncParameterCount(); $i < $count; ++$i) {
73+
$param = $type->getFuncParameterType($i);
74+
75+
$arguments[] = $param->getName();
76+
}
77+
78+
$abi = match ($type->getFuncABI()) {
79+
CType::ABI_DEFAULT,
80+
CType::ABI_CDECL => '[cdecl]',
81+
CType::ABI_FASTCALL => '[fastcall]',
82+
CType::ABI_THISCALL => '[thiscall]',
83+
CType::ABI_STDCALL => '[stdcall]',
84+
CType::ABI_PASCAL => '[pascal]',
85+
CType::ABI_REGISTER => '[register]',
86+
CType::ABI_MS => '[ms]',
87+
CType::ABI_SYSV => '[sysv]',
88+
CType::ABI_VECTORCALL => '[vectorcall]',
89+
default => '[unknown abi]'
90+
};
91+
92+
$returnType = $type->getFuncReturnType();
93+
94+
$stub->class = $abi.' callable('.implode(', ', $arguments).'): '
95+
.$returnType->getName();
96+
97+
return [Caster::PREFIX_VIRTUAL.'returnType' => $returnType];
98+
}
99+
100+
private static function castFFIPointer(Stub $stub, CType $type, CData $data = null): array
101+
{
102+
$ptr = $type->getPointerType();
103+
104+
if (null === $data) {
105+
return [Caster::PREFIX_VIRTUAL.'0' => $ptr];
106+
}
107+
108+
return match ($ptr->getKind()) {
109+
CType::TYPE_CHAR => [Caster::PREFIX_VIRTUAL.'cdata' => self::castFFIStringValue($data)],
110+
CType::TYPE_FUNC => self::castFFIFunction($stub, $ptr),
111+
default => [Caster::PREFIX_VIRTUAL.'cdata' => $data[0]],
112+
};
113+
}
114+
115+
private static function castFFIStringValue(CData $data): string|CutStub
116+
{
117+
$result = [];
118+
119+
for ($i = 0; $i < self::MAX_STRING_LENGTH; ++$i) {
120+
$result[$i] = $data[$i];
121+
122+
if ("\0" === $result[$i]) {
123+
return implode('', $result);
124+
}
125+
}
126+
127+
$string = implode('', $result);
128+
$stub = new CutStub($string);
129+
$stub->cut = -1;
130+
$stub->value = $string;
131+
132+
return $stub;
133+
}
134+
135+
private static function castFFIStructLike(CType $type, CData $data = null): array
136+
{
137+
$isUnion = ($type->getAttributes() & CType::ATTR_UNION) === CType::ATTR_UNION;
138+
139+
$result = [];
140+
141+
foreach ($type->getStructFieldNames() as $name) {
142+
$field = $type->getStructFieldType($name);
143+
144+
// Retrieving the value of a field from a union containing
145+
// a pointer is not a safe operation, because may contain
146+
// incorrect data.
147+
$isUnsafe = $isUnion && CType::TYPE_POINTER === $field->getKind();
148+
149+
if ($isUnsafe) {
150+
$result[Caster::PREFIX_VIRTUAL.$name.'?'] = $field;
151+
} elseif (null === $data) {
152+
$result[Caster::PREFIX_VIRTUAL.$name] = $field;
153+
} else {
154+
$fieldName = $data->{$name} instanceof CData ? '' : $field->getName().' ';
155+
$result[Caster::PREFIX_VIRTUAL.$fieldName.$name] = $data->{$name};
156+
}
157+
}
158+
159+
return $result;
160+
}
161+
}

Cloner/AbstractCloner.php

+3
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ abstract class AbstractCloner implements ClonerInterface
187187
'RdKafka\Topic' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopic'],
188188
'RdKafka\TopicPartition' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicPartition'],
189189
'RdKafka\TopicConf' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicConf'],
190+
191+
'FFI\CData' => ['Symfony\Component\VarDumper\Caster\FFICaster', 'castCTypeOrCData'],
192+
'FFI\CType' => ['Symfony\Component\VarDumper\Caster\FFICaster', 'castCTypeOrCData'],
190193
];
191194

192195
protected $maxItems = 2500;

0 commit comments

Comments
 (0)