Skip to content

Commit df30329

Browse files
authored
Treat writing to a by-reference foreach loop variable as a read (#221)
* Add test for foreach with pass-by-reference * Add counterexample of unused reference variable in foreach * Add isDynamicReference to VariableInfo * Set isDynamicReference for by-reference foreach loop vars * Treat writing to a by-reference foreach loopvar as a read
1 parent c6716a9 commit df30329

File tree

4 files changed

+36
-0
lines changed

4 files changed

+36
-0
lines changed

Tests/VariableAnalysisSniff/VariableAnalysisTest.php

+3
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ public function testFunctionWithReferenceWarnings() {
286286
59,
287287
60,
288288
64,
289+
81,
289290
];
290291
$this->assertEquals($expectedWarnings, $lines);
291292
}
@@ -312,6 +313,7 @@ public function testFunctionWithReferenceWarningsAllowsCustomFunctions() {
312313
40,
313314
46,
314315
64,
316+
81,
315317
];
316318
$this->assertEquals($expectedWarnings, $lines);
317319
}
@@ -339,6 +341,7 @@ public function testFunctionWithReferenceWarningsAllowsWordPressFunctionsIfSet()
339341
46,
340342
59,
341343
60,
344+
81,
342345
];
343346
$this->assertEquals($expectedWarnings, $lines);
344347
}

Tests/VariableAnalysisSniff/fixtures/FunctionWithReferenceFixture.php

+10
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,13 @@ function function_with_array_walk($userNameParts) {
7373
$value = ucfirst($value);
7474
});
7575
}
76+
77+
function function_with_foreach_with_reference($derivatives, $base_plugin_definition) {
78+
foreach ($derivatives as &$entry) {
79+
$entry .= $base_plugin_definition;
80+
}
81+
foreach ($derivatives as &$unused) { // unused variable
82+
$base_plugin_definition .= '1';
83+
}
84+
return $derivatives;
85+
}

VariableAnalysis/Lib/VariableInfo.php

+7
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ class VariableInfo {
3030
*/
3131
public $referencedVariableScope;
3232

33+
/**
34+
* True if the variable is a reference but one created at runtime
35+
*
36+
* @var bool
37+
*/
38+
public $isDynamicReference = false;
39+
3340
/**
3441
* Stack pointer of first declaration
3542
*

VariableAnalysis/Sniffs/CodeAnalysis/VariableAnalysisSniff.php

+16
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ protected function markVariableAssignment($varName, $stackPtr, $currScope) {
418418
return;
419419
}
420420
$varInfo->firstInitialized = $stackPtr;
421+
Helpers::debug('markVariableAssignment: marked as initialized', $varName);
421422
}
422423

423424
/**
@@ -944,6 +945,14 @@ protected function processVariableAsAssignment(File $phpcsFile, $stackPtr, $varN
944945

945946
Helpers::debug('processVariableAsAssignment: marking as assignment in scope', $currScope);
946947
$this->markVariableAssignment($varName, $stackPtr, $currScope);
948+
949+
// If the left-hand-side of the assignment (the variable we are examining)
950+
// is itself a reference, then that counts as a read as well as a write.
951+
$varInfo = $this->getOrCreateVariableInfo($varName, $currScope);
952+
if ($varInfo->isDynamicReference) {
953+
Helpers::debug('processVariableAsAssignment: also marking as a use because variable is a reference');
954+
$this->markVariableRead($varName, $stackPtr, $currScope);
955+
}
947956
}
948957

949958
/**
@@ -1164,6 +1173,13 @@ protected function processVariableAsForeachLoopVar(File $phpcsFile, $stackPtr, $
11641173
$varInfo->isForeachLoopAssociativeValue = true;
11651174
}
11661175

1176+
// Are we pass-by-reference?
1177+
$referencePtr = $phpcsFile->findPrevious(Tokens::$emptyTokens, $stackPtr - 1, null, true, null, true);
1178+
if (($referencePtr !== false) && ($tokens[$referencePtr]['code'] === T_BITWISE_AND)) {
1179+
Helpers::debug("processVariableAsForeachLoopVar: found foreach loop variable assigned by reference");
1180+
$varInfo->isDynamicReference = true;
1181+
}
1182+
11671183
return true;
11681184
}
11691185

0 commit comments

Comments
 (0)