Skip to content

Commit

Permalink
[VarDumper] Add FFI\CData and FFI\CType types
Browse files Browse the repository at this point in the history
  • Loading branch information
SerafimArts authored and fabpot committed Jul 20, 2022
1 parent 98587d9 commit 09fae1d
Show file tree
Hide file tree
Showing 4 changed files with 640 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
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
---

Expand Down
161 changes: 161 additions & 0 deletions Caster/FFICaster.php
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 <[email protected]>
*
* 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 <[email protected]>
*/
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),
default => [Caster::PREFIX_VIRTUAL.'cdata' => $data[0]],
};
}

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;
$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;
}
}
3 changes: 3 additions & 0 deletions Cloner/AbstractCloner.php
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ abstract class AbstractCloner implements ClonerInterface
'RdKafka\Topic' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopic'],
'RdKafka\TopicPartition' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicPartition'],
'RdKafka\TopicConf' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicConf'],

'FFI\CData' => ['Symfony\Component\VarDumper\Caster\FFICaster', 'castCTypeOrCData'],
'FFI\CType' => ['Symfony\Component\VarDumper\Caster\FFICaster', 'castCTypeOrCData'],
];

protected $maxItems = 2500;
Expand Down
Loading

0 comments on commit 09fae1d

Please sign in to comment.