Skip to content

Commit 5e78df3

Browse files
Fix infinite recursion in TypeTraverser with cycle detection
Add cycle detection to prevent infinite recursion when processing circular type dependencies, particularly with constructs like `is_callable(is_array(...))` that can cause TypeTraverser to loop indefinitely between mapInternal() and traverseInternal(). The fix tracks visited types using spl_object_hash() and returns the type as-is when a cycle is detected, preventing memory exhaustion and timeouts during static analysis. Fixes: Memory exhaustion with nested callable types
1 parent 76363f4 commit 5e78df3

File tree

1 file changed

+16
-1
lines changed

1 file changed

+16
-1
lines changed

src/Type/TypeTraverser.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ final class TypeTraverser
88
/** @var callable(Type $type, callable(Type): Type $traverse): Type */
99
private $cb;
1010

11+
/** @var array<string, bool> */
12+
private array $visitedTypes = [];
13+
1114
/**
1215
* Map a Type recursively
1316
*
@@ -49,7 +52,19 @@ private function __construct(callable $cb)
4952
/** @internal */
5053
public function mapInternal(Type $type): Type
5154
{
52-
return ($this->cb)($type, [$this, 'traverseInternal']);
55+
$typeHash = spl_object_hash($type);
56+
57+
// Prevent infinite recursion by tracking visited types
58+
if (isset($this->visitedTypes[$typeHash])) {
59+
// Return the type as-is if we've already processed it
60+
return $type;
61+
}
62+
63+
$this->visitedTypes[$typeHash] = true;
64+
$result = ($this->cb)($type, [$this, 'traverseInternal']);
65+
unset($this->visitedTypes[$typeHash]);
66+
67+
return $result;
5368
}
5469

5570
/** @internal */

0 commit comments

Comments
 (0)