diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 63a7ffc2ff..07466466f3 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -189,6 +189,7 @@ class NodeScopeResolver private const LOOP_SCOPE_ITERATIONS = 3; private const GENERALIZE_AFTER_ITERATION = 1; private const FOREACH_UNROLL_LIMIT = 16; + private const FOREACH_UNROLL_NESTED_LIMIT = 16; /** @var array filePath(string) => bool(true) */ private array $analysedFiles = []; @@ -3896,6 +3897,9 @@ private function tryProcessUnrolledConstantArrayForeach( if ($totalKeys === 0 || $totalKeys > self::FOREACH_UNROLL_LIMIT) { return null; } + if ($context->getForeachUnrollFactor() * $totalKeys > self::FOREACH_UNROLL_NESTED_LIMIT) { + return null; + } $nativeIterateeType = $originalScope->getNativeType($stmt->expr); $nativeConstantArrays = $nativeIterateeType->getConstantArrays(); @@ -3908,6 +3912,8 @@ private function tryProcessUnrolledConstantArrayForeach( $allChainScopes = []; $allBreakScopes = []; + $bodyContext = $context->enterUnrolledForeach($totalKeys); + foreach ($constantArrays as $arrayIndex => $constantArray) { $keyTypes = $constantArray->getKeyTypes(); $valueTypes = $constantArray->getValueTypes(); @@ -3971,7 +3977,7 @@ private function tryProcessUnrolledConstantArrayForeach( $iterScope, $iterStorage, new NoopNodeCallback(), - $context, + $bodyContext, )->filterOutLoopExitPoints(); $iterEndScope = $bodyResult->getScope(); diff --git a/src/Analyser/StatementContext.php b/src/Analyser/StatementContext.php index 5fd5381601..fb2893b584 100644 --- a/src/Analyser/StatementContext.php +++ b/src/Analyser/StatementContext.php @@ -15,6 +15,7 @@ final class StatementContext private function __construct( private bool $isTopLevel, + private int $foreachUnrollFactor = 1, ) { } @@ -40,13 +41,23 @@ public function isTopLevel(): bool return $this->isTopLevel; } + public function getForeachUnrollFactor(): int + { + return $this->foreachUnrollFactor; + } + public function enterDeep(): self { if ($this->isTopLevel) { - return self::createDeep(); + return new self(false, $this->foreachUnrollFactor); } return $this; } + public function enterUnrolledForeach(int $totalKeys): self + { + return new self($this->isTopLevel, $this->foreachUnrollFactor * $totalKeys); + } + } diff --git a/tests/bench/data/bug-14590.php b/tests/bench/data/bug-14590.php new file mode 100644 index 0000000000..7d508361e2 --- /dev/null +++ b/tests/bench/data/bug-14590.php @@ -0,0 +1,27 @@ +