FFICaster.php 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\VarDumper\Caster;
  11. use FFI\CData;
  12. use FFI\CType;
  13. use Symfony\Component\VarDumper\Cloner\Stub;
  14. /**
  15. * Casts FFI extension classes to array representation.
  16. *
  17. * @author Nesmeyanov Kirill <nesk@xakep.ru>
  18. */
  19. final class FFICaster
  20. {
  21. /**
  22. * In case of "char*" contains a string, the length of which depends on
  23. * some other parameter, then during the generation of the string it is
  24. * possible to go beyond the allowable memory area.
  25. *
  26. * This restriction serves to ensure that processing does not take
  27. * up the entire allowable PHP memory limit.
  28. */
  29. private const MAX_STRING_LENGTH = 255;
  30. public static function castCTypeOrCData(CData|CType $data, array $args, Stub $stub): array
  31. {
  32. if ($data instanceof CType) {
  33. $type = $data;
  34. $data = null;
  35. } else {
  36. $type = \FFI::typeof($data);
  37. }
  38. $stub->class = \sprintf('%s<%s> size %d align %d', ($data ?? $type)::class, $type->getName(), $type->getSize(), $type->getAlignment());
  39. return match ($type->getKind()) {
  40. CType::TYPE_FLOAT,
  41. CType::TYPE_DOUBLE,
  42. \defined('\FFI\CType::TYPE_LONGDOUBLE') ? CType::TYPE_LONGDOUBLE : -1,
  43. CType::TYPE_UINT8,
  44. CType::TYPE_SINT8,
  45. CType::TYPE_UINT16,
  46. CType::TYPE_SINT16,
  47. CType::TYPE_UINT32,
  48. CType::TYPE_SINT32,
  49. CType::TYPE_UINT64,
  50. CType::TYPE_SINT64,
  51. CType::TYPE_BOOL,
  52. CType::TYPE_CHAR,
  53. CType::TYPE_ENUM => null !== $data ? [Caster::PREFIX_VIRTUAL.'cdata' => $data->cdata] : [],
  54. CType::TYPE_POINTER => self::castFFIPointer($stub, $type, $data),
  55. CType::TYPE_STRUCT => self::castFFIStructLike($type, $data),
  56. CType::TYPE_FUNC => self::castFFIFunction($stub, $type),
  57. default => $args,
  58. };
  59. }
  60. private static function castFFIFunction(Stub $stub, CType $type): array
  61. {
  62. $arguments = [];
  63. for ($i = 0, $count = $type->getFuncParameterCount(); $i < $count; ++$i) {
  64. $param = $type->getFuncParameterType($i);
  65. $arguments[] = $param->getName();
  66. }
  67. $abi = match ($type->getFuncABI()) {
  68. CType::ABI_DEFAULT,
  69. CType::ABI_CDECL => '[cdecl]',
  70. CType::ABI_FASTCALL => '[fastcall]',
  71. CType::ABI_THISCALL => '[thiscall]',
  72. CType::ABI_STDCALL => '[stdcall]',
  73. CType::ABI_PASCAL => '[pascal]',
  74. CType::ABI_REGISTER => '[register]',
  75. CType::ABI_MS => '[ms]',
  76. CType::ABI_SYSV => '[sysv]',
  77. CType::ABI_VECTORCALL => '[vectorcall]',
  78. default => '[unknown abi]',
  79. };
  80. $returnType = $type->getFuncReturnType();
  81. $stub->class = $abi.' callable('.implode(', ', $arguments).'): '
  82. .$returnType->getName();
  83. return [Caster::PREFIX_VIRTUAL.'returnType' => $returnType];
  84. }
  85. private static function castFFIPointer(Stub $stub, CType $type, ?CData $data = null): array
  86. {
  87. $ptr = $type->getPointerType();
  88. if (null === $data) {
  89. return [Caster::PREFIX_VIRTUAL.'0' => $ptr];
  90. }
  91. return match ($ptr->getKind()) {
  92. CType::TYPE_CHAR => [Caster::PREFIX_VIRTUAL.'cdata' => self::castFFIStringValue($data)],
  93. CType::TYPE_FUNC => self::castFFIFunction($stub, $ptr),
  94. default => [Caster::PREFIX_VIRTUAL.'cdata' => $data[0]],
  95. };
  96. }
  97. private static function castFFIStringValue(CData $data): string|CutStub
  98. {
  99. $result = [];
  100. $ffi = \FFI::cdef(<<<C
  101. size_t zend_get_page_size(void);
  102. C);
  103. $pageSize = $ffi->zend_get_page_size();
  104. // get cdata address
  105. $start = $ffi->cast('uintptr_t', $ffi->cast('char*', $data))->cdata;
  106. // accessing memory in the same page as $start is safe
  107. $max = min(self::MAX_STRING_LENGTH, ($start | ($pageSize - 1)) - $start);
  108. for ($i = 0; $i < $max; ++$i) {
  109. $result[$i] = $data[$i];
  110. if ("\0" === $data[$i]) {
  111. return implode('', $result);
  112. }
  113. }
  114. $string = implode('', $result);
  115. $stub = new CutStub($string);
  116. $stub->cut = -1;
  117. $stub->value = $string;
  118. return $stub;
  119. }
  120. private static function castFFIStructLike(CType $type, ?CData $data = null): array
  121. {
  122. $isUnion = ($type->getAttributes() & CType::ATTR_UNION) === CType::ATTR_UNION;
  123. $result = [];
  124. foreach ($type->getStructFieldNames() as $name) {
  125. $field = $type->getStructFieldType($name);
  126. // Retrieving the value of a field from a union containing
  127. // a pointer is not a safe operation, because may contain
  128. // incorrect data.
  129. $isUnsafe = $isUnion && CType::TYPE_POINTER === $field->getKind();
  130. if ($isUnsafe) {
  131. $result[Caster::PREFIX_VIRTUAL.$name.'?'] = $field;
  132. } elseif (null === $data) {
  133. $result[Caster::PREFIX_VIRTUAL.$name] = $field;
  134. } else {
  135. $fieldName = $data->{$name} instanceof CData ? '' : $field->getName().' ';
  136. $result[Caster::PREFIX_VIRTUAL.$fieldName.$name] = $data->{$name};
  137. }
  138. }
  139. return $result;
  140. }
  141. }