From e009dbb1b56149cf3f9161fc3a4a8ecb7fb9a3f0 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 22 Jul 2025 16:40:42 +0200 Subject: [PATCH 1/5] Fix missing detection of dead code in closures --- src/Analyser/NodeScopeResolver.php | 5 +++-- src/Analyser/ProcessClosureResult.php | 6 ++++++ tests/PHPStan/Analyser/ExpressionResultTest.php | 4 ++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 667445d34d..b422e1101f 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -4690,7 +4690,7 @@ private function processClosureNode( array_merge($statementResult->getImpurePoints(), $closureImpurePoints), ), $closureScope); - return new ProcessClosureResult($scope, $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), $invalidateExpressions); + return new ProcessClosureResult($scope, $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), $invalidateExpressions, $statementResult->isAlwaysTerminating()); } $count = 0; @@ -4736,7 +4736,7 @@ private function processClosureNode( array_merge($statementResult->getImpurePoints(), $closureImpurePoints), ), $closureScope); - return new ProcessClosureResult($scope->processClosureScope($closureResultScope, null, $byRefUses), $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), $invalidateExpressions); + return new ProcessClosureResult($scope->processClosureScope($closureResultScope, null, $byRefUses), $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), $invalidateExpressions, $statementResult->isAlwaysTerminating()); } /** @@ -5180,6 +5180,7 @@ private function processArgs( if ($callCallbackImmediately) { $throwPoints = array_merge($throwPoints, array_map(static fn (ThrowPoint $throwPoint) => $throwPoint->isExplicit() ? ThrowPoint::createExplicit($scope, $throwPoint->getType(), $arg->value, $throwPoint->canContainAnyThrowable()) : ThrowPoint::createImplicit($scope, $arg->value), $closureResult->getThrowPoints())); $impurePoints = array_merge($impurePoints, $closureResult->getImpurePoints()); + $isAlwaysTerminating = $isAlwaysTerminating || $closureResult->isAlwaysTerminating(); } $uses = []; diff --git a/src/Analyser/ProcessClosureResult.php b/src/Analyser/ProcessClosureResult.php index 0051383278..a133bfa77b 100644 --- a/src/Analyser/ProcessClosureResult.php +++ b/src/Analyser/ProcessClosureResult.php @@ -17,6 +17,7 @@ public function __construct( private array $throwPoints, private array $impurePoints, private array $invalidateExpressions, + private bool $isAlwaysTerminating, ) { } @@ -50,4 +51,9 @@ public function getInvalidateExpressions(): array return $this->invalidateExpressions; } + public function isAlwaysTerminating(): bool + { + return $this->isAlwaysTerminating; + } + } diff --git a/tests/PHPStan/Analyser/ExpressionResultTest.php b/tests/PHPStan/Analyser/ExpressionResultTest.php index 5012b0ef87..f7ed8c49c4 100644 --- a/tests/PHPStan/Analyser/ExpressionResultTest.php +++ b/tests/PHPStan/Analyser/ExpressionResultTest.php @@ -101,6 +101,10 @@ public static function dataIsAlwaysTerminating(): array 'exit() ?? $x;', true, ], + [ + 'call_user_func(function() { exit(); });', + true, + ], [ 'var_dump(1+exit());', true, From edd87555c0e5692389d8dba6f34157ef433670ca Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 22 Jul 2025 16:43:30 +0200 Subject: [PATCH 2/5] Update ExpressionResultTest.php --- tests/PHPStan/Analyser/ExpressionResultTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/PHPStan/Analyser/ExpressionResultTest.php b/tests/PHPStan/Analyser/ExpressionResultTest.php index f7ed8c49c4..9f6d9171db 100644 --- a/tests/PHPStan/Analyser/ExpressionResultTest.php +++ b/tests/PHPStan/Analyser/ExpressionResultTest.php @@ -105,6 +105,10 @@ public static function dataIsAlwaysTerminating(): array 'call_user_func(function() { exit(); });', true, ], + [ + 'usort($arr, static function($a, $b):int { return $a <=> $b; });', + false, + ], [ 'var_dump(1+exit());', true, From b00f40b1fe1c6d603120a616119819e64dadff09 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 22 Jul 2025 16:59:38 +0200 Subject: [PATCH 3/5] Update ExpressionResultTest.php --- tests/PHPStan/Analyser/ExpressionResultTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/PHPStan/Analyser/ExpressionResultTest.php b/tests/PHPStan/Analyser/ExpressionResultTest.php index 9f6d9171db..b65a09dc34 100644 --- a/tests/PHPStan/Analyser/ExpressionResultTest.php +++ b/tests/PHPStan/Analyser/ExpressionResultTest.php @@ -101,6 +101,10 @@ public static function dataIsAlwaysTerminating(): array 'exit() ?? $x;', true, ], + [ + '(function() { exit(); })();', + true, + ], [ 'call_user_func(function() { exit(); });', true, From be47978f7d4ec9df85e62ba38cd038ff7e2b5ed2 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 1 Aug 2025 13:55:45 +0200 Subject: [PATCH 4/5] Update NodeScopeResolver.php --- src/Analyser/NodeScopeResolver.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index b422e1101f..a7b24a49ae 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -4639,6 +4639,9 @@ private function processClosureNode( throw new ShouldNotHappenException(); } + $returnType = $closureType->getReturnType(); + $isAlwaysTerminating = ($returnType instanceof NeverType && $returnType->isExplicit()); + $nodeCallback(new InClosureNode($closureType, $expr), $closureScope); $executionEnds = []; @@ -4690,7 +4693,7 @@ private function processClosureNode( array_merge($statementResult->getImpurePoints(), $closureImpurePoints), ), $closureScope); - return new ProcessClosureResult($scope, $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), $invalidateExpressions, $statementResult->isAlwaysTerminating()); + return new ProcessClosureResult($scope, $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), $invalidateExpressions, $isAlwaysTerminating); } $count = 0; @@ -4736,7 +4739,7 @@ private function processClosureNode( array_merge($statementResult->getImpurePoints(), $closureImpurePoints), ), $closureScope); - return new ProcessClosureResult($scope->processClosureScope($closureResultScope, null, $byRefUses), $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), $invalidateExpressions, $statementResult->isAlwaysTerminating()); + return new ProcessClosureResult($scope->processClosureScope($closureResultScope, null, $byRefUses), $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), $invalidateExpressions, $isAlwaysTerminating); } /** From 23a2b41585b52be36b345dfb88b0262eee1b158d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 1 Aug 2025 14:05:21 +0200 Subject: [PATCH 5/5] Update ExpressionResultTest.php --- tests/PHPStan/Analyser/ExpressionResultTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/PHPStan/Analyser/ExpressionResultTest.php b/tests/PHPStan/Analyser/ExpressionResultTest.php index b65a09dc34..7be3da9fdb 100644 --- a/tests/PHPStan/Analyser/ExpressionResultTest.php +++ b/tests/PHPStan/Analyser/ExpressionResultTest.php @@ -105,6 +105,10 @@ public static function dataIsAlwaysTerminating(): array '(function() { exit(); })();', true, ], + [ + 'function () {};', + false, + ], [ 'call_user_func(function() { exit(); });', true,