Skip to content

Commit

Permalink
feature #46773 [VarDumper] Add FFI\CData and FFI\CType types (Ser…
Browse files Browse the repository at this point in the history
…afimArts)

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
  • Loading branch information
fabpot committed Jul 20, 2022
2 parents 4386618 + 09fae1d commit cd56856
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 <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),
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 cd56856

Please sign in to comment.