diff --git a/src/Analyser/ExprHandler/MethodCallHandler.php b/src/Analyser/ExprHandler/MethodCallHandler.php index bf40faf5468..35c5455da5c 100644 --- a/src/Analyser/ExprHandler/MethodCallHandler.php +++ b/src/Analyser/ExprHandler/MethodCallHandler.php @@ -110,10 +110,6 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $methodReflection->getNamedArgumentsVariants(), ); - $methodThrowPoint = $this->getMethodThrowPoint($methodReflection, $parametersAcceptor, $expr, $scope); - if ($methodThrowPoint !== null) { - $throwPoints[] = $methodThrowPoint; - } } } else { $methodNameResult = $nodeScopeResolver->processExprNode($stmt, $expr->name, $scope, $storage, $nodeCallback, $context->enterDeep()); @@ -157,6 +153,13 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $argsResult->getScope(); if ($methodReflection !== null) { + if ($parametersAcceptor !== null) { + $methodThrowPoint = $this->getMethodThrowPoint($methodReflection, $parametersAcceptor, $expr, $scope); + if ($methodThrowPoint !== null) { + $throwPoints[] = $methodThrowPoint; + } + } + if ($methodReflection->getName() === '__construct' || $methodReflection->hasSideEffects()->yes()) { $nodeScopeResolver->callNodeCallback($nodeCallback, new InvalidateExprNode($normalizedExpr->var), $scope, $storage); $scope = $scope->invalidateExpression($normalizedExpr->var, true, $methodReflection->getDeclaringClass()); diff --git a/src/Analyser/ExprHandler/StaticCallHandler.php b/src/Analyser/ExprHandler/StaticCallHandler.php index 1438af86fad..16eceaefbc6 100644 --- a/src/Analyser/ExprHandler/StaticCallHandler.php +++ b/src/Analyser/ExprHandler/StaticCallHandler.php @@ -103,11 +103,6 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $methodReflection->getNamedArgumentsVariants(), ); - $methodThrowPoint = $this->getStaticMethodThrowPoint($methodReflection, $parametersAcceptor, $expr, $scope); - if ($methodThrowPoint !== null) { - $throwPoints[] = $methodThrowPoint; - } - $declaringClass = $methodReflection->getDeclaringClass(); if ( $declaringClass->getName() === 'Closure' @@ -203,6 +198,13 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $argsResult->getScope(); $scopeFunction = $scope->getFunction(); + if ($methodReflection !== null && $parametersAcceptor !== null) { + $methodThrowPoint = $this->getStaticMethodThrowPoint($methodReflection, $parametersAcceptor, $expr, $scope); + if ($methodThrowPoint !== null) { + $throwPoints[] = $methodThrowPoint; + } + } + if ( $methodReflection !== null && ( diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index 0bf14086718..b7b547ca165 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -1422,6 +1422,16 @@ public function testBug9349(): void ]); } + public function testBug14318(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + + $this->analyse([__DIR__ . '/data/bug-14318.php'], []); + } + #[RequiresPhp('>= 8.0')] public function testBug14274(): void { diff --git a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php index 1af3618c5b8..873c89fb869 100644 --- a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php +++ b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php @@ -209,6 +209,20 @@ public function testBug10367(): void $this->analyse([__DIR__ . '/data/bug-10367.php'], []); } + public function testBug11284(): void + { + $this->treatPhpDocTypesAsCertain = true; + + $this->analyse([__DIR__ . '/data/bug-11284.php'], []); + } + + public function testBug7806(): void + { + $this->treatPhpDocTypesAsCertain = true; + + $this->analyse([__DIR__ . '/data/bug-7806.php'], []); + } + #[RequiresPhp('>= 8.0')] public function testIssetAfterRememberedConstructor(): void { diff --git a/tests/PHPStan/Rules/Variables/data/bug-11284.php b/tests/PHPStan/Rules/Variables/data/bug-11284.php new file mode 100644 index 00000000000..879eb1e5983 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-11284.php @@ -0,0 +1,32 @@ + $err + * @throws \RuntimeException + */ + public function maybeThrows(array &$err): void + { + $err[] = 'error'; + if (random_int(0, 1) === 1) { + throw new \RuntimeException(); + } + } + + public function test(): void + { + $err = []; + try { + $this->maybeThrows($err); + } catch (\RuntimeException $e) { + if (!empty($err)) { + echo implode(', ', $err); + } + } + } + +} diff --git a/tests/PHPStan/Rules/Variables/data/bug-14318.php b/tests/PHPStan/Rules/Variables/data/bug-14318.php new file mode 100644 index 00000000000..68d25809562 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-14318.php @@ -0,0 +1,115 @@ +maybeThrows5($sql = "SELECT * FROM foo"); + $rs = $pdo->query($sql); + if ($result = $rs->fetch(\PDO::FETCH_ASSOC)) { + // do something + } + } catch (\PDOException $e) { + var_dump($sql); + } + } + + /** + * @throws \RuntimeException + */ + public function maybeThrows5(string $s): void + { + if (random_int(0, 1) === 1) { + throw new \RuntimeException(); + } + } +} + +class HelloWorld6 +{ + public function test6(): void + { + global $pdo; + + try { + $this->maybeThrows6(strlen($sql = "SELECT * FROM foo")); + $rs = $pdo->query($sql); + if ($result = $rs->fetch(\PDO::FETCH_ASSOC)) { + // do something + } + } catch (\PDOException $e) { + var_dump($sql); + } + } + + /** + * @throws \RuntimeException + */ + public function maybeThrows6(int $s): void + { + if (random_int(0, 1) === 1) { + throw new \RuntimeException(); + } + } +} + +class HelloWorld7Static +{ + public function test7(): void + { + global $pdo; + + try { + self::maybeThrows7($sql = "SELECT * FROM foo"); + $rs = $pdo->query($sql); + if ($result = $rs->fetch(\PDO::FETCH_ASSOC)) { + // do something + } + } catch (\PDOException $e) { + var_dump($sql); + } + } + + /** + * @throws \RuntimeException + */ + public static function maybeThrows7(string $s): void + { + if (random_int(0, 1) === 1) { + throw new \RuntimeException(); + } + } +} + +class HelloWorld8Static +{ + public function test8(): void + { + global $pdo; + + try { + self::maybeThrows8(strlen($sql = "SELECT * FROM foo")); + $rs = $pdo->query($sql); + if ($result = $rs->fetch(\PDO::FETCH_ASSOC)) { + // do something + } + } catch (\PDOException $e) { + var_dump($sql); + } + } + + /** + * @throws \RuntimeException + */ + public static function maybeThrows8(int $s): void + { + if (random_int(0, 1) === 1) { + throw new \RuntimeException(); + } + } +} diff --git a/tests/PHPStan/Rules/Variables/data/bug-7806.php b/tests/PHPStan/Rules/Variables/data/bug-7806.php new file mode 100644 index 00000000000..b029f23a25c --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-7806.php @@ -0,0 +1,54 @@ +|null $reasons + * @throws \Exception + */ + function check(array &$reasons = null): void { + $fileName = time() % 2 ? "abc":null; + if (!$fileName) { + $reasons[] = sprintf("Dependency check fail"); + throw new \Exception("check failed"); + } + } + + function test():void { + try { + $this->check($reasons); + printf("ok\n"); + } catch (\Exception $e) { + if (!empty($reasons)) { + $e = new \Exception("Dependency check failed: " . implode(', ', $reasons), 0, $e); + } + throw new \Exception("Failed", 0, $e); + } + } +} + +/** + * @param array|null $reasons + * @throws \Exception + */ +function check1(array &$reasons = null): void { + $fileName = time() % 2 ? "abc":null; + if (!$fileName) { + $reasons[] = sprintf("Dependency check fail"); + throw new \Exception("check failed"); + } +} + +function test1():void { + try { + check1($reasons); + printf("ok\n"); + } catch (\Exception $e) { + if (!empty($reasons)) { + $e = new \Exception("Dependency check failed: " . implode(', ', $reasons), 0, $e); + } + throw new \Exception("Failed", 0, $e); + } +} +