Skip to content

Commit b6073f0

Browse files
ondrejmirtesVincentLanglet
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 66617b9 commit b6073f0

File tree

3 files changed

+78
-1
lines changed

3 files changed

+78
-1
lines changed

src/Analyser/NodeScopeResolver.php

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

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

19041908
// implicit only
1905-
if (count($matchingThrowPoints) === 0 || $onlyExplicitIsThrow) {
1909+
if (count($matchingThrowPoints) === 0 || $onlyExplicitIsThrow || !$hasDirectExplicitNonThrowMatch) {
19061910
foreach ($throwPoints as $throwPointIndex => $throwPoint) {
19071911
if ($throwPoint->isExplicit()) {
19081912
continue;

tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,6 +1403,21 @@ public function testBug14019(): void
14031403
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-14019.php'], []);
14041404
}
14051405

1406+
public function testBug9349(): void
1407+
{
1408+
$this->cliArgumentsVariablesRegistered = true;
1409+
$this->polluteScopeWithLoopInitialAssignments = false;
1410+
$this->checkMaybeUndefinedVariables = true;
1411+
$this->polluteScopeWithAlwaysIterableForeach = true;
1412+
1413+
$this->analyse([__DIR__ . '/data/bug-9349.php'], [
1414+
[
1415+
'Variable $sql might not be defined.',
1416+
19,
1417+
],
1418+
]);
1419+
}
1420+
14061421
#[RequiresPhp('>= 8.0')]
14071422
public function testBug14274(): void
14081423
{
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
33+
public function test2(): void
34+
{
35+
global $pdo;
36+
37+
try {
38+
$this->maybeThrows2();
39+
$sql = "SELECT * FROM foo";
40+
$rs = $pdo->query($sql);
41+
if ($result = $rs->fetch(\PDO::FETCH_ASSOC)) {
42+
// do something
43+
}
44+
} catch (\PDOException $e) {
45+
var_dump($sql);
46+
}
47+
}
48+
49+
/**
50+
* @throws \LogicException
51+
*/
52+
public function maybeThrows2(): void
53+
{
54+
if (random_int(0, 1) === 1) {
55+
throw new \LogicException();
56+
}
57+
}
58+
}

0 commit comments

Comments
 (0)