Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[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

Merged
merged 1 commit into from
Jul 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions 5 src/Symfony/Component/VarDumper/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 161 src/Symfony/Component/VarDumper/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];
Copy link
Member

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?

Copy link
Contributor Author

@SerafimArts SerafimArts Jun 27, 2022

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

Any pointer contains inside a link to where it points and at least one element:

// C
int *getArray() 
{
    int *array = new int[42];
    array[0] = 1;
    array[1] = 2;

    return array;
}

// >> \FFI::cdef()
extern int *getArray(void);

// >> PHP
$data = $ffi->getArray();
dump($data);

// >> VarDumper representation

FFI\CData<int32_t*> { #0
    0: FFI\CData<int32_t> { #1
        +cdata: 1
    }
}

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:

FFI\CType<int32_t*> { #0
    0: FFI\CType<int32_t> { #1 }
}

We can also get where the pointer "points" to via array access:

echo $data[0]->cdata; // 1
// OR (magic of FFI: alternative, but not strict option)
echo $data->cdata; // 1

Ok, ill add a tests

}

return match ($ptr->getKind()) {
CType::TYPE_CHAR => [Caster::PREFIX_VIRTUAL.'cdata' => self::castFFIStringValue($data)],
CType::TYPE_FUNC => self::castFFIFunction($stub, $ptr),
Copy link
Member

Choose a reason for hiding this comment

The 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.
Did I get this right? Shouldn't we make a difference between both?
Do we have the same concern for other kind of pointed values?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

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

Do we have the same concern for other kind of pointed values?

Perhaps arrays with a predetermined size 🤔

default => [Caster::PREFIX_VIRTUAL.'cdata' => $data[0]],
Copy link
Member

Choose a reason for hiding this comment

The 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 $data[0]? Is this tested?

Copy link
Contributor Author

@SerafimArts SerafimArts Jun 27, 2022

Choose a reason for hiding this comment

The 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: $data->getType(); // int* and $data[0]->getType(); // int

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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I replace the custom truncation by a CutStub.
Can't we know the size of the truncated data btw?
I think this is also not tested, isn't it? Can it be?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we know the size of the truncated data btw?

In most cases, strings contain a trailing \0 (or \0\0 in wchar_t). This is how, for example, the function FFI::string($pointerToChar); // "string\0" works.

However, this is not entirely safe, since the function can return strings without the terminating \0, and store the length separately:

struct CustomString {
    char* data;
    int length;
}

In this case, while we are reading the data from char array, we can get into the "junk memory" or get an Access Violation or Segmentation Fault errors (https://en.wikipedia.org/wiki/Segmentation_fault). Therefore, there is a limit on the length of the line, so that even when reading extra data, we can stop in time.


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;
}
}
3 changes: 3 additions & 0 deletions 3 src/Symfony/Component/VarDumper/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
Morty Proxy This is a proxified and sanitized view of the page, visit original site.