Skip to content

Commit 3b04c65

Browse files
Poc
1 parent 67ef20b commit 3b04c65

6 files changed

+237
-10
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4305,7 +4305,16 @@ private function getFunctionThrowPoint(
43054305

43064306
$throwType = $extension->getThrowTypeFromFunctionCall($functionReflection, $normalizedFuncCall, $scope);
43074307
if ($throwType === null) {
4308-
return null;
4308+
if ($this->treatPhpDocTypesAsCertain) {
4309+
return null;
4310+
}
4311+
4312+
$nativeThrowType = $extension->getThrowTypeFromFunctionCall($functionReflection, $normalizedFuncCall, $scope->doNotTreatPhpDocTypesAsCertain());
4313+
if ($nativeThrowType === null) {
4314+
return null;
4315+
}
4316+
4317+
return ThrowPoint::createImplicit($scope, $funcCall);
43094318
}
43104319

43114320
return ThrowPoint::createExplicit($scope, $throwType, $funcCall, false);
@@ -4363,7 +4372,16 @@ private function getMethodThrowPoint(MethodReflection $methodReflection, Paramet
43634372

43644373
$throwType = $extension->getThrowTypeFromMethodCall($methodReflection, $normalizedMethodCall, $scope);
43654374
if ($throwType === null) {
4366-
return null;
4375+
if ($this->treatPhpDocTypesAsCertain) {
4376+
return null;
4377+
}
4378+
4379+
$nativeThrowType = $extension->getThrowTypeFromMethodCall($methodReflection, $normalizedMethodCall, $scope->doNotTreatPhpDocTypesAsCertain());
4380+
if ($nativeThrowType === null) {
4381+
return null;
4382+
}
4383+
4384+
return ThrowPoint::createImplicit($scope, $methodCall);
43674385
}
43684386

43694387
return ThrowPoint::createExplicit($scope, $throwType, $methodCall, false);
@@ -4407,7 +4425,16 @@ private function getConstructorThrowPoint(MethodReflection $constructorReflectio
44074425

44084426
$throwType = $extension->getThrowTypeFromStaticMethodCall($constructorReflection, $normalizedMethodCall, $scope);
44094427
if ($throwType === null) {
4410-
return null;
4428+
if ($this->treatPhpDocTypesAsCertain) {
4429+
return null;
4430+
}
4431+
4432+
$nativeThrowType = $extension->getThrowTypeFromStaticMethodCall($constructorReflection, $normalizedMethodCall, $scope->doNotTreatPhpDocTypesAsCertain());
4433+
if ($nativeThrowType === null) {
4434+
return null;
4435+
}
4436+
4437+
return ThrowPoint::createImplicit($scope, $funcCall);
44114438
}
44124439

44134440
return ThrowPoint::createExplicit($scope, $throwType, $new, false);
@@ -4439,7 +4466,16 @@ private function getStaticMethodThrowPoint(MethodReflection $methodReflection, P
44394466

44404467
$throwType = $extension->getThrowTypeFromStaticMethodCall($methodReflection, $normalizedMethodCall, $scope);
44414468
if ($throwType === null) {
4442-
return null;
4469+
if ($this->treatPhpDocTypesAsCertain) {
4470+
return null;
4471+
}
4472+
4473+
$nativeThrowType = $extension->getThrowTypeFromStaticMethodCall($methodReflection, $normalizedMethodCall, $scope->doNotTreatPhpDocTypesAsCertain());
4474+
if ($nativeThrowType === null) {
4475+
return null;
4476+
}
4477+
4478+
return ThrowPoint::createImplicit($scope, $methodCall);
44434479
}
44444480

44454481
return ThrowPoint::createExplicit($scope, $throwType, $methodCall, false);
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser;
4+
5+
use PHPStan\Rules\Exceptions\CatchWithUnthrownExceptionRule;
6+
use PHPStan\Rules\Rule as TRule;
7+
use PHPStan\Testing\RuleTestCase;
8+
9+
class DynamicMethodThrowTypeExtensionDeadCatchPhpDocNotCertainTest extends RuleTestCase
10+
{
11+
12+
protected function getRule(): TRule
13+
{
14+
return self::getContainer()->getByType(CatchWithUnthrownExceptionRule::class);
15+
}
16+
17+
public function testDynamicMethodThrowTypeExtension(): void
18+
{
19+
$this->analyse([__DIR__ . '/data/dynamic-method-throw-type-extension.php'], [
20+
[
21+
'Dead catch - Exception is never thrown in the try block.',
22+
102,
23+
],
24+
[
25+
'Dead catch - Exception is never thrown in the try block.',
26+
124,
27+
],
28+
]);
29+
}
30+
31+
protected function shouldTreatPhpDocTypesAsCertain(): bool
32+
{
33+
return false;
34+
}
35+
36+
public static function getAdditionalConfigFiles(): array
37+
{
38+
return [
39+
__DIR__ . '/dynamic-throw-type-extension.neon',
40+
__DIR__ . '/do-not-treat-php-doc-type-as-certain.neon',
41+
];
42+
}
43+
44+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser;
4+
5+
use PHPStan\Rules\Exceptions\CatchWithUnthrownExceptionRule;
6+
use PHPStan\Rules\Rule as TRule;
7+
use PHPStan\Testing\RuleTestCase;
8+
9+
class DynamicMethodThrowTypeExtensionDeadCatchTest extends RuleTestCase
10+
{
11+
12+
protected function getRule(): TRule
13+
{
14+
return self::getContainer()->getByType(CatchWithUnthrownExceptionRule::class);
15+
}
16+
17+
public function testMixedUnknownType(): void
18+
{
19+
$this->analyse([__DIR__ . '/data/dynamic-method-throw-type-extension.php'], [
20+
[
21+
'Dead catch - Exception is never thrown in the try block.',
22+
102,
23+
],
24+
[
25+
'Dead catch - Exception is never thrown in the try block.',
26+
124,
27+
],
28+
[
29+
'Dead catch - Exception is never thrown in the try block.',
30+
148,
31+
],
32+
[
33+
'Dead catch - Exception is never thrown in the try block.',
34+
172,
35+
],
36+
]);
37+
}
38+
39+
public static function getAdditionalConfigFiles(): array
40+
{
41+
return [
42+
__DIR__ . '/dynamic-throw-type-extension.neon',
43+
];
44+
}
45+
46+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser;
4+
5+
use PHPStan\Testing\TypeInferenceTestCase;
6+
use PHPUnit\Framework\Attributes\DataProvider;
7+
use const PHP_VERSION_ID;
8+
9+
class DynamicMethodThrowTypeExtensionPhpDocNotCertainTest extends TypeInferenceTestCase
10+
{
11+
12+
public static function dataFileAsserts(): iterable
13+
{
14+
if (PHP_VERSION_ID < 80000) {
15+
return [];
16+
}
17+
18+
yield from self::gatherAssertTypes(__DIR__ . '/data/dynamic-method-throw-type-extension.php');
19+
yield from self::gatherAssertTypes(__DIR__ . '/data/dynamic-method-throw-type-extension-named-args-fixture.php');
20+
}
21+
22+
/**
23+
* @param mixed ...$args
24+
*/
25+
#[DataProvider('dataFileAsserts')]
26+
public function testFileAsserts(
27+
string $assertType,
28+
string $file,
29+
...$args,
30+
): void
31+
{
32+
$this->assertFileAsserts($assertType, $file, ...$args);
33+
}
34+
35+
public static function getAdditionalConfigFiles(): array
36+
{
37+
return [
38+
__DIR__ . '/dynamic-throw-type-extension.neon',
39+
__DIR__ . '/do-not-treat-php-doc-type-as-certain.neon',
40+
];
41+
}
42+
43+
}

tests/PHPStan/Analyser/data/dynamic-method-throw-type-extension.php

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ public function getThrowTypeFromMethodCall(MethodReflection $methodReflection, M
2828
}
2929

3030
$argType = $scope->getType($methodCall->args[0]->value);
31-
if ((new ConstantBooleanType(true))->isSuperTypeOf($argType)->yes()) {
32-
return $methodReflection->getThrowType();
31+
if ((new ConstantBooleanType(false))->isSuperTypeOf($argType)->yes()) {
32+
return null;
3333
}
3434

35-
return null;
35+
return $methodReflection->getThrowType();
3636
}
3737

3838
}
@@ -52,11 +52,11 @@ public function getThrowTypeFromStaticMethodCall(MethodReflection $methodReflect
5252
}
5353

5454
$argType = $scope->getType($methodCall->args[0]->value);
55-
if ((new ConstantBooleanType(true))->isSuperTypeOf($argType)->yes()) {
56-
return $methodReflection->getThrowType();
55+
if ((new ConstantBooleanType(false))->isSuperTypeOf($argType)->yes()) {
56+
return null;
5757
}
5858

59-
return null;
59+
return $methodReflection->getThrowType();
6060
}
6161

6262
}
@@ -88,6 +88,8 @@ public function doFoo1()
8888
{
8989
try {
9090
$result = $this->throwOrNot(true);
91+
} catch (\Exception) {
92+
9193
} finally {
9294
assertVariableCertainty(TrinaryLogic::createMaybe(), $result);
9395
}
@@ -97,6 +99,8 @@ public function doFoo2()
9799
{
98100
try {
99101
$result = $this->throwOrNot(false);
102+
} catch (\Exception) { // DeadCatch
103+
100104
} finally {
101105
assertVariableCertainty(TrinaryLogic::createYes(), $result);
102106
}
@@ -106,6 +110,8 @@ public function doFoo3()
106110
{
107111
try {
108112
$result = self::staticThrowOrNot(true);
113+
} catch (\Exception) {
114+
109115
} finally {
110116
assertVariableCertainty(TrinaryLogic::createMaybe(), $result);
111117
}
@@ -115,6 +121,56 @@ public function doFoo4()
115121
{
116122
try {
117123
$result = self::staticThrowOrNot(false);
124+
} catch (\Exception) { // DeadCatch
125+
126+
} finally {
127+
assertVariableCertainty(TrinaryLogic::createYes(), $result);
128+
}
129+
}
130+
131+
/** @param true $value */
132+
public function doFooPhpdoc1(bool $value)
133+
{
134+
try {
135+
$result = $this->throwOrNot($value);
136+
} catch (\Exception) {
137+
138+
} finally {
139+
assertVariableCertainty(TrinaryLogic::createMaybe(), $result);
140+
}
141+
}
142+
143+
/** @param false $value */
144+
public function doFooPhpdoc2(bool $value)
145+
{
146+
try {
147+
$result = $this->throwOrNot($value);
148+
} catch (\Exception) { // DeadCatch if phpdoc is trusted
149+
150+
} finally {
151+
assertVariableCertainty(TrinaryLogic::createYes(), $result);
152+
}
153+
}
154+
155+
/** @param true $value */
156+
public function doFooPhpdoc3(bool $value)
157+
{
158+
try {
159+
$result = self::staticThrowOrNot($value);
160+
} catch (\Exception) {
161+
162+
} finally {
163+
assertVariableCertainty(TrinaryLogic::createMaybe(), $result);
164+
}
165+
}
166+
167+
/** @param false $value */
168+
public function doFooPhpdoc4(bool $value)
169+
{
170+
try {
171+
$result = self::staticThrowOrNot($value);
172+
} catch (\Exception) { // DeadCatch if phpdoc is trusted
173+
118174
} finally {
119175
assertVariableCertainty(TrinaryLogic::createYes(), $result);
120176
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
parameters:
2+
treatPhpDocTypesAsCertain: false

0 commit comments

Comments
 (0)