-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[VarDumper] Add FFI\CData
and FFI\CType
types
#46773
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,11 @@ | ||
CHANGELOG | ||
========= | ||
|
||
6.2 | ||
--- | ||
|
||
* Add support for `FFI\CData` and `FFI\CType` | ||
|
||
5.4 | ||
--- | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\VarDumper\Caster; | ||
|
||
use FFI\CData; | ||
use FFI\CType; | ||
use Symfony\Component\VarDumper\Cloner\Stub; | ||
|
||
/** | ||
* Casts FFI extension classes to array representation. | ||
* | ||
* @author Nesmeyanov Kirill <nesk@xakep.ru> | ||
*/ | ||
final class FFICaster | ||
{ | ||
/** | ||
* In case of "char*" contains a string, the length of which depends on | ||
* some other parameter, then during the generation of the string it is | ||
* possible to go beyond the allowable memory area. | ||
* | ||
* This restriction serves to ensure that processing does not take | ||
* up the entire allowable PHP memory limit. | ||
*/ | ||
private const MAX_STRING_LENGTH = 255; | ||
|
||
public static function castCTypeOrCData(CData|CType $data, array $args, Stub $stub): array | ||
{ | ||
if ($data instanceof CType) { | ||
$type = $data; | ||
$data = null; | ||
} else { | ||
$type = \FFI::typeof($data); | ||
} | ||
|
||
$stub->class = sprintf('%s<%s> size %d align %d', ($data ?? $type)::class, $type->getName(), $type->getSize(), $type->getAlignment()); | ||
|
||
return match ($type->getKind()) { | ||
CType::TYPE_FLOAT, | ||
CType::TYPE_DOUBLE, | ||
\defined('\FFI\CType::TYPE_LONGDOUBLE') ? CType::TYPE_LONGDOUBLE : -1, | ||
CType::TYPE_UINT8, | ||
CType::TYPE_SINT8, | ||
CType::TYPE_UINT16, | ||
CType::TYPE_SINT16, | ||
CType::TYPE_UINT32, | ||
CType::TYPE_SINT32, | ||
CType::TYPE_UINT64, | ||
CType::TYPE_SINT64, | ||
CType::TYPE_BOOL, | ||
CType::TYPE_CHAR, | ||
CType::TYPE_ENUM => null !== $data ? [Caster::PREFIX_VIRTUAL.'cdata' => $data->cdata] : [], | ||
CType::TYPE_POINTER => self::castFFIPointer($stub, $type, $data), | ||
CType::TYPE_STRUCT => self::castFFIStructLike($type, $data), | ||
CType::TYPE_FUNC => self::castFFIFunction($stub, $type), | ||
default => $args, | ||
}; | ||
} | ||
|
||
private static function castFFIFunction(Stub $stub, CType $type): array | ||
{ | ||
$arguments = []; | ||
|
||
for ($i = 0, $count = $type->getFuncParameterCount(); $i < $count; ++$i) { | ||
$param = $type->getFuncParameterType($i); | ||
|
||
$arguments[] = $param->getName(); | ||
} | ||
|
||
$abi = match ($type->getFuncABI()) { | ||
CType::ABI_DEFAULT, | ||
CType::ABI_CDECL => '[cdecl]', | ||
CType::ABI_FASTCALL => '[fastcall]', | ||
CType::ABI_THISCALL => '[thiscall]', | ||
CType::ABI_STDCALL => '[stdcall]', | ||
CType::ABI_PASCAL => '[pascal]', | ||
CType::ABI_REGISTER => '[register]', | ||
CType::ABI_MS => '[ms]', | ||
CType::ABI_SYSV => '[sysv]', | ||
CType::ABI_VECTORCALL => '[vectorcall]', | ||
default => '[unknown abi]' | ||
}; | ||
|
||
$returnType = $type->getFuncReturnType(); | ||
|
||
$stub->class = $abi.' callable('.implode(', ', $arguments).'): ' | ||
.$returnType->getName(); | ||
|
||
return [Caster::PREFIX_VIRTUAL.'returnType' => $returnType]; | ||
} | ||
|
||
private static function castFFIPointer(Stub $stub, CType $type, CData $data = null): array | ||
{ | ||
$ptr = $type->getPointerType(); | ||
|
||
if (null === $data) { | ||
return [Caster::PREFIX_VIRTUAL.'0' => $ptr]; | ||
} | ||
|
||
return match ($ptr->getKind()) { | ||
CType::TYPE_CHAR => [Caster::PREFIX_VIRTUAL.'cdata' => self::castFFIStringValue($data)], | ||
CType::TYPE_FUNC => self::castFFIFunction($stub, $ptr), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From what I understand, a pointer pointing to a function is going to be dumped the same as dumping a function. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yep. However, a function is always a pointer to it (seemingly o___0). It looks something like: $pointer = function () { ... }; The variable contains a reference to a function (i.e. a pointer to it), but just like that, a function does not exist without a pointer to it. Maybe I'm wrong on this. Need a C/C++ specialist, which I am not :D
Perhaps arrays with a predetermined size 🤔 |
||
default => [Caster::PREFIX_VIRTUAL.'cdata' => $data[0]], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That might be a dumb question as I don't know FFI much, by why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I noted above, this is getting real data by a pointer to this data: No, there are no tests for pointers. Can be added, no problem. |
||
}; | ||
} | ||
|
||
private static function castFFIStringValue(CData $data): string|CutStub | ||
{ | ||
$result = []; | ||
|
||
for ($i = 0; $i < self::MAX_STRING_LENGTH; ++$i) { | ||
$result[$i] = $data[$i]; | ||
|
||
if ("\0" === $result[$i]) { | ||
return implode('', $result); | ||
} | ||
} | ||
|
||
$string = implode('', $result); | ||
$stub = new CutStub($string); | ||
$stub->cut = -1; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I replace the custom truncation by a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In most cases, strings contain a trailing However, this is not entirely safe, since the function can return strings without the terminating
In this case, while we are reading the data from No, this has only been tested in manual mode. You are right, we need to add to this too. |
||
$stub->value = $string; | ||
|
||
return $stub; | ||
} | ||
|
||
private static function castFFIStructLike(CType $type, CData $data = null): array | ||
{ | ||
$isUnion = ($type->getAttributes() & CType::ATTR_UNION) === CType::ATTR_UNION; | ||
|
||
$result = []; | ||
|
||
foreach ($type->getStructFieldNames() as $name) { | ||
$field = $type->getStructFieldType($name); | ||
|
||
// Retrieving the value of a field from a union containing | ||
// a pointer is not a safe operation, because may contain | ||
// incorrect data. | ||
$isUnsafe = $isUnion && CType::TYPE_POINTER === $field->getKind(); | ||
|
||
if ($isUnsafe) { | ||
$result[Caster::PREFIX_VIRTUAL.$name.'?'] = $field; | ||
} elseif (null === $data) { | ||
$result[Caster::PREFIX_VIRTUAL.$name] = $field; | ||
} else { | ||
$fieldName = $data->{$name} instanceof CData ? '' : $field->getName().' '; | ||
$result[Caster::PREFIX_VIRTUAL.$fieldName.$name] = $data->{$name}; | ||
} | ||
} | ||
|
||
return $result; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand what this is about, can you please add a test case to cover it?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any pointer contains inside a link to where it points and at least one element:
Although the size and type of the memory area where the pointer refers is not known (I propose to provide an alternative for
void*
, a pointer to something undefined) in advance, there is at least a zero element there (if the memory area is available).In this case, if the
CData
was not transferred, a structure based on the type will be formed:We can also get where the pointer "points" to via array access:
Ok, ill add a tests