From b0f9ca2444ce05a2c4372c74d90bebb0af4a0c95 Mon Sep 17 00:00:00 2001 From: PurHur Date: Sat, 23 May 2026 18:51:44 +0000 Subject: [PATCH] Fix VM ?: merge blocks and document pre/post inc (#137) CFG join blocks (ternary, branch merges) use sparse scope slot indices. Variadic Frame construction reindexed those slots, so echo after ?: printed empty output and setcookie return values were lost. Preserve slot indices on merge blocks only; resolve merge operands via findSlot on the merge block. Add compliance PHPTs for ++/-- and a unit gate. Co-authored-by: Cursor --- lib/Block.php | 20 ++++++++++-- .../cases/language/pre_post_dec.phpt | 13 +++----- .../cases/language/pre_post_inc.phpt | 6 +--- test/unit/FrameScopeSlotTest.php | 31 +++++++++++++++++++ 4 files changed, 54 insertions(+), 16 deletions(-) create mode 100644 test/unit/FrameScopeSlotTest.php diff --git a/lib/Block.php b/lib/Block.php index 96f60b313..4e8f8eadb 100755 --- a/lib/Block.php +++ b/lib/Block.php @@ -274,6 +274,7 @@ private static function findVariableInParentFrames(Operand $op, Frame $frame): ? public function getFrame(Context $context, ?Frame $frame = null): Frame { // Todo: build scope $scope = []; + $cfgMerge = count($this->parents) > 1; $scopeSize = $this->scope->count(); foreach ($this->scope as $op) { $pos = $this->scope[$op]; @@ -296,7 +297,9 @@ public function getFrame(Context $context, ?Frame $frame = null): Frame { continue; } $found = false; - $parent = $frame->block->findSlot($op, $frame); + $parent = $cfgMerge + ? $this->findSlot($op, $frame) + : $frame->block->findSlot($op, $frame); if (!is_null($parent)) { $scope[$pos] = $parent; $found = true; @@ -327,6 +330,13 @@ public function getFrame(Context $context, ?Frame $frame = null): Frame { $scope[$pos] = $inherited; continue; } + if ($cfgMerge) { + $fromJump = $this->findSlot($op, $frame); + if (null !== $fromJump) { + $scope[$pos] = $fromJump; + continue; + } + } } if ( $this->inheritUndefinedLocals @@ -340,7 +350,13 @@ public function getFrame(Context $context, ?Frame $frame = null): Frame { } } - $return = new Frame(null, $this, $frame, ...$scope); + // CFG merge blocks (?:, if/else join) can use sparse slot indices; variadic spread reindexes (#137). + if ($cfgMerge) { + $return = new Frame(null, $this, $frame); + $return->scope = $scope; + } else { + $return = new Frame(null, $this, $frame, ...$scope); + } $return->scriptPath = $this->scriptPath(); if (!is_null($frame) && !is_null($frame->returnVar)) { $return->returnVar = $frame->returnVar; diff --git a/test/compliance/cases/language/pre_post_dec.phpt b/test/compliance/cases/language/pre_post_dec.phpt index 100c23513..fc1d78d96 100644 --- a/test/compliance/cases/language/pre_post_dec.phpt +++ b/test/compliance/cases/language/pre_post_dec.phpt @@ -1,14 +1,9 @@ --TEST-- -Pre/post decrement on loop counter (VM) +language pre/post decrement (issue #137) --FILE-- markTestSkipped('bin/vm.php missing'); + } + $cmd = array_merge([PHP_BINARY, $vm, '-r', 'echo true ? "yes" : "no";']); + $pipes = []; + $proc = proc_open($cmd, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes, $repoRoot); + $this->assertIsResource($proc); + $stdout = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + $this->assertSame(0, proc_close($proc)); + $this->assertSame('yes', $stdout); + } +}