Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 36 additions & 24 deletions src/Reflection/Php/PhpFunctionReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
namespace PHPStan\Reflection\Php;

use PhpParser\Node;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\Declare_;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Namespace_;
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionFunction;
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter;
use PHPStan\Cache\Cache;
Expand All @@ -27,6 +24,7 @@
use function array_key_exists;
use function array_map;
use function filemtime;
use function is_array;
use function is_file;
use function sprintf;
use function time;
Expand Down Expand Up @@ -149,12 +147,12 @@ private function isVariadic(): bool
if ($modifiedTime === false) {
$modifiedTime = time();
}
$variableCacheKey = sprintf('%d-v3', $modifiedTime);
$variableCacheKey = sprintf('%d-v4', $modifiedTime);
$key = sprintf('variadic-function-%s-%s', $functionName, $fileName);
$cachedResult = $this->cache->load($key, $variableCacheKey);
if ($cachedResult === null) {
$nodes = $this->parser->parseFile($fileName);
$result = $this->callsFuncGetArgs($nodes);
$result = !$this->containsVariadicFunction($nodes)->no();
$this->cache->save($key, $variableCacheKey, $result);
return $result;
}
Expand All @@ -167,41 +165,40 @@ private function isVariadic(): bool
}

/**
* @param Node[] $nodes
* @param Node[]|scalar[]|Node $node
*/
private function callsFuncGetArgs(array $nodes): bool
private function containsVariadicFunction(array|Node $node): TrinaryLogic
{
foreach ($nodes as $node) {
$result = TrinaryLogic::createMaybe();

if ($node instanceof Node) {
if ($node instanceof Function_) {
$functionName = (string) $node->namespacedName;

if ($functionName === $this->reflection->getName()) {
return $this->functionCallStatementFinder->findFunctionCallInStatements(ParametersAcceptor::VARIADIC_FUNCTIONS, $node->getStmts()) !== null;
return TrinaryLogic::createFromBoolean($this->isFunctionNodeVariadic($node));
}

continue;
}

if ($node instanceof ClassLike) {
continue;
}

if ($node instanceof Namespace_) {
if ($this->callsFuncGetArgs($node->stmts)) {
return true;
foreach ($node->getSubNodeNames() as $subNodeName) {
$innerNode = $node->{$subNodeName};
if (!$innerNode instanceof Node && !is_array($innerNode)) {
continue;
}
}

if (!$node instanceof Declare_ || $node->stmts === null) {
continue;
$result = $result->and($this->containsVariadicFunction($innerNode));
}
} elseif (is_array($node)) {
foreach ($node as $subNode) {
if (!$subNode instanceof Node) {
continue;
}

if ($this->callsFuncGetArgs($node->stmts)) {
return true;
$result = $result->and($this->containsVariadicFunction($subNode));
}
}

return false;
return $result;
}

private function getReturnType(): Type
Expand Down Expand Up @@ -303,4 +300,19 @@ public function acceptsNamedArguments(): bool
return $this->acceptsNamedArguments;
}

private function isFunctionNodeVariadic(Function_ $node): bool
{
foreach ($node->params as $parameter) {
if ($parameter->variadic) {
return true;
}
}

if ($this->functionCallStatementFinder->findFunctionCallInStatements(ParametersAcceptor::VARIADIC_FUNCTIONS, $node->getStmts()) !== null) {
return true;
}

return false;
}

}
19 changes: 19 additions & 0 deletions tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1742,4 +1742,23 @@ public function testBug11506(): void
$this->analyse([__DIR__ . '/data/bug-11506.php'], []);
}

public function testBug11559(): void
{
$this->analyse([__DIR__ . '/data/bug-11559.php'], []);
}

public function testBug11559b(): void
{
$this->analyse([__DIR__ . '/data/bug-11559b.php'], [
[
'Function Bug11559b\maybe_variadic_fn invoked with 5 parameters, 0 required.',
14,
],
[
'Function Bug11559b\maybe_variadic_fn4 invoked with 2 parameters, 0 required.',
65,
],
]);
}

}
41 changes: 41 additions & 0 deletions tests/PHPStan/Rules/Functions/data/bug-11559.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Bug11559;

if ( ! function_exists( 'some_variadic_function' ) ) {
function some_variadic_function() {
$values = func_get_args();
}
}

some_variadic_function('action','asdf','1234', null, true);


if (rand(0,1)) {
} elseif ( ! function_exists( 'some_variadic_function2' ) ) {
function some_variadic_function2() {
$values = func_get_args();
}
}

some_variadic_function2('action','asdf','1234', null, true);

if (rand(0,1)) {
} else if ( ! function_exists( 'some_variadic_function3' ) ) {
function some_variadic_function3() {
$values = func_get_args();
}
}

some_variadic_function3('action','asdf','1234', null, true);

if (rand(0,1)) {
} else {
if ( ! function_exists( 'some_variadic_function4' ) ) {
function some_variadic_function4() {
$values = func_get_args();
}
}
}

some_variadic_function4('action','asdf','1234', null, true);
82 changes: 82 additions & 0 deletions tests/PHPStan/Rules/Functions/data/bug-11559b.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace Bug11559b;

if (rand(0,1)) {
function maybe_variadic_fn() {
}
} else {
function maybe_variadic_fn() {
$values = func_get_args();
}
}

maybe_variadic_fn('action','asdf','1234', null, true);



if (rand(0,1)) {
function maybe_variadic_fn1(string $s, string $s2) {
}
} else if ( ! function_exists( 'maybe_variadic_fn1' ) ) {
function maybe_variadic_fn1() {
$values = func_get_args();
}
}

maybe_variadic_fn1('action','asdf');



if (rand(0,1)) {
function maybe_variadic_fn2(string $s, string $s2) {
}
} else if ( ! function_exists( 'maybe_variadic_fn2' ) ) {
function maybe_variadic_fn2(...$values) {
}
}

maybe_variadic_fn2('action','asdf');



if (rand(0,1)) {
function maybe_variadic_fn3(...$values) {
}
} else if ( ! function_exists( 'maybe_variadic_fn3' ) ) {
function maybe_variadic_fn3() {
$values = func_get_args();
}
}

maybe_variadic_fn3('action','asdf');




if (rand(0,1)) {
function maybe_variadic_fn4() {
}
} else if ( ! function_exists( 'maybe_variadic_fn4' ) ) {
function maybe_variadic_fn4(...$values) {
}
}

maybe_variadic_fn4('action','asdf');


function variadic_fn5(...$values) {
}
variadic_fn5('action','asdf');


if (rand(0,1)) {
function maybe_variadic_fn6($x, $y): void {
}
} else if ( ! function_exists( 'maybe_variadic_fn6' ) ) {
function maybe_variadic_fn6($y, ...$values): void {
}
}

maybe_variadic_fn6('action','asdf');

Loading