Skip to content

Commit 86a188a

Browse files
committed
Fix false positive dead catch for ReflectionMethod::invoke/invokeArgs
- Added ReflectionMethodInvokeMethodThrowTypeExtension to declare that ReflectionMethod::invoke(), ReflectionMethod::invokeArgs(), ReflectionFunction::invoke(), and ReflectionFunction::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 BetterReflection's adapter declared @throws ReflectionException on these methods, causing PHPStan to treat ReflectionException as the only possible thrown type Closes phpstan/phpstan#7719
1 parent 106fc93 commit 86a188a

5 files changed

Lines changed: 99 additions & 2 deletions

File tree

phpstan-baseline.neon

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,6 +1641,12 @@ parameters:
16411641
count: 1
16421642
path: src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php
16431643

1644+
-
1645+
rawMessage: 'Method PHPStan\Type\Php\ReflectionMethodInvokeMethodThrowTypeExtension::getThrowTypeFromMethodCall() never returns null so it can be removed from the return type.'
1646+
identifier: return.unusedType
1647+
count: 1
1648+
path: src/Type/Php/ReflectionMethodInvokeMethodThrowTypeExtension.php
1649+
16441650
-
16451651
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.'
16461652
identifier: phpstanApi.instanceofType
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 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 ReflectionFunction;
13+
use ReflectionMethod;
14+
use Throwable;
15+
use function in_array;
16+
17+
#[AutowiredService]
18+
final class ReflectionMethodInvokeMethodThrowTypeExtension implements DynamicMethodThrowTypeExtension
19+
{
20+
21+
public function isMethodSupported(MethodReflection $methodReflection): bool
22+
{
23+
return in_array($methodReflection->getDeclaringClass()->getName(), [ReflectionMethod::class, ReflectionFunction::class], true)
24+
&& in_array($methodReflection->getName(), ['invoke', 'invokeArgs'], true);
25+
}
26+
27+
public function getThrowTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
28+
{
29+
return new ObjectType(Throwable::class);
30+
}
31+
32+
}

tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,11 @@ public function testBug9568(): void
604604
$this->analyse([__DIR__ . '/data/bug-9568.php'], []);
605605
}
606606

607+
public function testBug7719(): void
608+
{
609+
$this->analyse([__DIR__ . '/data/bug-7719.php'], []);
610+
}
611+
607612
#[RequiresPhp('>= 8.4')]
608613
public function testPropertyHooks(): void
609614
{
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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 sayHelloWithInvoke(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+
35+
public function sayHelloWithFunction(string $functionName): void
36+
{
37+
try {
38+
$result = (new \ReflectionFunction($functionName))->invokeArgs([1, 2]);
39+
} catch (\RuntimeException $e) {
40+
echo $e->getMessage();
41+
die;
42+
}
43+
var_dump($result);
44+
}
45+
46+
public function sayHelloWithFunctionInvoke(string $functionName): void
47+
{
48+
try {
49+
$result = (new \ReflectionFunction($functionName))->invoke(1, 2);
50+
} catch (\RuntimeException $e) {
51+
echo $e->getMessage();
52+
die;
53+
}
54+
var_dump($result);
55+
}
56+
}

tmp/cache/.gitignore

Lines changed: 0 additions & 2 deletions
This file was deleted.

0 commit comments

Comments
 (0)