VarCloner.php 13 KB

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