Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

array_walk with pass-by-reference causes false positive unused variable #215

Closed
sirbrillig opened this issue Nov 23, 2020 · 2 comments · Fixed by #216 or #217
Closed

array_walk with pass-by-reference causes false positive unused variable #215

sirbrillig opened this issue Nov 23, 2020 · 2 comments · Fixed by #216 or #217
Labels

Comments

@sirbrillig
Copy link
Owner

This code produces Unused variable $value, when it is in fact used:

     array_walk($userNameParts, static function (string &$value): void {
            if (strlen($value) <= 3) {
                return;
            }

            $value = ucfirst($value);
        });

Reported by @Levivb in #214

@sirbrillig sirbrillig added the bug label Nov 23, 2020
This was referenced Nov 23, 2020
@sirbrillig
Copy link
Owner Author

sirbrillig commented Nov 23, 2020

This is supposed to be handled by the code here:

if ($varInfo->referencedVariableScope !== null && isset($varInfo->firstInitialized)) {
// If we're pass-by-reference then it's a common pattern to
// use the variable to return data to the caller, so any
// assignment also counts as "variable use" for the purposes
// of "unused variable" warnings.
return;

but in this case, referencedVariableScope is null for some reason, which implies that this block is not firing:

// Are we pass-by-reference?
$referencePtr = $phpcsFile->findPrevious(Tokens::$emptyTokens, $stackPtr - 1, null, true, null, true);
if (($referencePtr !== false) && ($tokens[$referencePtr]['code'] === T_BITWISE_AND)) {
$varInfo = $this->getOrCreateVariableInfo($varName, $functionPtr);
$varInfo->referencedVariableScope = $outerScope;
}

@sirbrillig
Copy link
Owner Author

Actually, it looks like the variable is correctly being marked as pass-by-reference, but then when it reaches where the variable is used the code thinks that the variable is a new instance; more specifically, that it has a new scope. The original variable is defined as having a scope starting with the array_walk closure, but the new variable's scope starts at the enclosing function.

The bug is happening because the scope detection code is getting confused about the difference between a bound reference variable and one which is going to be bound at runtime.

For example:

$foo = 'hello';
$bar = &$foo; // $bar is now a reference variable bound to $foo
$bar = 'bye'; // this writes to the bound variable; now $foo is 'bye'
array_walk($data, function (&$foo) { // $foo is now a reference variable that will be bound at runtime
  $foo = ucfirst($foo); // this writes to the bound variable, but we can't know anything about that variable since it does not exist in the code before runtime
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
1 participant