VarCloner.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  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. $len = 1; // Length of $queue
  25. $pos = 0; // Number of cloned items past the first level
  26. $refsCounter = 0; // Hard references counter
  27. $queue = array(array($var)); // This breadth-first queue is the return value
  28. $arrayRefs = array(); // Map of queue indexes to stub array objects
  29. $hardRefs = array(); // Map of original zval hashes to stub objects
  30. $objRefs = array(); // Map of original object handles to their stub object couterpart
  31. $resRefs = array(); // Map of original resource handles to their stub object couterpart
  32. $values = array(); // Map of stub objects' hashes to original values
  33. $maxItems = $this->maxItems;
  34. $maxString = $this->maxString;
  35. $cookie = (object) array(); // Unique object used to detect hard references
  36. $gid = uniqid(mt_rand(), true); // Unique string used to detect the special $GLOBALS variable
  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. $fromObjCast = array_keys($queue[$i]);
  58. $fromObjCast = array_keys(array_flip($fromObjCast)) !== $fromObjCast;
  59. $refs = $vals = $fromObjCast ? array_values($queue[$i]) : $queue[$i];
  60. foreach ($queue[$i] as $k => $v) {
  61. // $k is the original key
  62. // $v is the original value or a stub object in case of hard references
  63. if ($k !== ++$j) {
  64. $indexed = false;
  65. }
  66. if ($fromObjCast) {
  67. $k = $j;
  68. }
  69. if ($useExt) {
  70. $zval = symfony_zval_info($k, $refs);
  71. } else {
  72. $refs[$k] = $cookie;
  73. if ($zval['zval_isref'] = $vals[$k] === $cookie) {
  74. $zval['zval_hash'] = $v instanceof Stub ? spl_object_hash($v) : null;
  75. }
  76. $zval['type'] = gettype($v);
  77. }
  78. if ($zval['zval_isref']) {
  79. $vals[$k] = &$stub; // Break hard references to make $queue completely
  80. unset($stub); // independent from the original structure
  81. if (isset($hardRefs[$zval['zval_hash']])) {
  82. $vals[$k] = $useExt ? ($v = $hardRefs[$zval['zval_hash']]) : ($refs[$k] = $v);
  83. if ($v->value instanceof Stub && (Stub::TYPE_OBJECT === $v->value->type || Stub::TYPE_RESOURCE === $v->value->type)) {
  84. ++$v->value->refCount;
  85. }
  86. ++$v->refCount;
  87. continue;
  88. }
  89. }
  90. // Create $stub when the original value $v can not be used directly
  91. // If $v is a nested structure, put that structure in array $a
  92. switch ($zval['type']) {
  93. case 'string':
  94. if (isset($v[0]) && !preg_match('//u', $v)) {
  95. $stub = new Stub();
  96. $stub->type = Stub::TYPE_STRING;
  97. $stub->class = Stub::STRING_BINARY;
  98. if (0 <= $maxString && 0 < $cut = strlen($v) - $maxString) {
  99. $stub->cut = $cut;
  100. $stub->value = substr($v, 0, -$cut);
  101. } else {
  102. $stub->value = $v;
  103. }
  104. } elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < $cut = mb_strlen($v, 'UTF-8') - $maxString) {
  105. $stub = new Stub();
  106. $stub->type = Stub::TYPE_STRING;
  107. $stub->class = Stub::STRING_UTF8;
  108. $stub->cut = $cut;
  109. $stub->value = mb_substr($v, 0, $maxString, 'UTF-8');
  110. }
  111. break;
  112. case 'integer':
  113. break;
  114. case 'array':
  115. if ($v) {
  116. $stub = $arrayRefs[$len] = new Stub();
  117. $stub->type = Stub::TYPE_ARRAY;
  118. $stub->class = Stub::ARRAY_ASSOC;
  119. // Copies of $GLOBALS have very strange behavior,
  120. // let's detect them with some black magic
  121. $a = $v;
  122. $a[$gid] = true;
  123. // Happens with copies of $GLOBALS
  124. if (isset($v[$gid])) {
  125. unset($v[$gid]);
  126. $a = array();
  127. foreach ($v as $gk => &$gv) {
  128. $a[$gk] = &$gv;
  129. }
  130. } else {
  131. $a = $v;
  132. }
  133. $stub->value = $zval['array_count'] ?: count($a);
  134. }
  135. break;
  136. case 'object':
  137. if (empty($objRefs[$h = $zval['object_handle'] ?: ($hashMask ^ hexdec(substr(spl_object_hash($v), $hashOffset, PHP_INT_SIZE)))])) {
  138. $stub = new Stub();
  139. $stub->type = Stub::TYPE_OBJECT;
  140. $stub->class = $zval['object_class'] ?: get_class($v);
  141. $stub->value = $v;
  142. $stub->handle = $h;
  143. $a = $this->castObject($stub, 0 < $i);
  144. if ($v !== $stub->value) {
  145. if (Stub::TYPE_OBJECT !== $stub->type || null === $stub->value) {
  146. break;
  147. }
  148. if ($useExt) {
  149. $zval['type'] = $stub->value;
  150. $zval = symfony_zval_info('type', $zval);
  151. $h = $zval['object_handle'];
  152. } else {
  153. $h = $hashMask ^ hexdec(substr(spl_object_hash($stub->value), $hashOffset, PHP_INT_SIZE));
  154. }
  155. $stub->handle = $h;
  156. }
  157. $stub->value = null;
  158. if (0 <= $maxItems && $maxItems <= $pos) {
  159. $stub->cut = count($a);
  160. $a = null;
  161. }
  162. }
  163. if (empty($objRefs[$h])) {
  164. $objRefs[$h] = $stub;
  165. } else {
  166. $stub = $objRefs[$h];
  167. ++$stub->refCount;
  168. $a = null;
  169. }
  170. break;
  171. case 'resource':
  172. case 'unknown type':
  173. case 'resource (closed)':
  174. if (empty($resRefs[$h = (int) $v])) {
  175. $stub = new Stub();
  176. $stub->type = Stub::TYPE_RESOURCE;
  177. if ('Unknown' === $stub->class = $zval['resource_type'] ?: @get_resource_type($v)) {
  178. $stub->class = 'Closed';
  179. }
  180. $stub->value = $v;
  181. $stub->handle = $h;
  182. $a = $this->castResource($stub, 0 < $i);
  183. $stub->value = null;
  184. if (0 <= $maxItems && $maxItems <= $pos) {
  185. $stub->cut = count($a);
  186. $a = null;
  187. }
  188. }
  189. if (empty($resRefs[$h])) {
  190. $resRefs[$h] = $stub;
  191. } else {
  192. $stub = $resRefs[$h];
  193. ++$stub->refCount;
  194. $a = null;
  195. }
  196. break;
  197. }
  198. if (isset($stub)) {
  199. if ($zval['zval_isref']) {
  200. if ($useExt) {
  201. $vals[$k] = $hardRefs[$zval['zval_hash']] = $v = new Stub();
  202. $v->value = $stub;
  203. } else {
  204. $refs[$k] = new Stub();
  205. $refs[$k]->value = $stub;
  206. $h = spl_object_hash($refs[$k]);
  207. $vals[$k] = $hardRefs[$h] = &$refs[$k];
  208. $values[$h] = $v;
  209. }
  210. $vals[$k]->handle = ++$refsCounter;
  211. } else {
  212. $vals[$k] = $stub;
  213. }
  214. if ($a) {
  215. if ($i && 0 <= $maxItems) {
  216. $k = count($a);
  217. if ($pos < $maxItems) {
  218. if ($maxItems < $pos += $k) {
  219. $a = array_slice($a, 0, $maxItems - $pos);
  220. if ($stub->cut >= 0) {
  221. $stub->cut += $pos - $maxItems;
  222. }
  223. }
  224. } else {
  225. if ($stub->cut >= 0) {
  226. $stub->cut += $k;
  227. }
  228. $stub = $a = null;
  229. unset($arrayRefs[$len]);
  230. continue;
  231. }
  232. }
  233. $queue[$len] = $a;
  234. $stub->position = $len++;
  235. }
  236. $stub = $a = null;
  237. } elseif ($zval['zval_isref']) {
  238. if ($useExt) {
  239. $vals[$k] = $hardRefs[$zval['zval_hash']] = new Stub();
  240. $vals[$k]->value = $v;
  241. } else {
  242. $refs[$k] = $vals[$k] = new Stub();
  243. $refs[$k]->value = $v;
  244. $h = spl_object_hash($refs[$k]);
  245. $hardRefs[$h] = &$refs[$k];
  246. $values[$h] = $v;
  247. }
  248. $vals[$k]->handle = ++$refsCounter;
  249. }
  250. }
  251. if ($fromObjCast) {
  252. $refs = $vals;
  253. $vals = array();
  254. $j = -1;
  255. foreach ($queue[$i] as $k => $v) {
  256. foreach (array($k => $v) as $a => $v) {
  257. }
  258. if ($a !== $k) {
  259. $vals = (object) $vals;
  260. $vals->{$k} = $refs[++$j];
  261. $vals = (array) $vals;
  262. } else {
  263. $vals[$k] = $refs[++$j];
  264. }
  265. }
  266. }
  267. $queue[$i] = $vals;
  268. if (isset($arrayRefs[$i])) {
  269. if ($indexed) {
  270. $arrayRefs[$i]->class = Stub::ARRAY_INDEXED;
  271. }
  272. unset($arrayRefs[$i]);
  273. }
  274. }
  275. foreach ($values as $h => $v) {
  276. $hardRefs[$h] = $v;
  277. }
  278. return $queue;
  279. }
  280. private static function initHashMask()
  281. {
  282. $obj = (object) array();
  283. self::$hashOffset = 16 - PHP_INT_SIZE;
  284. self::$hashMask = -1;
  285. if (defined('HHVM_VERSION')) {
  286. self::$hashOffset += 16;
  287. } else {
  288. // check if we are nested in an output buffering handler to prevent a fatal error with ob_start() below
  289. $obFuncs = array('ob_clean', 'ob_end_clean', 'ob_flush', 'ob_end_flush', 'ob_get_contents', 'ob_get_flush');
  290. foreach (debug_backtrace(\PHP_VERSION_ID >= 50400 ? DEBUG_BACKTRACE_IGNORE_ARGS : false) as $frame) {
  291. if (isset($frame['function'][0]) && !isset($frame['class']) && 'o' === $frame['function'][0] && in_array($frame['function'], $obFuncs)) {
  292. $frame['line'] = 0;
  293. break;
  294. }
  295. }
  296. if (!empty($frame['line'])) {
  297. ob_start();
  298. debug_zval_dump($obj);
  299. self::$hashMask = (int) substr(ob_get_clean(), 17);
  300. }
  301. }
  302. self::$hashMask ^= hexdec(substr(spl_object_hash($obj), self::$hashOffset, PHP_INT_SIZE));
  303. }
  304. }