Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 1 | <?php |
| 2 | |
| 3 | /* |
| 4 | * This file is part of the Symfony package. |
| 5 | * |
| 6 | * (c) Fabien Potencier <fabien@symfony.com> |
| 7 | * |
| 8 | * For the full copyright and license information, please view the LICENSE |
| 9 | * file that was distributed with this source code. |
| 10 | */ |
| 11 | |
| 12 | namespace Symfony\Component\VarDumper\Cloner; |
| 13 | |
| 14 | /** |
| 15 | * @author Nicolas Grekas <p@tchwork.com> |
| 16 | */ |
| 17 | class VarCloner extends AbstractCloner |
| 18 | { |
Matthias Andreas Benkard | 1ba5381 | 2022-12-27 17:32:58 +0100 | [diff] [blame^] | 19 | private static string $gid; |
| 20 | private static array $arrayCache = []; |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 21 | |
| 22 | /** |
| 23 | * {@inheritdoc} |
| 24 | */ |
Matthias Andreas Benkard | 1ba5381 | 2022-12-27 17:32:58 +0100 | [diff] [blame^] | 25 | protected function doClone(mixed $var): array |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 26 | { |
| 27 | $len = 1; // Length of $queue |
| 28 | $pos = 0; // Number of cloned items past the minimum depth |
| 29 | $refsCounter = 0; // Hard references counter |
Matthias Andreas Benkard | 1ba5381 | 2022-12-27 17:32:58 +0100 | [diff] [blame^] | 30 | $queue = [[$var]]; // This breadth-first queue is the return value |
| 31 | $hardRefs = []; // Map of original zval ids to stub objects |
| 32 | $objRefs = []; // Map of original object handles to their stub object counterpart |
| 33 | $objects = []; // Keep a ref to objects to ensure their handle cannot be reused while cloning |
| 34 | $resRefs = []; // Map of original resource handles to their stub object counterpart |
| 35 | $values = []; // Map of stub objects' ids to original values |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 36 | $maxItems = $this->maxItems; |
| 37 | $maxString = $this->maxString; |
| 38 | $minDepth = $this->minDepth; |
| 39 | $currentDepth = 0; // Current tree depth |
| 40 | $currentDepthFinalIndex = 0; // Final $queue index for current tree depth |
| 41 | $minimumDepthReached = 0 === $minDepth; // Becomes true when minimum tree depth has been reached |
| 42 | $cookie = (object) []; // Unique object used to detect hard references |
| 43 | $a = null; // Array cast for nested structures |
| 44 | $stub = null; // Stub capturing the main properties of an original item value |
| 45 | // or null if the original value is used directly |
| 46 | |
Matthias Andreas Benkard | 1ba5381 | 2022-12-27 17:32:58 +0100 | [diff] [blame^] | 47 | $gid = self::$gid ??= md5(random_bytes(6)); // Unique string used to detect the special $GLOBALS variable |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 48 | $arrayStub = new Stub(); |
| 49 | $arrayStub->type = Stub::TYPE_ARRAY; |
| 50 | $fromObjCast = false; |
| 51 | |
| 52 | for ($i = 0; $i < $len; ++$i) { |
| 53 | // Detect when we move on to the next tree depth |
| 54 | if ($i > $currentDepthFinalIndex) { |
| 55 | ++$currentDepth; |
| 56 | $currentDepthFinalIndex = $len - 1; |
| 57 | if ($currentDepth >= $minDepth) { |
| 58 | $minimumDepthReached = true; |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | $refs = $vals = $queue[$i]; |
| 63 | foreach ($vals as $k => $v) { |
| 64 | // $v is the original value or a stub object in case of hard references |
| 65 | |
Matthias Andreas Benkard | 1ba5381 | 2022-12-27 17:32:58 +0100 | [diff] [blame^] | 66 | $zvalRef = ($r = \ReflectionReference::fromArrayElement($vals, $k)) ? $r->getId() : null; |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 67 | |
Matthias Andreas Benkard | 1ba5381 | 2022-12-27 17:32:58 +0100 | [diff] [blame^] | 68 | if ($zvalRef) { |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 69 | $vals[$k] = &$stub; // Break hard references to make $queue completely |
| 70 | unset($stub); // independent from the original structure |
Matthias Andreas Benkard | 1ba5381 | 2022-12-27 17:32:58 +0100 | [diff] [blame^] | 71 | if (null !== $vals[$k] = $hardRefs[$zvalRef] ?? null) { |
| 72 | $v = $vals[$k]; |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 73 | if ($v->value instanceof Stub && (Stub::TYPE_OBJECT === $v->value->type || Stub::TYPE_RESOURCE === $v->value->type)) { |
| 74 | ++$v->value->refCount; |
| 75 | } |
| 76 | ++$v->refCount; |
| 77 | continue; |
| 78 | } |
Matthias Andreas Benkard | 1ba5381 | 2022-12-27 17:32:58 +0100 | [diff] [blame^] | 79 | $vals[$k] = new Stub(); |
| 80 | $vals[$k]->value = $v; |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 81 | $vals[$k]->handle = ++$refsCounter; |
Matthias Andreas Benkard | 1ba5381 | 2022-12-27 17:32:58 +0100 | [diff] [blame^] | 82 | $hardRefs[$zvalRef] = $vals[$k]; |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 83 | } |
Matthias Andreas Benkard | 1ba5381 | 2022-12-27 17:32:58 +0100 | [diff] [blame^] | 84 | // Create $stub when the original value $v cannot be used directly |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 85 | // If $v is a nested structure, put that structure in array $a |
| 86 | switch (true) { |
| 87 | case null === $v: |
| 88 | case \is_bool($v): |
| 89 | case \is_int($v): |
| 90 | case \is_float($v): |
| 91 | continue 2; |
| 92 | case \is_string($v): |
| 93 | if ('' === $v) { |
| 94 | continue 2; |
| 95 | } |
| 96 | if (!preg_match('//u', $v)) { |
| 97 | $stub = new Stub(); |
| 98 | $stub->type = Stub::TYPE_STRING; |
| 99 | $stub->class = Stub::STRING_BINARY; |
| 100 | if (0 <= $maxString && 0 < $cut = \strlen($v) - $maxString) { |
| 101 | $stub->cut = $cut; |
| 102 | $stub->value = substr($v, 0, -$cut); |
| 103 | } else { |
| 104 | $stub->value = $v; |
| 105 | } |
| 106 | } elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < $cut = mb_strlen($v, 'UTF-8') - $maxString) { |
| 107 | $stub = new Stub(); |
| 108 | $stub->type = Stub::TYPE_STRING; |
| 109 | $stub->class = Stub::STRING_UTF8; |
| 110 | $stub->cut = $cut; |
| 111 | $stub->value = mb_substr($v, 0, $maxString, 'UTF-8'); |
| 112 | } else { |
| 113 | continue 2; |
| 114 | } |
| 115 | $a = null; |
| 116 | break; |
| 117 | |
| 118 | case \is_array($v): |
| 119 | if (!$v) { |
| 120 | continue 2; |
| 121 | } |
| 122 | $stub = $arrayStub; |
Matthias Andreas Benkard | 1ba5381 | 2022-12-27 17:32:58 +0100 | [diff] [blame^] | 123 | |
| 124 | if (\PHP_VERSION_ID >= 80100) { |
| 125 | $stub->class = array_is_list($v) ? Stub::ARRAY_INDEXED : Stub::ARRAY_ASSOC; |
| 126 | $a = $v; |
| 127 | break; |
| 128 | } |
| 129 | |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 130 | $stub->class = Stub::ARRAY_INDEXED; |
| 131 | |
| 132 | $j = -1; |
| 133 | foreach ($v as $gk => $gv) { |
| 134 | if ($gk !== ++$j) { |
| 135 | $stub->class = Stub::ARRAY_ASSOC; |
Matthias Andreas Benkard | 1ba5381 | 2022-12-27 17:32:58 +0100 | [diff] [blame^] | 136 | $a = $v; |
| 137 | $a[$gid] = true; |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 138 | break; |
| 139 | } |
| 140 | } |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 141 | |
Matthias Andreas Benkard | 1ba5381 | 2022-12-27 17:32:58 +0100 | [diff] [blame^] | 142 | // Copies of $GLOBALS have very strange behavior, |
| 143 | // let's detect them with some black magic |
| 144 | if (isset($v[$gid])) { |
| 145 | unset($v[$gid]); |
| 146 | $a = []; |
| 147 | foreach ($v as $gk => &$gv) { |
| 148 | if ($v === $gv && !isset($hardRefs[\ReflectionReference::fromArrayElement($v, $gk)->getId()])) { |
| 149 | unset($v); |
| 150 | $v = new Stub(); |
| 151 | $v->value = [$v->cut = \count($gv), Stub::TYPE_ARRAY => 0]; |
| 152 | $v->handle = -1; |
| 153 | $gv = &$a[$gk]; |
| 154 | $hardRefs[\ReflectionReference::fromArrayElement($a, $gk)->getId()] = &$gv; |
| 155 | $gv = $v; |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 156 | } |
Matthias Andreas Benkard | 1ba5381 | 2022-12-27 17:32:58 +0100 | [diff] [blame^] | 157 | |
| 158 | $a[$gk] = &$gv; |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 159 | } |
Matthias Andreas Benkard | 1ba5381 | 2022-12-27 17:32:58 +0100 | [diff] [blame^] | 160 | unset($gv); |
| 161 | } else { |
| 162 | $a = $v; |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 163 | } |
| 164 | break; |
| 165 | |
| 166 | case \is_object($v): |
| 167 | if (empty($objRefs[$h = spl_object_id($v)])) { |
| 168 | $stub = new Stub(); |
| 169 | $stub->type = Stub::TYPE_OBJECT; |
| 170 | $stub->class = \get_class($v); |
| 171 | $stub->value = $v; |
| 172 | $stub->handle = $h; |
| 173 | $a = $this->castObject($stub, 0 < $i); |
| 174 | if ($v !== $stub->value) { |
| 175 | if (Stub::TYPE_OBJECT !== $stub->type || null === $stub->value) { |
| 176 | break; |
| 177 | } |
| 178 | $stub->handle = $h = spl_object_id($stub->value); |
| 179 | } |
| 180 | $stub->value = null; |
| 181 | if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { |
| 182 | $stub->cut = \count($a); |
| 183 | $a = null; |
| 184 | } |
| 185 | } |
| 186 | if (empty($objRefs[$h])) { |
| 187 | $objRefs[$h] = $stub; |
| 188 | $objects[] = $v; |
| 189 | } else { |
| 190 | $stub = $objRefs[$h]; |
| 191 | ++$stub->refCount; |
| 192 | $a = null; |
| 193 | } |
| 194 | break; |
| 195 | |
| 196 | default: // resource |
| 197 | if (empty($resRefs[$h = (int) $v])) { |
| 198 | $stub = new Stub(); |
| 199 | $stub->type = Stub::TYPE_RESOURCE; |
| 200 | if ('Unknown' === $stub->class = @get_resource_type($v)) { |
| 201 | $stub->class = 'Closed'; |
| 202 | } |
| 203 | $stub->value = $v; |
| 204 | $stub->handle = $h; |
| 205 | $a = $this->castResource($stub, 0 < $i); |
| 206 | $stub->value = null; |
| 207 | if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { |
| 208 | $stub->cut = \count($a); |
| 209 | $a = null; |
| 210 | } |
| 211 | } |
| 212 | if (empty($resRefs[$h])) { |
| 213 | $resRefs[$h] = $stub; |
| 214 | } else { |
| 215 | $stub = $resRefs[$h]; |
| 216 | ++$stub->refCount; |
| 217 | $a = null; |
| 218 | } |
| 219 | break; |
| 220 | } |
| 221 | |
| 222 | if ($a) { |
| 223 | if (!$minimumDepthReached || 0 > $maxItems) { |
| 224 | $queue[$len] = $a; |
| 225 | $stub->position = $len++; |
| 226 | } elseif ($pos < $maxItems) { |
| 227 | if ($maxItems < $pos += \count($a)) { |
| 228 | $a = \array_slice($a, 0, $maxItems - $pos, true); |
| 229 | if ($stub->cut >= 0) { |
| 230 | $stub->cut += $pos - $maxItems; |
| 231 | } |
| 232 | } |
| 233 | $queue[$len] = $a; |
| 234 | $stub->position = $len++; |
| 235 | } elseif ($stub->cut >= 0) { |
| 236 | $stub->cut += \count($a); |
| 237 | $stub->position = 0; |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | if ($arrayStub === $stub) { |
| 242 | if ($arrayStub->cut) { |
| 243 | $stub = [$arrayStub->cut, $arrayStub->class => $arrayStub->position]; |
| 244 | $arrayStub->cut = 0; |
| 245 | } elseif (isset(self::$arrayCache[$arrayStub->class][$arrayStub->position])) { |
| 246 | $stub = self::$arrayCache[$arrayStub->class][$arrayStub->position]; |
| 247 | } else { |
| 248 | self::$arrayCache[$arrayStub->class][$arrayStub->position] = $stub = [$arrayStub->class => $arrayStub->position]; |
| 249 | } |
| 250 | } |
| 251 | |
Matthias Andreas Benkard | 1ba5381 | 2022-12-27 17:32:58 +0100 | [diff] [blame^] | 252 | if (!$zvalRef) { |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 253 | $vals[$k] = $stub; |
Matthias Andreas Benkard | 1ba5381 | 2022-12-27 17:32:58 +0100 | [diff] [blame^] | 254 | } else { |
| 255 | $hardRefs[$zvalRef]->value = $stub; |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 256 | } |
| 257 | } |
| 258 | |
| 259 | if ($fromObjCast) { |
| 260 | $fromObjCast = false; |
| 261 | $refs = $vals; |
| 262 | $vals = []; |
| 263 | $j = -1; |
| 264 | foreach ($queue[$i] as $k => $v) { |
| 265 | foreach ([$k => true] as $gk => $gv) { |
| 266 | } |
| 267 | if ($gk !== $k) { |
| 268 | $vals = (object) $vals; |
| 269 | $vals->{$k} = $refs[++$j]; |
| 270 | $vals = (array) $vals; |
| 271 | } else { |
| 272 | $vals[$k] = $refs[++$j]; |
| 273 | } |
| 274 | } |
| 275 | } |
| 276 | |
| 277 | $queue[$i] = $vals; |
| 278 | } |
| 279 | |
| 280 | foreach ($values as $h => $v) { |
| 281 | $hardRefs[$h] = $v; |
| 282 | } |
| 283 | |
| 284 | return $queue; |
| 285 | } |
| 286 | } |