Skip to content

Commit 9dbff97

Browse files
authored
Fix args are mistakenly handled as immediately-invoked
1 parent e6c14f1 commit 9dbff97

File tree

13 files changed

+309
-3
lines changed

13 files changed

+309
-3
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5139,16 +5139,22 @@ private function processArgs(
51395139
$scopeToPass = $closureBindScope;
51405140
}
51415141

5142+
$parameterCallableType = null;
5143+
if ($parameterType !== null) {
5144+
$parameterCallableType = TypeUtils::findCallableType($parameterType);
5145+
}
5146+
51425147
if ($parameter instanceof ExtendedParameterReflection) {
51435148
$parameterCallImmediately = $parameter->isImmediatelyInvokedCallable();
51445149
if ($parameterCallImmediately->maybe()) {
5145-
$callCallbackImmediately = $calleeReflection instanceof FunctionReflection;
5150+
$callCallbackImmediately = $parameterCallableType !== null && $calleeReflection instanceof FunctionReflection;
51465151
} else {
51475152
$callCallbackImmediately = $parameterCallImmediately->yes();
51485153
}
51495154
} else {
5150-
$callCallbackImmediately = $calleeReflection instanceof FunctionReflection;
5155+
$callCallbackImmediately = $parameterCallableType !== null && $calleeReflection instanceof FunctionReflection;
51515156
}
5157+
51525158
if ($arg->value instanceof Expr\Closure) {
51535159
$restoreThisScope = null;
51545160
if (

src/Type/TypeUtils.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,24 @@ public static function findThisType(Type $type): ?ThisType
170170
return null;
171171
}
172172

173+
public static function findCallableType(Type $type): ?Type
174+
{
175+
if ($type->isCallable()->yes()) {
176+
return $type;
177+
}
178+
179+
if ($type instanceof UnionType) {
180+
foreach ($type->getTypes() as $innerType) {
181+
$callableType = self::findCallableType($innerType);
182+
if ($callableType !== null) {
183+
return $callableType;
184+
}
185+
}
186+
}
187+
188+
return null;
189+
}
190+
173191
/**
174192
* @return HasPropertyType[]
175193
*/

stubs/core.stub

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,3 +327,38 @@ function get_defined_constants(bool $categorize = false): array {}
327327
*/
328328
function getopt(string $short_options, array $long_options = [], &$rest_index = null) {}
329329

330+
/**
331+
* @param callable|int $handler
332+
* @param-later-invoked-callable $handler
333+
*/
334+
function pcntl_signal(int $signal, $handler, bool $restart_syscalls = true): bool {}
335+
336+
/**
337+
* @param-later-invoked-callable $callback
338+
*/
339+
function set_error_handler(?callable $callback, int $error_levels = E_ALL): ?callable {}
340+
341+
/**
342+
* @param-later-invoked-callable $callback
343+
*/
344+
function set_exception_handler(?callable $callback): ?callable {}
345+
346+
/**
347+
* @param-later-invoked-callable $callback
348+
*/
349+
function spl_autoload_register(?callable $callback = null, bool $throw = true, bool $prepend = false): bool {}
350+
351+
/**
352+
* @param-later-invoked-callable $callback
353+
*/
354+
function register_shutdown_function(callable $callback, mixed ...$args): void {}
355+
356+
/**
357+
* @param-later-invoked-callable $callback
358+
*/
359+
function header_register_callback(callable $callback): bool {}
360+
361+
/**
362+
* @param-later-invoked-callable $callback
363+
*/
364+
function register_tick_function(callable $callback, mixed ...$args): bool {}

tests/PHPStan/Analyser/ExpressionResultTest.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
use PHPStan\ShouldNotHappenException;
88
use PHPStan\Testing\PHPStanTestCase;
99
use PHPStan\TrinaryLogic;
10+
use PHPStan\Type\ArrayType;
1011
use PHPStan\Type\IntegerType;
12+
use PHPStan\Type\MixedType;
1113
use PHPUnit\Framework\Attributes\DataProvider;
1214
use function count;
1315
use function get_class;
@@ -127,6 +129,22 @@ public static function dataIsAlwaysTerminating(): array
127129
'var_dump(exit()."a");',
128130
true,
129131
],
132+
[
133+
'array_push($arr, fn() => "exit");',
134+
false,
135+
],
136+
[
137+
'array_push($arr, function() { exit(); });',
138+
false,
139+
],
140+
[
141+
'array_push($arr, "exit");',
142+
false,
143+
],
144+
[
145+
'array_unshift($arr, "exit");',
146+
false,
147+
],
130148
];
131149
}
132150

@@ -155,7 +173,8 @@ public function testIsAlwaysTerminating(
155173
/** @var ScopeFactory $scopeFactory */
156174
$scopeFactory = self::getContainer()->getByType(ScopeFactory::class);
157175
$scope = $scopeFactory->create(ScopeContext::create('test.php'))
158-
->assignVariable('x', new IntegerType(), new IntegerType(), TrinaryLogic::createYes());
176+
->assignVariable('x', new IntegerType(), new IntegerType(), TrinaryLogic::createYes())
177+
->assignVariable('arr', new ArrayType(new MixedType(), new MixedType()), new ArrayType(new MixedType(), new MixedType()), TrinaryLogic::createYes());
159178

160179
$result = $nodeScopeResolver->processExprNode(
161180
$stmt,

tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,4 +348,29 @@ public function testBug13232d(): void
348348
]);
349349
}
350350

351+
#[RequiresPhp('>= 8.1')]
352+
public function testBug13288(): void
353+
{
354+
$this->treatPhpDocTypesAsCertain = false;
355+
$this->analyse([__DIR__ . '/data/bug-13288.php'], []);
356+
}
357+
358+
public function testBug13311(): void
359+
{
360+
$this->treatPhpDocTypesAsCertain = false;
361+
$this->analyse([__DIR__ . '/data/bug-13311.php'], []);
362+
}
363+
364+
public function testBug13307(): void
365+
{
366+
$this->treatPhpDocTypesAsCertain = false;
367+
$this->analyse([__DIR__ . '/data/bug-13307.php'], []);
368+
}
369+
370+
public function testBug13331(): void
371+
{
372+
$this->treatPhpDocTypesAsCertain = false;
373+
$this->analyse([__DIR__ . '/data/bug-13331.php'], []);
374+
}
375+
351376
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php // lint >= 8.1
2+
3+
namespace Bug13288;
4+
5+
function error_to_exception(int $errno, string $errstr, string $errfile = 'unknown', int $errline = 0): never {
6+
throw new \ErrorException($errstr, $errno, $errno, $errfile, $errline);
7+
}
8+
9+
set_error_handler(error_to_exception(...));
10+
11+
echo 'ok';
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Bug13307;
4+
5+
function dd(): never {
6+
exit(1);
7+
}
8+
9+
class HelloWorld
10+
{
11+
public function testMethod(): string
12+
{
13+
var_dump("\Bug13307\dd");
14+
15+
return "test";
16+
}
17+
18+
public function testMethod2(): string
19+
{
20+
var_dump("\Bug13307\DD");
21+
22+
return "test";
23+
}
24+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Bug13311;
4+
5+
use Exception;
6+
7+
$error_handler = static function () { throw new Exception(); };
8+
set_error_handler($error_handler, \E_NOTICE | \E_WARNING);
9+
10+
try {
11+
$out = iconv($from, $to . $iconv_options, $str);
12+
} catch (Throwable $e) {
13+
$out = false;
14+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug13331;
4+
5+
$signalHandler = function () {
6+
exit();
7+
};
8+
9+
pcntl_async_signals(true);
10+
pcntl_signal(SIGINT, $signalHandler);
11+
pcntl_signal(SIGQUIT, $signalHandler);
12+
pcntl_signal(SIGTERM, $signalHandler);

tests/PHPStan/Rules/Exceptions/data/missing-exception-function-throws.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,10 @@ function doBar3(): void
5656
{
5757
throw new \LogicException(); // error
5858
}
59+
60+
function bug13288(array $a): void
61+
{
62+
array_push($a, function() {
63+
throw new \LogicException(); // ok, as array_push() will not invoke the function
64+
});
65+
}

0 commit comments

Comments
 (0)