├── LICENSE.md
├── README.md
├── composer.json
├── phpcs.xml
├── psalm.xml
└── src
├── Caster
├── FFICDataCaster.php
├── FFICTypeCaster.php
└── FFICaster.php
└── bootstrap.php
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright © Kirill Nesmeyanov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # VarDumper Extension For FFI Types
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | This library allows you to dump FFI types using the functions `dd()` and `dump()`.
15 |
16 | ## Requirements
17 |
18 | - PHP >= 8.1
19 |
20 | ## Installation
21 |
22 | Library is available as composer repository and can be installed using the
23 | following command in a root of your project.
24 |
25 | ```sh
26 | $ composer require ffi/var-dumper
27 | ```
28 |
29 | ## Usage
30 |
31 | ```php
32 | dump(\FFI::new('struct { float x }'));
33 |
34 | //
35 | // Expected Output:
36 | //
37 | // struct {
38 | // x: 0.0
39 | // }
40 | //
41 | ```
42 |
43 | ### Unsafe Access
44 |
45 | Some values may contain data that will cause access errors when read. For
46 | example, pointers leading to "emptiness".
47 |
48 | Such data is marked as "unsafe" and only the first element is displayed. If you
49 | want to display the values in full, you should use the `VAR_DUMPER_FFI_UNSAFE=1`
50 | environment variable.
51 |
52 | ```php
53 | // Create char* with "Hello World!\0" string.
54 | $string = \FFI::new('char[13]');
55 | \FFI::memcpy($string, 'Hello World!', 12);
56 | $pointer = \FFI::cast('char*', $string);
57 |
58 | // Dump
59 | dump($pointer);
60 |
61 | // VAR_DUMPER_FFI_UNSAFE=0
62 | //
63 | // > char* (unsafe access) {
64 | // > +0: "H"
65 | // > }
66 |
67 | // VAR_DUMPER_FFI_UNSAFE=1
68 | //
69 | // > b"Hello World!\x00"
70 | ```
71 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ffi/var-dumper",
3 | "type": "library",
4 | "description": "List of symfony/var-dumper casters with FFI support",
5 | "license": "MIT",
6 | "keywords": ["ffi", "var-dumper", "symfony", "casters"],
7 | "support": {
8 | "source": "https://github.com/php-ffi/var-dumper",
9 | "issues": "https://github.com/php-ffi/var-dumper/issues",
10 | "docs": "https://github.com/php-ffi/var-dumper/blob/master/README.md"
11 | },
12 | "authors": [
13 | {
14 | "name": "Nesmeyanov Kirill",
15 | "email": "nesk@xakep.ru",
16 | "homepage": "https://serafimarts.ru",
17 | "role": "maintainer"
18 | }
19 | ],
20 | "require": {
21 | "php": "^8.1",
22 | "symfony/var-dumper": "^5.4|^6.0|^7.0"
23 | },
24 | "autoload": {
25 | "psr-4": {
26 | "FFI\\VarDumper\\": "src"
27 | },
28 | "files": [
29 | "src/bootstrap.php"
30 | ]
31 | },
32 | "require-dev": {
33 | "vimeo/psalm": "^5.4",
34 | "phpunit/phpunit": "^9.5",
35 | "squizlabs/php_codesniffer": "^3.7"
36 | },
37 | "autoload-dev": {
38 | "psr-4": {
39 | "FFI\\VarDumper\\Tests\\": "tests"
40 | }
41 | },
42 | "config": {
43 | "sort-packages": true,
44 | "optimize-autoloader": true
45 | },
46 | "extra": {
47 | "branch-alias": {
48 | "dev-main": "1.0.x-dev",
49 | "dev-master": "1.0.x-dev"
50 | }
51 | },
52 | "minimum-stability": "dev",
53 | "prefer-stable": true
54 | }
55 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | ./src
46 |
47 |
48 | */tests/*
49 |
50 |
--------------------------------------------------------------------------------
/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/Caster/FFICDataCaster.php:
--------------------------------------------------------------------------------
1 | class = $type->getName();
33 | $stub->handle = 0;
34 |
35 | return match (true) {
36 | self::isScalar($type), self::isEnum($type) => [Caster::PREFIX_VIRTUAL . 'cdata' => $data->cdata],
37 | self::isPointer($type) => self::castFFIPointer($stub, $type, $data),
38 | self::isFunction($type) => self::castFFIFunction($stub, $type),
39 | self::isStructLike($type) => self::castFFIStructLike($stub, $type, $data),
40 | self::isArray($type) => self::castFFIArrayType($stub, $type, $data),
41 | default => $args,
42 | };
43 | }
44 |
45 | private static function castFFIStringValue(CData $data): string|CutStub
46 | {
47 | $result = [];
48 |
49 | for ($i = 0; $i < self::MAX_STRING_LENGTH; ++$i) {
50 | $result[$i] = $data[$i];
51 |
52 | if ($result[$i] === "\0") {
53 | break;
54 | }
55 | }
56 |
57 | return \implode('', $result);
58 | }
59 |
60 | /**
61 | * @param CType $type
62 | * @param CData $data
63 | * @return array
64 | */
65 | private static function arrayValues(CType $type, CData $data): array
66 | {
67 | $result = [];
68 |
69 | for ($i = 0, $size = $type->getArrayLength(); $i < $size; ++$i) {
70 | $result[] = $data[$i];
71 | }
72 |
73 | return $result;
74 | }
75 |
76 | private static function castFFIArrayType(Stub $stub, CType $type, CData $data): array
77 | {
78 | $of = $type->getArrayElementType();
79 |
80 | if ($of->getKind() === CType::TYPE_CHAR) {
81 | $stub->type = Stub::TYPE_STRING;
82 | $stub->value = \implode('', self::arrayValues($type, $data));
83 | $stub->class = Stub::STRING_BINARY;
84 |
85 | return [];
86 | }
87 |
88 | $stub->type = Stub::TYPE_ARRAY;
89 | $stub->value = $type->getName();
90 | $stub->class = $type->getArrayLength();
91 |
92 | return self::arrayValues($type, $data);
93 | }
94 |
95 | private static function castFFIPointer(Stub $stub, CType $type, CData $data): mixed
96 | {
97 | $stub->class = $type->getName();
98 | $reference = $type->getPointerType();
99 |
100 | if (self::isStructLike($reference)) {
101 | return self::castFFIStructLike($stub, $reference, $data[0]);
102 | }
103 |
104 | if (self::isFunction($reference)) {
105 | return self::castFFIFunction($stub, $reference);
106 | }
107 |
108 | if ($reference->getKind() === CType::TYPE_CHAR) {
109 | $stub->class .= ' (unsafe access)';
110 |
111 | if ($_SERVER['VAR_DUMPER_FFI_UNSAFE'] ?? false) {
112 | $stub->type = Stub::TYPE_STRING;
113 | $stub->value = self::castFFIStringValue($data);
114 | $stub->class = Stub::STRING_BINARY;
115 |
116 | return [];
117 | }
118 | }
119 |
120 | return [0 => $data[0]];
121 | }
122 |
123 | private static function castFFIFunction(Stub $stub, CType $type): array
124 | {
125 | $stub->class = self::funcToString($type);
126 |
127 | return [Caster::PREFIX_VIRTUAL . 'returnType' => $type->getFuncReturnType()];
128 | }
129 |
130 | private static function castFFIStructLike(Stub $stub, CType $type, CData $data): array
131 | {
132 | $result = [];
133 |
134 | foreach ($type->getStructFieldNames() as $name) {
135 | $field = $type->getStructFieldType($name);
136 |
137 | if (self::isStructLike($field) || self::isPointerToStructLike($field)) {
138 | $result[Caster::PREFIX_VIRTUAL . $name] = $data->{$name};
139 | } else {
140 | $result[Caster::PREFIX_VIRTUAL . $name . '<' . $field->getName() . '>'] = $data->{$name};
141 | }
142 | }
143 |
144 | return $result;
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/Caster/FFICTypeCaster.php:
--------------------------------------------------------------------------------
1 | class = 'FFI\CType<' . $type->getName() . '>';
19 |
20 | return match (true) {
21 | self::isScalar($type),
22 | self::isEnum($type) => [],
23 | self::isPointer($type) => [Caster::PREFIX_VIRTUAL . '0' => $type->getPointerType()],
24 | self::isFunction($type) => self::castFFIFunction($stub, $type),
25 | self::isStruct($type) => self::castFFIStruct($type),
26 | self::isUnion($type) => self::castFFIUnion($type),
27 | default => $args,
28 | };
29 | }
30 |
31 | private static function castFFIUnion(CType $type): array
32 | {
33 | $result = [];
34 | foreach ($type->getStructFieldNames() as $name) {
35 | $result[Caster::PREFIX_VIRTUAL . $name . '?'] = $type->getStructFieldType($name);
36 | }
37 |
38 | return $result;
39 | }
40 |
41 | private static function castFFIStruct(CType $type): array
42 | {
43 | $result = [];
44 |
45 | foreach ($type->getStructFieldNames() as $name) {
46 | $result[Caster::PREFIX_VIRTUAL . $name] = $type->getStructFieldType($name);
47 | }
48 |
49 | return $result;
50 | }
51 |
52 | private static function castFFIFunction(Stub $stub, CType $type): array
53 | {
54 | $stub->class = self::funcToString($type);
55 |
56 | return [Caster::PREFIX_VIRTUAL . 'returnType' => $type->getFuncReturnType()];
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Caster/FFICaster.php:
--------------------------------------------------------------------------------
1 | cdata;
37 | }
38 |
39 | /**
40 | * @param CType $type
41 | * @return bool
42 | */
43 | protected static function isScalar(CType $type): bool
44 | {
45 | $scalars = self::KIND_SCALARS;
46 |
47 | if (\defined('\FFI\CType::TYPE_LONGDOUBLE')) {
48 | $scalars[] = CType::TYPE_LONGDOUBLE;
49 | }
50 |
51 | return \in_array($type->getKind(), $scalars, true);
52 | }
53 |
54 | /**
55 | * @param CType $type
56 | * @return bool
57 | */
58 | protected static function isEnum(CType $type): bool
59 | {
60 | return $type->getKind() === CType::TYPE_ENUM;
61 | }
62 |
63 | /**
64 | * @param CType $type
65 | * @return bool
66 | */
67 | protected static function isArray(CType $type): bool
68 | {
69 | return $type->getKind() === CType::TYPE_ARRAY;
70 | }
71 |
72 | /**
73 | * @param CType $type
74 | * @return bool
75 | */
76 | protected static function isPointer(CType $type): bool
77 | {
78 | return $type->getKind() === CType::TYPE_POINTER;
79 | }
80 |
81 | /**
82 | * @param CType $type
83 | * @return bool
84 | */
85 | protected static function isFunction(CType $type): bool
86 | {
87 | return $type->getKind() === CType::TYPE_FUNC;
88 | }
89 |
90 | /**
91 | * @param CType $type
92 | * @return bool
93 | */
94 | protected static function isStruct(CType $type): bool
95 | {
96 | return self::isStructLike($type)
97 | && ($type->getAttributes() & CType::ATTR_UNION) !== CType::ATTR_UNION;
98 | }
99 |
100 | /**
101 | * @param CType $type
102 | * @return bool
103 | */
104 | protected static function isUnion(CType $type): bool
105 | {
106 | return self::isStructLike($type)
107 | && ($type->getAttributes() & CType::ATTR_UNION) === CType::ATTR_UNION;
108 | }
109 |
110 | /**
111 | * @param CType $type
112 | * @return bool
113 | */
114 | protected static function isStructLike(CType $type): bool
115 | {
116 | return $type->getKind() === CType::TYPE_STRUCT;
117 | }
118 |
119 | /**
120 | * @param CType $type
121 | * @return bool
122 | */
123 | protected static function isPointerToStructLike(CType $type): bool
124 | {
125 | if ($type->getKind() !== CType::TYPE_POINTER) {
126 | return false;
127 | }
128 |
129 | $reference = $type->getPointerType();
130 |
131 | if ($reference->getKind() === CType::TYPE_POINTER) {
132 | return self::isPointerToStructLike($reference);
133 | }
134 |
135 | return self::isStructLike($reference);
136 | }
137 |
138 | /**
139 | * @param CType $function
140 | * @return non-empty-string
141 | */
142 | protected static function funcAbiToString(CType $function): string
143 | {
144 | return match ($function->getFuncABI()) {
145 | CType::ABI_DEFAULT, CType::ABI_CDECL => 'cdecl',
146 | CType::ABI_FASTCALL => 'fastcall',
147 | CType::ABI_THISCALL => 'thiscall',
148 | CType::ABI_STDCALL => 'stdcall',
149 | CType::ABI_PASCAL => 'pascal',
150 | CType::ABI_REGISTER => 'register',
151 | CType::ABI_MS => 'ms',
152 | CType::ABI_SYSV => 'sysv',
153 | CType::ABI_VECTORCALL => 'vectorcall',
154 | default => 'unknown'
155 | };
156 | }
157 |
158 | /**
159 | * @param CType $type
160 | * @return string
161 | */
162 | protected static function funcToString(CType $type): string
163 | {
164 | $arguments = [];
165 |
166 | for ($i = 0, $count = $type->getFuncParameterCount(); $i < $count; ++$i) {
167 | $param = $type->getFuncParameterType($i);
168 |
169 | $arguments[] = $param->getName();
170 | }
171 |
172 | $returnType = $type->getFuncReturnType();
173 |
174 | return \vsprintf('[%s] callable(%s): %s', [
175 | self::funcAbiToString($type),
176 | \implode(', ', $arguments),
177 | $returnType->getName(),
178 | ]);
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/src/bootstrap.php:
--------------------------------------------------------------------------------
1 |