Skip to content

Commit 4afa331

Browse files
github-actions[bot]VincentLanglet
authored andcommitted
Fix anonymous class constructor throw points leaking inner scope
- Throw points from anonymous class constructor body carried inner method scopes instead of the outer scope, causing variables defined before try blocks to be reported as "might not be defined" in finally blocks - Added replaceScope() method to InternalThrowPoint to allow scope replacement - New regression test in tests/PHPStan/Rules/Variables/data/bug-13920.php Closes phpstan/phpstan#13920
1 parent 5e40360 commit 4afa331

File tree

4 files changed

+42
-1
lines changed

4 files changed

+42
-1
lines changed

src/Analyser/ExprHandler/NewHandler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
190190
$constructorResult = $node;
191191
}, StatementContext::createTopLevel());
192192
if ($constructorResult !== null) {
193-
$throwPoints = array_map(static fn (ThrowPoint $point) => InternalThrowPoint::createFromPublic($point), $constructorResult->getStatementResult()->getThrowPoints());
193+
$throwPoints = array_map(static fn (ThrowPoint $point): InternalThrowPoint => InternalThrowPoint::createFromPublic($point)->replaceScope($scope), $constructorResult->getStatementResult()->getThrowPoints());
194194
$impurePoints = $constructorResult->getImpurePoints();
195195
}
196196
} else {

src/Analyser/InternalThrowPoint.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ public function canContainAnyThrowable(): bool
8888
return $this->canContainAnyThrowable;
8989
}
9090

91+
public function replaceScope(MutatingScope $scope): self
92+
{
93+
return new self($scope, $this->type, $this->node, $this->explicit, $this->canContainAnyThrowable);
94+
}
95+
9196
public function subtractCatchType(Type $catchType): self
9297
{
9398
return new self($this->scope, TypeCombinator::remove($this->type, $catchType), $this->node, $this->explicit, $this->canContainAnyThrowable);

tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1425,6 +1425,16 @@ public function testBug12373(): void
14251425
$this->analyse([__DIR__ . '/data/bug-12373.php'], []);
14261426
}
14271427

1428+
public function testBug13920(): void
1429+
{
1430+
$this->cliArgumentsVariablesRegistered = true;
1431+
$this->polluteScopeWithLoopInitialAssignments = true;
1432+
$this->checkMaybeUndefinedVariables = true;
1433+
$this->polluteScopeWithAlwaysIterableForeach = true;
1434+
1435+
$this->analyse([__DIR__ . '/data/bug-13920.php'], []);
1436+
}
1437+
14281438
public function testBug14117(): void
14291439
{
14301440
$this->cliArgumentsVariablesRegistered = true;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug13920;
4+
5+
class TestOutput {
6+
public function __construct() {}
7+
}
8+
9+
final class MyTest
10+
{
11+
public function testRun(): void
12+
{
13+
$savedArgv = $_SERVER['argv'];
14+
15+
try {
16+
$output = new class() extends TestOutput {
17+
public function __construct()
18+
{
19+
parent::__construct();
20+
}
21+
};
22+
} finally {
23+
$_SERVER['argv'] = $savedArgv;
24+
}
25+
}
26+
}

0 commit comments

Comments
 (0)