From b2b621493a6073647b202ccaf6f18dad80c026c3 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Mon, 11 Nov 2024 12:48:50 +0100 Subject: [PATCH] Fix `for` endless loop detection --- src/Analyser/NodeScopeResolver.php | 5 ++- .../PHPStan/Analyser/StatementResultTest.php | 8 ++++ .../DeadCode/UnreachableStatementRuleTest.php | 6 +++ .../PHPStan/Rules/DeadCode/data/bug-11992.php | 37 +++++++++++++++++++ 4 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Rules/DeadCode/data/bug-11992.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 1a6a069d28..5c4d86526e 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1369,7 +1369,10 @@ private function processStmtNode( } $bodyScope = $bodyScope->mergeWith($initScope); + + $alwaysIterates = TrinaryLogic::createFromBoolean($context->isTopLevel()); if ($lastCondExpr !== null) { + $alwaysIterates = $alwaysIterates->and($bodyScope->getType($lastCondExpr)->toBoolean()->isTrue()); $bodyScope = $this->processExprNode($stmt, $lastCondExpr, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope(); } @@ -1385,9 +1388,7 @@ private function processStmtNode( } $finalScope = $finalScope->generalizeWith($loopScope); - $alwaysIterates = TrinaryLogic::createFromBoolean($context->isTopLevel()); if ($lastCondExpr !== null) { - $alwaysIterates = $alwaysIterates->and($finalScope->getType($lastCondExpr)->toBoolean()->isTrue()); $finalScope = $finalScope->filterByFalseyValue($lastCondExpr); } diff --git a/tests/PHPStan/Analyser/StatementResultTest.php b/tests/PHPStan/Analyser/StatementResultTest.php index b86de8617e..04ca04cb26 100644 --- a/tests/PHPStan/Analyser/StatementResultTest.php +++ b/tests/PHPStan/Analyser/StatementResultTest.php @@ -297,6 +297,14 @@ public function dataIsAlwaysTerminating(): array 'for (; "a", "";) { }', false, ], + [ + 'for ($c = (0x80 | 0x40); $c & 0x80; $c = $c << 1) { }', + false, + ], + [ + 'for ($i = 0; $i < 10; $i++) { $i = 5; }', + true, + ], [ 'do { } while (doFoo());', false, diff --git a/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php index 191ba39134..da076db1c7 100644 --- a/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php @@ -224,4 +224,10 @@ public function testBug11179(): void $this->analyse([__DIR__ . '/data/bug-11179.php'], []); } + public function testBug11992(): void + { + $this->treatPhpDocTypesAsCertain = false; + $this->analyse([__DIR__ . '/data/bug-11992.php'], []); + } + } diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-11992.php b/tests/PHPStan/Rules/DeadCode/data/bug-11992.php new file mode 100644 index 0000000000..e6c35c967d --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/bug-11992.php @@ -0,0 +1,37 @@ +valid(); + $it->next() + ) { + printf("name: %s\n", $it->getFilename()); + } + printf("done\n"); +} + +exampleA(); +exampleB(); +exampleC();