Skip to content

Commit 0ee67d5

Browse files
authored
Detect function variadic-ness anywhere deep in the declaration file
1 parent 74f3f58 commit 0ee67d5

File tree

4 files changed

+178
-24
lines changed

4 files changed

+178
-24
lines changed

Diff for: src/Reflection/Php/PhpFunctionReflection.php

+36-24
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@
33
namespace PHPStan\Reflection\Php;
44

55
use PhpParser\Node;
6-
use PhpParser\Node\Stmt\ClassLike;
7-
use PhpParser\Node\Stmt\Declare_;
86
use PhpParser\Node\Stmt\Function_;
9-
use PhpParser\Node\Stmt\Namespace_;
107
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionFunction;
118
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter;
129
use PHPStan\Cache\Cache;
@@ -27,6 +24,7 @@
2724
use function array_key_exists;
2825
use function array_map;
2926
use function filemtime;
27+
use function is_array;
3028
use function is_file;
3129
use function sprintf;
3230
use function time;
@@ -149,12 +147,12 @@ private function isVariadic(): bool
149147
if ($modifiedTime === false) {
150148
$modifiedTime = time();
151149
}
152-
$variableCacheKey = sprintf('%d-v3', $modifiedTime);
150+
$variableCacheKey = sprintf('%d-v4', $modifiedTime);
153151
$key = sprintf('variadic-function-%s-%s', $functionName, $fileName);
154152
$cachedResult = $this->cache->load($key, $variableCacheKey);
155153
if ($cachedResult === null) {
156154
$nodes = $this->parser->parseFile($fileName);
157-
$result = $this->callsFuncGetArgs($nodes);
155+
$result = !$this->containsVariadicFunction($nodes)->no();
158156
$this->cache->save($key, $variableCacheKey, $result);
159157
return $result;
160158
}
@@ -167,41 +165,40 @@ private function isVariadic(): bool
167165
}
168166

169167
/**
170-
* @param Node[] $nodes
168+
* @param Node[]|scalar[]|Node $node
171169
*/
172-
private function callsFuncGetArgs(array $nodes): bool
170+
private function containsVariadicFunction(array|Node $node): TrinaryLogic
173171
{
174-
foreach ($nodes as $node) {
172+
$result = TrinaryLogic::createMaybe();
173+
174+
if ($node instanceof Node) {
175175
if ($node instanceof Function_) {
176176
$functionName = (string) $node->namespacedName;
177177

178178
if ($functionName === $this->reflection->getName()) {
179-
return $this->functionCallStatementFinder->findFunctionCallInStatements(ParametersAcceptor::VARIADIC_FUNCTIONS, $node->getStmts()) !== null;
179+
return TrinaryLogic::createFromBoolean($this->isFunctionNodeVariadic($node));
180180
}
181-
182-
continue;
183181
}
184182

185-
if ($node instanceof ClassLike) {
186-
continue;
187-
}
188-
189-
if ($node instanceof Namespace_) {
190-
if ($this->callsFuncGetArgs($node->stmts)) {
191-
return true;
183+
foreach ($node->getSubNodeNames() as $subNodeName) {
184+
$innerNode = $node->{$subNodeName};
185+
if (!$innerNode instanceof Node && !is_array($innerNode)) {
186+
continue;
192187
}
193-
}
194188

195-
if (!$node instanceof Declare_ || $node->stmts === null) {
196-
continue;
189+
$result = $result->and($this->containsVariadicFunction($innerNode));
197190
}
191+
} elseif (is_array($node)) {
192+
foreach ($node as $subNode) {
193+
if (!$subNode instanceof Node) {
194+
continue;
195+
}
198196

199-
if ($this->callsFuncGetArgs($node->stmts)) {
200-
return true;
197+
$result = $result->and($this->containsVariadicFunction($subNode));
201198
}
202199
}
203200

204-
return false;
201+
return $result;
205202
}
206203

207204
private function getReturnType(): Type
@@ -303,4 +300,19 @@ public function acceptsNamedArguments(): bool
303300
return $this->acceptsNamedArguments;
304301
}
305302

303+
private function isFunctionNodeVariadic(Function_ $node): bool
304+
{
305+
foreach ($node->params as $parameter) {
306+
if ($parameter->variadic) {
307+
return true;
308+
}
309+
}
310+
311+
if ($this->functionCallStatementFinder->findFunctionCallInStatements(ParametersAcceptor::VARIADIC_FUNCTIONS, $node->getStmts()) !== null) {
312+
return true;
313+
}
314+
315+
return false;
316+
}
317+
306318
}

Diff for: tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php

+19
Original file line numberDiff line numberDiff line change
@@ -1742,4 +1742,23 @@ public function testBug11506(): void
17421742
$this->analyse([__DIR__ . '/data/bug-11506.php'], []);
17431743
}
17441744

1745+
public function testBug11559(): void
1746+
{
1747+
$this->analyse([__DIR__ . '/data/bug-11559.php'], []);
1748+
}
1749+
1750+
public function testBug11559b(): void
1751+
{
1752+
$this->analyse([__DIR__ . '/data/bug-11559b.php'], [
1753+
[
1754+
'Function Bug11559b\maybe_variadic_fn invoked with 5 parameters, 0 required.',
1755+
14,
1756+
],
1757+
[
1758+
'Function Bug11559b\maybe_variadic_fn4 invoked with 2 parameters, 0 required.',
1759+
65,
1760+
],
1761+
]);
1762+
}
1763+
17451764
}

Diff for: tests/PHPStan/Rules/Functions/data/bug-11559.php

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace Bug11559;
4+
5+
if ( ! function_exists( 'some_variadic_function' ) ) {
6+
function some_variadic_function() {
7+
$values = func_get_args();
8+
}
9+
}
10+
11+
some_variadic_function('action','asdf','1234', null, true);
12+
13+
14+
if (rand(0,1)) {
15+
} elseif ( ! function_exists( 'some_variadic_function2' ) ) {
16+
function some_variadic_function2() {
17+
$values = func_get_args();
18+
}
19+
}
20+
21+
some_variadic_function2('action','asdf','1234', null, true);
22+
23+
if (rand(0,1)) {
24+
} else if ( ! function_exists( 'some_variadic_function3' ) ) {
25+
function some_variadic_function3() {
26+
$values = func_get_args();
27+
}
28+
}
29+
30+
some_variadic_function3('action','asdf','1234', null, true);
31+
32+
if (rand(0,1)) {
33+
} else {
34+
if ( ! function_exists( 'some_variadic_function4' ) ) {
35+
function some_variadic_function4() {
36+
$values = func_get_args();
37+
}
38+
}
39+
}
40+
41+
some_variadic_function4('action','asdf','1234', null, true);

Diff for: tests/PHPStan/Rules/Functions/data/bug-11559b.php

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
namespace Bug11559b;
4+
5+
if (rand(0,1)) {
6+
function maybe_variadic_fn() {
7+
}
8+
} else {
9+
function maybe_variadic_fn() {
10+
$values = func_get_args();
11+
}
12+
}
13+
14+
maybe_variadic_fn('action','asdf','1234', null, true);
15+
16+
17+
18+
if (rand(0,1)) {
19+
function maybe_variadic_fn1(string $s, string $s2) {
20+
}
21+
} else if ( ! function_exists( 'maybe_variadic_fn1' ) ) {
22+
function maybe_variadic_fn1() {
23+
$values = func_get_args();
24+
}
25+
}
26+
27+
maybe_variadic_fn1('action','asdf');
28+
29+
30+
31+
if (rand(0,1)) {
32+
function maybe_variadic_fn2(string $s, string $s2) {
33+
}
34+
} else if ( ! function_exists( 'maybe_variadic_fn2' ) ) {
35+
function maybe_variadic_fn2(...$values) {
36+
}
37+
}
38+
39+
maybe_variadic_fn2('action','asdf');
40+
41+
42+
43+
if (rand(0,1)) {
44+
function maybe_variadic_fn3(...$values) {
45+
}
46+
} else if ( ! function_exists( 'maybe_variadic_fn3' ) ) {
47+
function maybe_variadic_fn3() {
48+
$values = func_get_args();
49+
}
50+
}
51+
52+
maybe_variadic_fn3('action','asdf');
53+
54+
55+
56+
57+
if (rand(0,1)) {
58+
function maybe_variadic_fn4() {
59+
}
60+
} else if ( ! function_exists( 'maybe_variadic_fn4' ) ) {
61+
function maybe_variadic_fn4(...$values) {
62+
}
63+
}
64+
65+
maybe_variadic_fn4('action','asdf');
66+
67+
68+
function variadic_fn5(...$values) {
69+
}
70+
variadic_fn5('action','asdf');
71+
72+
73+
if (rand(0,1)) {
74+
function maybe_variadic_fn6($x, $y): void {
75+
}
76+
} else if ( ! function_exists( 'maybe_variadic_fn6' ) ) {
77+
function maybe_variadic_fn6($y, ...$values): void {
78+
}
79+
}
80+
81+
maybe_variadic_fn6('action','asdf');
82+

0 commit comments

Comments
 (0)