fix(optimizer): reject foreach-bound loop counters

master
韩天峰 10 hours ago
parent a4820ff759
commit aa1da2eb54
  1. 37
      phpunit/src/SsaAnalysisTest.php
  2. 11
      src/Optimizer/LoopVarOptimizer.php
  3. 43
      tests/compiler/optimizations/loop-counter-reused-by-foreach.phpt

@ -1241,6 +1241,43 @@ class SsaAnalysisTest extends TestCase
$this->assertArrayNotHasKey('n', $locals);
}
public function testLoopVarOptimizerRejectsCounterReusedAsForeachKey(): void
{
$locals = $this->optimizeLoopVarsForCode('
foreach (["name" => 1] as $i => $value) {
echo $value;
}
for ($i = 0; $i < 10; $i++) {
echo $i;
}
');
$this->assertArrayNotHasKey('i', $locals);
}
public function testLoopVarOptimizerRejectsCounterReusedAsForeachValueOrDestructuringTarget(): void
{
$valueLocals = $this->optimizeLoopVarsForCode('
foreach (["value"] as $i) {
echo $i;
}
for ($i = 0; $i < 10; $i++) {
echo $i;
}
');
$listLocals = $this->optimizeLoopVarsForCode('
foreach ([["value"]] as [$i]) {
echo $i;
}
for ($i = 0; $i < 10; $i++) {
echo $i;
}
');
$this->assertArrayNotHasKey('i', $valueLocals);
$this->assertArrayNotHasKey('i', $listLocals);
}
// ========================================================================
// SsaTypeOptimizer: detectSsaDefType
// ========================================================================

@ -758,6 +758,17 @@ trait LoopVarOptimizer
}
}
if ($expr instanceof Stmt\Foreach_) {
// Foreach key/value variables are assigned implicitly on every
// iteration. A name that is also used by a range-proven `for`
// counter cannot share a function-scoped php::Int slot with an
// arbitrary key or value (including list destructuring targets).
if (($expr->keyVar instanceof Node && $this->exprUsesVar($expr->keyVar, $varName))
|| $this->exprUsesVar($expr->valueVar, $varName)) {
return true;
}
}
foreach ($expr->getSubNodeNames() as $name) {
$value = $expr->$name;
if ($value instanceof Node) {

@ -0,0 +1,43 @@
--TEST--
Loop counter narrowing rejects variables previously assigned by foreach
--FILE--
<?php
function main(): void
{
foreach (['name' => 10, 20 => 30] as $i => $value) {
var_dump($i, $value);
}
for ($i = 0; $i < 2; $i++) {
var_dump($i);
}
foreach (['text'] as $i) {
var_dump($i);
}
for ($i = 0; $i < 1; $i++) {
var_dump($i);
}
foreach ([['nested']] as [$i]) {
var_dump($i);
}
for ($i = 0; $i < 1; $i++) {
var_dump($i);
}
}
?>
--EXPECT--
string(4) "name"
int(10)
int(20)
int(30)
int(0)
int(1)
string(4) "text"
int(0)
string(6) "nested"
int(0)
Loading…
Cancel
Save