├── 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 | PHP 8.1+ 5 | Latest Stable Version 6 | Latest Unstable Version 7 | Total Downloads 8 | License MIT 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 |