Commit c06e7370ae5 for php
commit c06e7370ae58ca8e132aae162c86bdaf40bc394d
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Sun Sep 13 14:18:59 2026 -0400
ext/opcache: Fix GH-23679 tracing JIT shadowed private writes
zend_get_known_property_info treated a ZEND_ACC_CHANGED public property as
the known offset even when the executing scope owned a private property of
the same name, so tracing JIT wrote through a child's public slot.
Returning NULL uses the runtime-cache path, which already goes through
zend_get_property_offset. FETCH_OBJ_W, ASSIGN_OBJ, INC, and ASSIGN_OBJ_OP
share the helper; $this access, child methods, and unshadowed or
protected-to-public properties do not hit this arm.
Fixes GH-23679
Closes GH-23683
diff --git a/NEWS b/NEWS
index 4f963f08f1f..23ce2db98b9 100644
--- a/NEWS
+++ b/NEWS
@@ -64,6 +64,10 @@ PHP NEWS
. Fixed OSS-Fuzz #5674034779193344 (Read of uninitialized memory in
is_cacheable_stream_path()). (ndossche)
. Fix zend_analyze_calls() call_stack buffer overrun. (Mrmaxmeier)
+ . Fixed bug GH-23628 (Tracing JIT reads undefined property slots of lazy
+ proxy objects instead of forwarding to the real instance). (lisachenko)
+ . Fixed bug GH-23679 (Tracing JIT writes a parent private property into a
+ child's shadowing public property). (Ilia Alshanetsky)
- PDO:
. Fixed PDOStatement::getColumnMeta() reading out of bounds for an invalid
@@ -98,10 +102,6 @@ PHP NEWS
. Fixed inflate_init() dropping the preset dictionary for raw streams with
a non-default window. (Ilia Alshanetsky)
-- Opcache:
- . Fixed bug GH-23628 (Tracing JIT reads undefined property slots of lazy
- proxy objects instead of forwarding to the real instance). (lisachenko)
-
24 Sep 2026, PHP 8.5.11
diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c
index 76510743d33..1e95d80456b 100644
--- a/ext/opcache/jit/zend_jit.c
+++ b/ext/opcache/jit/zend_jit.c
@@ -650,6 +650,11 @@ static zend_property_info* zend_get_known_property_info(const zend_op_array *op_
}
if (info->flags & ZEND_ACC_PUBLIC) {
+ if ((info->flags & ZEND_ACC_CHANGED)
+ && op_array->scope
+ && op_array->scope != ce) {
+ return NULL;
+ }
return info;
} else if (on_this) {
if (ce == info->ce) {
diff --git a/ext/opcache/tests/jit/gh23679.phpt b/ext/opcache/tests/jit/gh23679.phpt
new file mode 100644
index 00000000000..8b3c65c6567
--- /dev/null
+++ b/ext/opcache/tests/jit/gh23679.phpt
@@ -0,0 +1,116 @@
+--TEST--
+GH-23679: tracing JIT must not write a parent private property into a child's shadowing public property
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_update_protection=0
+opcache.protect_memory=1
+opcache.jit=tracing
+opcache.jit_hot_loop=1
+opcache.jit_hot_func=1
+opcache.jit_hot_return=1
+opcache.jit_hot_side_exit=1
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+class A
+{
+ private $arr = [];
+ private $n = 0;
+
+ public function setDim($k, $v)
+ {
+ $x = clone $this;
+ $x->arr[$k] = $v;
+ return $x;
+ }
+
+ public function setAll($v)
+ {
+ $x = clone $this;
+ $x->arr = $v;
+ return $x;
+ }
+
+ public function inc()
+ {
+ $x = clone $this;
+ $x->n++;
+ return $x;
+ }
+
+ public function addN($k)
+ {
+ $x = clone $this;
+ $x->n += $k;
+ return $x;
+ }
+
+ public function getDim($k)
+ {
+ return $this->arr[$k] ?? 'MISSING';
+ }
+
+ public function getN()
+ {
+ return $this->n;
+ }
+}
+
+class B extends A
+{
+ public $arr = [];
+ public $n = 100;
+}
+
+for ($i = 0; $i < 50; $i++) {
+ (new A)->setDim('x', 1)->getDim('x');
+ (new A)->setAll(['x' => 1])->getDim('x');
+ (new A)->inc()->getN();
+ (new A)->addN(5)->getN();
+
+ $b = new B;
+ $b->arr = ['keep' => 1];
+ $b->n = 100;
+
+ $r = $b->setDim('x', 2);
+ if ($r->getDim('x') !== 2 || $r->arr !== ['keep' => 1]) {
+ echo "dim-assign i=$i private=";
+ var_dump($r->getDim('x'));
+ echo "dim-assign i=$i public=";
+ var_dump($r->arr);
+ exit(1);
+ }
+
+ $r = $b->setAll(['y' => 4]);
+ if ($r->getDim('y') !== 4 || $r->arr !== ['keep' => 1]) {
+ echo "assign i=$i private=";
+ var_dump($r->getDim('y'));
+ echo "assign i=$i public=";
+ var_dump($r->arr);
+ exit(1);
+ }
+
+ $r = $b->inc();
+ if ($r->getN() !== 1 || $r->n !== 100) {
+ echo "inc i=$i private=";
+ var_dump($r->getN());
+ echo "inc i=$i public=";
+ var_dump($r->n);
+ exit(1);
+ }
+
+ $r = $b->addN(5);
+ if ($r->getN() !== 5 || $r->n !== 100) {
+ echo "assign-op i=$i private=";
+ var_dump($r->getN());
+ echo "assign-op i=$i public=";
+ var_dump($r->n);
+ exit(1);
+ }
+}
+echo "ok\n";
+?>
+--EXPECT--
+ok