162 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			162 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?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;
 | |
|     }
 | |
| }
 | 
