No Description

VarCloner.php 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  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\Cloner;
  11. /**
  12. * @author Nicolas Grekas <p@tchwork.com>
  13. */
  14. class VarCloner extends AbstractCloner
  15. {
  16. private static $hashMask = 0;
  17. private static $hashOffset = 0;
  18. /**
  19. * {@inheritdoc}
  20. */
  21. protected function doClone($var)
  22. {
  23. $useExt = $this->useExt;
  24. $i = 0; // Current iteration position in $queue
  25. $len = 1; // Length of $queue
  26. $pos = 0; // Number of cloned items past the first level
  27. $refs = 0; // Hard references counter
  28. $queue = array(array($var)); // This breadth-first queue is the return value
  29. $arrayRefs = array(); // Map of queue indexes to stub array objects
  30. $hardRefs = array(); // Map of original zval hashes to stub objects
  31. $objRefs = array(); // Map of original object handles to their stub object couterpart
  32. $resRefs = array(); // Map of original resource handles to their stub object couterpart
  33. $values = array(); // Map of stub objects' hashes to original values
  34. $maxItems = $this->maxItems;
  35. $maxString = $this->maxString;
  36. $cookie = (object) array(); // Unique object used to detect hard references
  37. $a = null; // Array cast for nested structures
  38. $stub = null; // Stub capturing the main properties of an original item value,
  39. // or null if the original value is used directly
  40. $zval = array( // Main properties of the current value
  41. 'type' => null,
  42. 'zval_isref' => null,
  43. 'zval_hash' => null,
  44. 'array_count' => null,
  45. 'object_class' => null,
  46. 'object_handle' => null,
  47. 'resource_type' => null,
  48. );
  49. if (!self::$hashMask) {
  50. self::initHashMask();
  51. }
  52. $hashMask = self::$hashMask;
  53. $hashOffset = self::$hashOffset;
  54. for ($i = 0; $i < $len; ++$i) {
  55. $indexed = true; // Whether the currently iterated array is numerically indexed or not
  56. $j = -1; // Position in the currently iterated array
  57. $step = $queue[$i]; // Copy of the currently iterated array used for hard references detection
  58. foreach ($step as $k => $v) {
  59. // $k is the original key
  60. // $v is the original value or a stub object in case of hard references
  61. if ($indexed && $k !== ++$j) {
  62. $indexed = false;
  63. }
  64. if ($useExt) {
  65. $zval = symfony_zval_info($k, $step);
  66. } else {
  67. $step[$k] = $cookie;
  68. if ($zval['zval_isref'] = $queue[$i][$k] === $cookie) {
  69. $zval['zval_hash'] = $v instanceof Stub ? spl_object_hash($v) : null;
  70. }
  71. $zval['type'] = gettype($v);
  72. }
  73. if ($zval['zval_isref']) {
  74. $queue[$i][$k] =& $stub; // Break hard references to make $queue completely
  75. unset($stub); // independent from the original structure
  76. if (isset($hardRefs[$zval['zval_hash']])) {
  77. $queue[$i][$k] = $useExt ? ($v = $hardRefs[$zval['zval_hash']]) : ($step[$k] = $v);
  78. if ($v->value instanceof Stub && (Stub::TYPE_OBJECT === $v->value->type || Stub::TYPE_RESOURCE === $v->value->type)) {
  79. ++$v->value->refCount;
  80. }
  81. ++$v->refCount;
  82. continue;
  83. }
  84. }
  85. // Create $stub when the original value $v can not be used directly
  86. // If $v is a nested structure, put that structure in array $a
  87. switch ($zval['type']) {
  88. case 'string':
  89. if (isset($v[0]) && !preg_match('//u', $v)) {
  90. $stub = new Stub();
  91. $stub->type = Stub::TYPE_STRING;
  92. $stub->class = Stub::STRING_BINARY;
  93. if (0 <= $maxString && 0 < $cut = strlen($v) - $maxString) {
  94. $stub->cut = $cut;
  95. $stub->value = substr($v, 0, -$cut);
  96. } else {
  97. $stub->value = $v;
  98. }
  99. } elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < $cut = iconv_strlen($v, 'UTF-8') - $maxString) {
  100. $stub = new Stub();
  101. $stub->type = Stub::TYPE_STRING;
  102. $stub->class = Stub::STRING_UTF8;
  103. $stub->cut = $cut;
  104. $stub->value = iconv_substr($v, 0, $maxString, 'UTF-8');
  105. }
  106. break;
  107. case 'integer':
  108. break;
  109. case 'array':
  110. if ($v) {
  111. $stub = $arrayRefs[$len] = new Stub();
  112. $stub->type = Stub::TYPE_ARRAY;
  113. $stub->class = Stub::ARRAY_ASSOC;
  114. $stub->value = $zval['array_count'] ?: count($v);
  115. $a = $v;
  116. $a[] = null;
  117. $h = count($v);
  118. array_pop($a);
  119. // Happens with copies of $GLOBALS
  120. if ($h !== $stub->value) {
  121. $a = array();
  122. foreach ($v as $gk => &$gv) {
  123. $a[$gk] =& $gv;
  124. }
  125. }
  126. }
  127. break;
  128. case 'object':
  129. if (empty($objRefs[$h = $zval['object_handle'] ?: ($hashMask ^ hexdec(substr(spl_object_hash($v), $hashOffset, PHP_INT_SIZE)))])) {
  130. $stub = new Stub();
  131. $stub->type = Stub::TYPE_OBJECT;
  132. $stub->class = $zval['object_class'] ?: get_class($v);
  133. $stub->value = $v;
  134. $stub->handle = $h;
  135. $a = $this->castObject($stub, 0 < $i);
  136. if ($v !== $stub->value) {
  137. if (Stub::TYPE_OBJECT !== $stub->type) {
  138. break;
  139. }
  140. if ($useExt) {
  141. $zval['type'] = $stub->value;
  142. $zval = symfony_zval_info('type', $zval);
  143. $h = $zval['object_handle'];
  144. } else {
  145. $h = $hashMask ^ hexdec(substr(spl_object_hash($stub->value), $hashOffset, PHP_INT_SIZE));
  146. }
  147. $stub->handle = $h;
  148. }
  149. $stub->value = null;
  150. if (0 <= $maxItems && $maxItems <= $pos) {
  151. $stub->cut = count($a);
  152. $a = null;
  153. }
  154. }
  155. if (empty($objRefs[$h])) {
  156. $objRefs[$h] = $stub;
  157. } else {
  158. $stub = $objRefs[$h];
  159. ++$stub->refCount;
  160. $a = null;
  161. }
  162. break;
  163. case 'resource':
  164. case 'unknown type':
  165. if (empty($resRefs[$h = (int) $v])) {
  166. $stub = new Stub();
  167. $stub->type = Stub::TYPE_RESOURCE;
  168. $stub->class = $zval['resource_type'] ?: get_resource_type($v);
  169. $stub->value = $v;
  170. $stub->handle = $h;
  171. $a = $this->castResource($stub, 0 < $i);
  172. $stub->value = null;
  173. if (0 <= $maxItems && $maxItems <= $pos) {
  174. $stub->cut = count($a);
  175. $a = null;
  176. }
  177. }
  178. if (empty($resRefs[$h])) {
  179. $resRefs[$h] = $stub;
  180. } else {
  181. $stub = $resRefs[$h];
  182. ++$stub->refCount;
  183. $a = null;
  184. }
  185. break;
  186. }
  187. if (isset($stub)) {
  188. if ($zval['zval_isref']) {
  189. if ($useExt) {
  190. $queue[$i][$k] = $hardRefs[$zval['zval_hash']] = $v = new Stub();
  191. $v->value = $stub;
  192. } else {
  193. $step[$k] = new Stub();
  194. $step[$k]->value = $stub;
  195. $h = spl_object_hash($step[$k]);
  196. $queue[$i][$k] = $hardRefs[$h] =& $step[$k];
  197. $values[$h] = $v;
  198. }
  199. $queue[$i][$k]->handle = ++$refs;
  200. } else {
  201. $queue[$i][$k] = $stub;
  202. }
  203. if ($a) {
  204. if ($i && 0 <= $maxItems) {
  205. $k = count($a);
  206. if ($pos < $maxItems) {
  207. if ($maxItems < $pos += $k) {
  208. $a = array_slice($a, 0, $maxItems - $pos);
  209. if ($stub->cut >= 0) {
  210. $stub->cut += $pos - $maxItems;
  211. }
  212. }
  213. } else {
  214. if ($stub->cut >= 0) {
  215. $stub->cut += $k;
  216. }
  217. $stub = $a = null;
  218. unset($arrayRefs[$len]);
  219. continue;
  220. }
  221. }
  222. $queue[$len] = $a;
  223. $stub->position = $len++;
  224. }
  225. $stub = $a = null;
  226. } elseif ($zval['zval_isref']) {
  227. if ($useExt) {
  228. $queue[$i][$k] = $hardRefs[$zval['zval_hash']] = new Stub();
  229. $queue[$i][$k]->value = $v;
  230. } else {
  231. $step[$k] = $queue[$i][$k] = new Stub();
  232. $step[$k]->value = $v;
  233. $h = spl_object_hash($step[$k]);
  234. $hardRefs[$h] =& $step[$k];
  235. $values[$h] = $v;
  236. }
  237. $queue[$i][$k]->handle = ++$refs;
  238. }
  239. }
  240. if (isset($arrayRefs[$i])) {
  241. if ($indexed) {
  242. $arrayRefs[$i]->class = Stub::ARRAY_INDEXED;
  243. }
  244. unset($arrayRefs[$i]);
  245. }
  246. }
  247. foreach ($values as $h => $v) {
  248. $hardRefs[$h] = $v;
  249. }
  250. return $queue;
  251. }
  252. private static function initHashMask()
  253. {
  254. $obj = (object) array();
  255. self::$hashOffset = 16 - PHP_INT_SIZE;
  256. self::$hashMask = -1;
  257. if (defined('HHVM_VERSION')) {
  258. self::$hashOffset += 16;
  259. } else {
  260. // check if we are nested in an output buffering handler to prevent a fatal error with ob_start() below
  261. $obFuncs = array('ob_clean', 'ob_end_clean', 'ob_flush', 'ob_end_flush', 'ob_get_contents', 'ob_get_flush');
  262. foreach (debug_backtrace(PHP_VERSION_ID >= 50400 ? DEBUG_BACKTRACE_IGNORE_ARGS : false) as $frame) {
  263. if (isset($frame['function'][0]) && !isset($frame['class']) && 'o' === $frame['function'][0] && in_array($frame['function'], $obFuncs)) {
  264. $frame['line'] = 0;
  265. break;
  266. }
  267. }
  268. if (!empty($frame['line'])) {
  269. ob_start();
  270. debug_zval_dump($obj);
  271. self::$hashMask = substr(ob_get_clean(), 17);
  272. }
  273. }
  274. self::$hashMask ^= hexdec(substr(spl_object_hash($obj), self::$hashOffset, PHP_INT_SIZE));
  275. }
  276. }