Skip to content

Commit 3984d04

Browse files
ondrejmirtesphpstan-bot
authored andcommitted
Fix throw points not properly matched to catch clauses
- When a method has @throws with a supertype of the caught exception (e.g. @throws RuntimeException with catch PDOException), implicit throw points from other method calls were incorrectly excluded from the catch scope - Phase 3 (implicit throw point matching) was skipped when explicit @throws matched even as "maybe", now it only skips when there's a definitive "yes" match - Added regression test in tests/PHPStan/Rules/Variables/data/bug-9349.php Closes phpstan/phpstan#9349
1 parent d32efcb commit 3984d04

3 files changed

Lines changed: 52 additions & 1 deletion

File tree

src/Analyser/NodeScopeResolver.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1879,6 +1879,7 @@ public function processStmtNode(
18791879

18801880
// explicit only
18811881
$onlyExplicitIsThrow = true;
1882+
$hasDirectExplicitNonThrowMatch = false;
18821883
if (count($matchingThrowPoints) === 0) {
18831884
foreach ($throwPoints as $throwPointIndex => $throwPoint) {
18841885
foreach ($catchTypes as $catchTypeIndex => $catchTypeItem) {
@@ -1896,14 +1897,17 @@ public function processStmtNode(
18961897
&& !($throwNode instanceof Node\Stmt\Expression && $throwNode->expr instanceof Expr\Throw_)
18971898
) {
18981899
$onlyExplicitIsThrow = false;
1900+
if ($catchTypeItem->isSuperTypeOf($throwPoint->getType())->yes()) {
1901+
$hasDirectExplicitNonThrowMatch = true;
1902+
}
18991903
}
19001904
$matchingThrowPoints[$throwPointIndex] = $throwPoint;
19011905
}
19021906
}
19031907
}
19041908

19051909
// implicit only
1906-
if (count($matchingThrowPoints) === 0 || $onlyExplicitIsThrow) {
1910+
if (count($matchingThrowPoints) === 0 || $onlyExplicitIsThrow || !$hasDirectExplicitNonThrowMatch) {
19071911
foreach ($throwPoints as $throwPointIndex => $throwPoint) {
19081912
if ($throwPoint->isExplicit()) {
19091913
continue;

tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,6 +1393,21 @@ public function testBug6830(): void
13931393
$this->analyse([__DIR__ . '/data/bug-6830.php'], []);
13941394
}
13951395

1396+
public function testBug9349(): void
1397+
{
1398+
$this->cliArgumentsVariablesRegistered = true;
1399+
$this->polluteScopeWithLoopInitialAssignments = false;
1400+
$this->checkMaybeUndefinedVariables = true;
1401+
$this->polluteScopeWithAlwaysIterableForeach = true;
1402+
1403+
$this->analyse([__DIR__ . '/data/bug-9349.php'], [
1404+
[
1405+
'Variable $sql might not be defined.',
1406+
19,
1407+
],
1408+
]);
1409+
}
1410+
13961411
public function testBug14117(): void
13971412
{
13981413
$this->cliArgumentsVariablesRegistered = true;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug9349;
4+
5+
class HelloWorld
6+
{
7+
public function test(): void
8+
{
9+
global $pdo;
10+
11+
try {
12+
$this->maybeThrows();
13+
$sql = "SELECT * FROM foo";
14+
$rs = $pdo->query($sql);
15+
if ($result = $rs->fetch(\PDO::FETCH_ASSOC)) {
16+
// do something
17+
}
18+
} catch (\PDOException $e) {
19+
var_dump($sql);
20+
}
21+
}
22+
23+
/**
24+
* @throws \RuntimeException
25+
*/
26+
public function maybeThrows(): void
27+
{
28+
if (random_int(0, 1) === 1) {
29+
throw new \RuntimeException();
30+
}
31+
}
32+
}

0 commit comments

Comments
 (0)