Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -12325,7 +12325,9 @@ private function isErrorSuppressInnerExpr(Op $child): bool
|| $child instanceof Op\Expr\Include_
|| $child instanceof Op\Expr\ArrayDimFetch
|| $child instanceof Op\Expr\Isset_
|| $child instanceof Op\Expr\Empty_;
|| $child instanceof Op\Expr\Empty_
// `@$cv` materializes via Assign under silence (#13587 / #29132).
|| $child instanceof Op\Expr\Assign;
}

/**
Expand Down
10 changes: 8 additions & 2 deletions patches/php-cfg-bare-variable-read-stmt.patch
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,24 @@
}

protected function parseStmt_Class(Stmt\Class_ $node)
@@ -1417,8 +1426,16 @@
@@ -1417,8 +1426,24 @@
$block->addParent($this->block);
$this->block = $block;
$result = $this->parseExprNode($expr->expr);
- // Evaluate reads inside the suppress region (issue #3546; Zend ZEND_COMPILE_SILENCE).
- $result = $this->readVariable($result);
+ // Bare variable reads need a real fetch op for ZEND_CHECK_UNDEFINED_VAR (#13587).
+ // Materialize into a temp under silence so `$a = @$undef` does not re-fetch the CV
+ // after END_SILENCE (#29132). UnaryPlus would coerce null→int(0); Assign preserves null.
+ if ($expr->expr instanceof Node\Expr\Variable) {
+ $this->block->children[] = new Op\Expr\UnaryPlus(
+ $tmp = new Temporary();
+ $assign = new Op\Expr\Assign(
+ $tmp,
+ $this->readVariable($result),
+ $attrs
+ );
+ $this->block->children[] = $assign;
+ $result = $assign->result;
+ } else {
+ // Evaluate reads inside the suppress region (issue #3546; Zend ZEND_COMPILE_SILENCE).
+ $result = $this->readVariable($result);
Expand Down
4 changes: 4 additions & 0 deletions test/compliance/ErrorSuppressJITTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ public static function providePHPTests(): \Generator
__DIR__.'/cases/language/error_suppress_jit.phpt',
'error_suppress_jit.phpt'
);
yield 'at_silence_assign_undef_rhs.phpt' => self::parsePHPT(
__DIR__.'/cases/language/at_silence_assign_undef_rhs.phpt',
'at_silence_assign_undef_rhs.phpt'
);
}

public function setUp(): void
Expand Down
16 changes: 16 additions & 0 deletions test/compliance/cases/language/at_silence_assign_undef_rhs.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
Language: @$undef as assignment RHS is silenced; value remains null (#29132)
--FILE--
<?php
error_reporting(E_ALL);
$a = @$undef_assign_rhs_29132;
var_dump($a);
echo "ok\n";
@$bare_undef_29132;
$b = [];
$c = @$b['missing_29132'];
echo "ok2\n";
--EXPECT--
NULL
ok
ok2
29 changes: 29 additions & 0 deletions test/unit/ErrorControlOperatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,34 @@ public static function providePHPTests(): \Generator
__DIR__.'/../compliance/cases/language/error_control_operator.phpt',
'error_control_operator.phpt'
);
yield 'at_silence_assign_undef_rhs.phpt' => self::parsePHPT(
__DIR__.'/../compliance/cases/language/at_silence_assign_undef_rhs.phpt',
'at_silence_assign_undef_rhs.phpt'
);
}

/** Issue #29132 — `$a = @$undef` must not print Undefined variable on stderr. */
public function testAssignUndefRhsSilenceLeavesStderrEmpty(): void
{
$code = <<<'PHP'
<?php
error_reporting(E_ALL);
$a = @$undef_assign_rhs_29132_stderr;
echo "ok\n";
PHP;
$repoRoot = dirname(__DIR__, 2);
$env = [];
foreach (array_merge($_ENV, $_SERVER) as $key => $value) {
if (is_string($value)) {
$env[$key] = $value;
}
}
self::applyLlvmToolchainEnv($env);
unset($env['PHP_COMPILER_SKIP_LLVM_PRELOAD']);
$cmd = array_merge(self::llvmEnvPrefix(), $this->phpCommand(), [$this->BIN]);
[$stdout, $exitCode, $stderr] = self::runVmSubprocess($cmd, $repoRoot, $env, $code, __FUNCTION__);
$this->assertSame(0, $exitCode, $stderr);
$this->assertSame("ok\n", $stdout);
$this->assertStringNotContainsString('Undefined variable', $stderr);
}
}
Loading