diff --git a/src/Analyser/ExprHandler/NewHandler.php b/src/Analyser/ExprHandler/NewHandler.php index 2e57336cb9e..57cc504a3a3 100644 --- a/src/Analyser/ExprHandler/NewHandler.php +++ b/src/Analyser/ExprHandler/NewHandler.php @@ -84,6 +84,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $parametersAcceptor = null; $constructorReflection = null; $classReflection = null; + $isDynamic = false; $hasYield = false; $throwPoints = []; $impurePoints = []; @@ -92,7 +93,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex if ($expr->class instanceof Name) { $className = $scope->resolveName($expr->class); - [$constructorReflection, $classReflection, $parametersAcceptor, $constructorImpurePoints] = $this->processConstructorReflection($className, $expr, $scope); + [$constructorReflection, $classReflection, $parametersAcceptor, $constructorImpurePoints] = $this->processConstructorReflection($className, $expr, $scope, false); $impurePoints = array_merge($impurePoints, $constructorImpurePoints); if ($parametersAcceptor !== null) { @@ -153,6 +154,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $nodeScopeResolver->processStmtNode($expr->class, $scope, $storage, $nodeCallback, StatementContext::createTopLevel()); } } else { + $isDynamic = true; $objectClasses = $scope->getType($expr)->getObjectClassNames(); if (count($objectClasses) === 1) { $objectExprResult = $nodeScopeResolver->processExprNode($stmt, new New_(new Name($objectClasses[0])), $scope, $storage, new NoopNodeCallback(), $context->enterDeep()); @@ -172,7 +174,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $throwPoints = array_merge($throwPoints, $additionalThrowPoints); if ($className !== null) { - [$constructorReflection, $classReflection, $parametersAcceptor, $constructorImpurePoints] = $this->processConstructorReflection($className, $expr, $scope); + [$constructorReflection, $classReflection, $parametersAcceptor, $constructorImpurePoints] = $this->processConstructorReflection($className, $expr, $scope, true); $impurePoints = array_merge($impurePoints, $constructorImpurePoints); } else { $impurePoints[] = new ImpurePoint( @@ -202,7 +204,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex if ($constructorThrowPoint !== null) { $throwPoints[] = $constructorThrowPoint; } - } elseif ($classReflection === null) { + } elseif ($classReflection === null || ($isDynamic && $constructorReflection === null && !$classReflection->isFinal())) { $throwPoints[] = InternalThrowPoint::createImplicit($scope, $expr); } @@ -218,7 +220,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex /** * @return array{?MethodReflection, ?ClassReflection, ?ParametersAcceptor, ImpurePoint[]} */ - private function processConstructorReflection(string $className, New_ $expr, MutatingScope $scope): array + private function processConstructorReflection(string $className, New_ $expr, MutatingScope $scope, bool $isDynamic): array { $constructorReflection = null; $parametersAcceptor = null; @@ -257,6 +259,14 @@ private function processConstructorReflection(string $className, New_ $expr, Mut 'instantiation of unknown class', false, ); + } elseif ($isDynamic && !$classReflection->isFinal()) { + $impurePoints[] = new ImpurePoint( + $scope, + $expr, + 'new', + sprintf('instantiation of class %s', $classReflection->getDisplayName()), + false, + ); } return [$constructorReflection, $classReflection, $parametersAcceptor, $impurePoints]; diff --git a/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php b/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php index 52be895f99b..e7e10cb9e93 100644 --- a/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php @@ -798,4 +798,22 @@ public function testBug14569(): void $this->analyse([__DIR__ . '/data/bug-14569.php'], []); } + public function testBug6574(): void + { + $this->analyse([__DIR__ . '/data/bug-6574.php'], [ + [ + 'Dead catch - Exception is never thrown in the try block.', + 97, + ], + [ + 'Dead catch - Exception is never thrown in the try block.', + 106, + ], + [ + 'Dead catch - Exception is never thrown in the try block.', + 115, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Exceptions/data/bug-4806.php b/tests/PHPStan/Rules/Exceptions/data/bug-4806.php index 78d1ae5d389..d9f4b7fa39b 100644 --- a/tests/PHPStan/Rules/Exceptions/data/bug-4806.php +++ b/tests/PHPStan/Rules/Exceptions/data/bug-4806.php @@ -12,7 +12,7 @@ final public function __construct() } } -class HasNoConstructor +final class HasNoConstructor { } diff --git a/tests/PHPStan/Rules/Exceptions/data/bug-6574.php b/tests/PHPStan/Rules/Exceptions/data/bug-6574.php new file mode 100644 index 00000000000..d366912b8bc --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/data/bug-6574.php @@ -0,0 +1,117 @@ + $class */ +function interfaceWithoutConstructor(string $class): void +{ + try { + new $class(); + } catch (\Exception $e) { + } +} + +/** @param class-string $class */ +function interfaceWithConstructor(string $class): void +{ + try { + new $class(); + } catch (\Exception $e) { + } +} + +/** @param class-string $class */ +function abstractClassWithoutConstructor(string $class): void +{ + try { + new $class(); + } catch (\Exception $e) { + } +} + +/** @param class-string $class */ +function abstractClassWithConstructor(string $class): void +{ + try { + new $class(); + } catch (\Exception $e) { + } +} + +/** @param class-string $class */ +function nonFinalClassWithoutConstructor(string $class): void +{ + try { + new $class(); + } catch (\Exception $e) { + } +} + +/** @param class-string $class */ +function interfaceWithThrowsVoidConstructor(string $class): void +{ + try { + new $class(); + } catch (\Exception $e) { // dead catch - constructor is @throws void + } +} + +/** @param class-string $class */ +function abstractClassWithThrowsVoidConstructor(string $class): void +{ + try { + new $class(); + } catch (\Exception $e) { // dead catch - constructor is @throws void + } +} + +/** @param class-string $class */ +function finalClassWithoutConstructor(string $class): void +{ + try { + new $class(); + } catch (\Exception $e) { // dead catch - final class with no constructor + } +} diff --git a/tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php b/tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php index d4fd0d07518..cb9afa117a6 100644 --- a/tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php +++ b/tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php @@ -225,4 +225,9 @@ public function testBug14557(): void $this->analyse([__DIR__ . '/data/bug-14557-function.php'], []); } + public function testBug6574(): void + { + $this->analyse([__DIR__ . '/data/bug-6574.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Pure/data/bug-6574.php b/tests/PHPStan/Rules/Pure/data/bug-6574.php new file mode 100644 index 00000000000..f025719a252 --- /dev/null +++ b/tests/PHPStan/Rules/Pure/data/bug-6574.php @@ -0,0 +1,23 @@ + $class */ +function interfaceWithoutConstructor(string $class): void +{ + new $class(); +} + +/** @param class-string $class */ +function abstractClassWithoutConstructor(string $class): void +{ + new $class(); +}