Skip to content

Commit 6f4fcd1

Browse files
VincentLangletphpstan-bot
authored andcommitted
Fix phpstan/phpstan#6799: Invalidate variables passed by reference in array arguments
- Variables referenced via & in array literals passed as function arguments (e.g. call_user_func_array($cb, [&$var, ...])) are now invalidated after the function call, since the callee can modify them through the reference - Added invalidateByRefVariablesInArrayArg() to NodeScopeResolver::processArgs() - New regression test in tests/PHPStan/Analyser/nsrt/bug-6799.php
1 parent 85c4052 commit 6f4fcd1

2 files changed

Lines changed: 79 additions & 0 deletions

File tree

src/Analyser/NodeScopeResolver.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3535,10 +3535,41 @@ public function processArgs(
35353535
}
35363536
}
35373537

3538+
// Invalidate variables passed by reference inside array arguments
3539+
// e.g. call_user_func_array($callback, [&$var, ...]) - $var might be modified
3540+
foreach ($args as $arg) {
3541+
$scope = $this->invalidateByRefVariablesInArrayArg($scope, $arg->value);
3542+
}
3543+
35383544
// not storing this, it's scope after processing all args
35393545
return new ExpressionResult($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints);
35403546
}
35413547

3548+
private function invalidateByRefVariablesInArrayArg(MutatingScope $scope, Expr $expr): MutatingScope
3549+
{
3550+
if (!$expr instanceof Array_) {
3551+
return $scope;
3552+
}
3553+
3554+
foreach ($expr->items as $arrayItem) {
3555+
if ($arrayItem->value instanceof Array_) {
3556+
$scope = $this->invalidateByRefVariablesInArrayArg($scope, $arrayItem->value);
3557+
}
3558+
3559+
if (!$arrayItem->byRef) {
3560+
continue;
3561+
}
3562+
3563+
if (!$arrayItem->value instanceof Variable || !is_string($arrayItem->value->name)) {
3564+
continue;
3565+
}
3566+
3567+
$scope = $scope->assignVariable($arrayItem->value->name, new MixedType(), new MixedType(), TrinaryLogic::createYes());
3568+
}
3569+
3570+
return $scope;
3571+
}
3572+
35423573
/**
35433574
* @param MethodReflection|FunctionReflection|null $calleeReflection
35443575
*/
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug6799;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class HelloWorld
8+
{
9+
/**
10+
* @param string[] $where
11+
* @param mixed[] $filter
12+
*/
13+
protected function addFilter(array &$where, array $filter, string $value): void
14+
{
15+
if ($value != "" && !empty($filter) && !empty($filter['sql']) && is_string($filter['sql'])) {
16+
$where[] = (string)$filter['sql'] . " = '" . $value . "'";
17+
}
18+
}
19+
20+
/**
21+
* @param string[] $filterValues
22+
* @param mixed[] $filters
23+
*/
24+
protected function test(array $filterValues, array $filters): void
25+
{
26+
if (!empty($filterValues) && !empty($filters)) {
27+
$whereFilter = array();
28+
foreach ($filterValues as $type => $value) {
29+
call_user_func_array(array($this, 'addFilter'), array(&$whereFilter, $filters[$type], $value));
30+
}
31+
assertType('mixed', $whereFilter);
32+
}
33+
}
34+
}
35+
36+
function testSimple(): void
37+
{
38+
$arr = [];
39+
some_function(1, [&$arr, 'foo']);
40+
assertType('mixed', $arr);
41+
}
42+
43+
/**
44+
* @param mixed $x
45+
*/
46+
function some_function(int $a, $x): void
47+
{
48+
}

0 commit comments

Comments
 (0)