Skip to content

Commit af0f5af

Browse files
committed
Fix false positive dead catch for ReflectionMethod::invoke/invokeArgs
- Added ReflectionMethodInvokeMethodThrowTypeExtension to declare that ReflectionMethod::invoke() and invokeArgs() can throw any Throwable, since they execute arbitrary user code - New regression test in tests/PHPStan/Rules/Exceptions/data/bug-7719.php - The root cause was that without a DynamicMethodThrowTypeExtension, PHPStan only considered ReflectionException (from stubs) as the throw type, causing any non-ReflectionException catch to be flagged as dead
1 parent 106fc93 commit af0f5af

3 files changed

Lines changed: 70 additions & 0 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Php;
4+
5+
use PhpParser\Node\Expr\MethodCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\DependencyInjection\AutowiredService;
8+
use PHPStan\Reflection\MethodReflection;
9+
use PHPStan\Type\DynamicMethodThrowTypeExtension;
10+
use PHPStan\Type\ObjectType;
11+
use PHPStan\Type\Type;
12+
use ReflectionMethod;
13+
use Throwable;
14+
use function in_array;
15+
16+
#[AutowiredService]
17+
final class ReflectionMethodInvokeMethodThrowTypeExtension implements DynamicMethodThrowTypeExtension
18+
{
19+
20+
public function isMethodSupported(MethodReflection $methodReflection): bool
21+
{
22+
return in_array($methodReflection->getName(), ['invoke', 'invokeArgs'], true)
23+
&& $methodReflection->getDeclaringClass()->getName() === ReflectionMethod::class;
24+
}
25+
26+
public function getThrowTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
27+
{
28+
return new ObjectType(Throwable::class);
29+
}
30+
31+
}

tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,4 +643,9 @@ public function testPropertyHooks(): void
643643
]);
644644
}
645645

646+
public function testBug7719(): void
647+
{
648+
$this->analyse([__DIR__ . '/data/bug-7719.php'], []);
649+
}
650+
646651
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug7719;
4+
5+
class Endpoint {
6+
public function run(int $id): int {
7+
return $id;
8+
}
9+
}
10+
11+
class HelloWorld
12+
{
13+
public function sayHello(Endpoint $endpoint, string $methodName): void
14+
{
15+
try {
16+
$methodResponse = (new \ReflectionMethod($endpoint, $methodName))->invokeArgs($endpoint, ['id' => 2]);
17+
} catch (\RuntimeException $e) {
18+
echo $e->getMessage();
19+
die;
20+
}
21+
var_dump($methodResponse);
22+
}
23+
24+
public function sayHelloInvoke(Endpoint $endpoint, string $methodName): void
25+
{
26+
try {
27+
$methodResponse = (new \ReflectionMethod($endpoint, $methodName))->invoke($endpoint, 2);
28+
} catch (\RuntimeException $e) {
29+
echo $e->getMessage();
30+
die;
31+
}
32+
var_dump($methodResponse);
33+
}
34+
}

0 commit comments

Comments
 (0)