Skip to content

Commit ec87b76

Browse files
committed
Fix false positive undefined variable when breaking out of infinite loop
- In while(true) loops, the only way to exit is via break, so the post-loop scope should be constructed solely from break exit point scopes - Previously, break scopes were merged with the "normal exit" scope (filterByFalseyValue), which degraded variable certainty to "maybe" for variables only defined in the break path - New regression test in tests/PHPStan/Rules/Variables/data/bug-9023.php Closes phpstan/phpstan#9023
1 parent 106fc93 commit ec87b76

File tree

3 files changed

+49
-2
lines changed

3 files changed

+49
-2
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1517,8 +1517,16 @@ private function processStmtNode(
15171517
}
15181518

15191519
$breakExitPoints = $finalScopeResult->getExitPointsByType(Break_::class);
1520-
foreach ($breakExitPoints as $breakExitPoint) {
1521-
$finalScope = $finalScope->mergeWith($breakExitPoint->getScope());
1520+
if ($alwaysIterates && count($breakExitPoints) > 0) {
1521+
$breakScope = null;
1522+
foreach ($breakExitPoints as $breakExitPoint) {
1523+
$breakScope = $breakScope === null ? $breakExitPoint->getScope() : $breakScope->mergeWith($breakExitPoint->getScope());
1524+
}
1525+
$finalScope = $breakScope;
1526+
} else {
1527+
foreach ($breakExitPoints as $breakExitPoint) {
1528+
$finalScope = $finalScope->mergeWith($breakExitPoint->getScope());
1529+
}
15221530
}
15231531

15241532
$isIterableAtLeastOnce = $beforeCondBooleanType->isTrue()->yes();

tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,4 +1257,14 @@ public function testBug12944(): void
12571257
$this->analyse([__DIR__ . '/data/bug-12944.php'], []);
12581258
}
12591259

1260+
public function testBug9023(): void
1261+
{
1262+
$this->cliArgumentsVariablesRegistered = true;
1263+
$this->polluteScopeWithLoopInitialAssignments = false;
1264+
$this->checkMaybeUndefinedVariables = true;
1265+
$this->polluteScopeWithAlwaysIterableForeach = true;
1266+
1267+
$this->analyse([__DIR__ . '/data/bug-9023.php'], []);
1268+
}
1269+
12601270
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug9023;
4+
5+
use Exception;
6+
7+
function unreliableFunc(): string
8+
{
9+
if (random_int(1, 5) === 1) {
10+
return 'something';
11+
} else {
12+
throw new Exception('Just demonstrating');
13+
}
14+
}
15+
16+
function testWhileTrueBreakWithTryCatch(): void
17+
{
18+
while (true) {
19+
try {
20+
$defined = unreliableFunc();
21+
break;
22+
} catch (Exception $e) {
23+
sleep(10);
24+
continue;
25+
}
26+
}
27+
28+
echo $defined;
29+
}

0 commit comments

Comments
 (0)