Skip to content

Commit c06e737

Browse files
committed
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
1 parent a8927bc commit c06e737

3 files changed

Lines changed: 125 additions & 4 deletions

File tree

‎NEWS‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ PHP NEWS
6464
. Fixed OSS-Fuzz #5674034779193344 (Read of uninitialized memory in
6565
is_cacheable_stream_path()). (ndossche)
6666
. Fix zend_analyze_calls() call_stack buffer overrun. (Mrmaxmeier)
67+
. Fixed bug GH-23628 (Tracing JIT reads undefined property slots of lazy
68+
proxy objects instead of forwarding to the real instance). (lisachenko)
69+
. Fixed bug GH-23679 (Tracing JIT writes a parent private property into a
70+
child's shadowing public property). (Ilia Alshanetsky)
6771

6872
- PDO:
6973
. Fixed PDOStatement::getColumnMeta() reading out of bounds for an invalid
@@ -98,10 +102,6 @@ PHP NEWS
98102
. Fixed inflate_init() dropping the preset dictionary for raw streams with
99103
a non-default window. (Ilia Alshanetsky)
100104

101-
- Opcache:
102-
. Fixed bug GH-23628 (Tracing JIT reads undefined property slots of lazy
103-
proxy objects instead of forwarding to the real instance). (lisachenko)
104-
105105

106106
24 Sep 2026, PHP 8.5.11
107107

‎ext/opcache/jit/zend_jit.c‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,11 @@ static zend_property_info* zend_get_known_property_info(const zend_op_array *op_
650650
}
651651

652652
if (info->flags & ZEND_ACC_PUBLIC) {
653+
if ((info->flags & ZEND_ACC_CHANGED)
654+
&& op_array->scope
655+
&& op_array->scope != ce) {
656+
return NULL;
657+
}
653658
return info;
654659
} else if (on_this) {
655660
if (ce == info->ce) {

‎ext/opcache/tests/jit/gh23679.phpt‎

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
--TEST--
2+
GH-23679: tracing JIT must not write a parent private property into a child's shadowing public property
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.file_update_protection=0
7+
opcache.protect_memory=1
8+
opcache.jit=tracing
9+
opcache.jit_hot_loop=1
10+
opcache.jit_hot_func=1
11+
opcache.jit_hot_return=1
12+
opcache.jit_hot_side_exit=1
13+
--EXTENSIONS--
14+
opcache
15+
--FILE--
16+
<?php
17+
class A
18+
{
19+
private $arr = [];
20+
private $n = 0;
21+
22+
public function setDim($k, $v)
23+
{
24+
$x = clone $this;
25+
$x->arr[$k] = $v;
26+
return $x;
27+
}
28+
29+
public function setAll($v)
30+
{
31+
$x = clone $this;
32+
$x->arr = $v;
33+
return $x;
34+
}
35+
36+
public function inc()
37+
{
38+
$x = clone $this;
39+
$x->n++;
40+
return $x;
41+
}
42+
43+
public function addN($k)
44+
{
45+
$x = clone $this;
46+
$x->n += $k;
47+
return $x;
48+
}
49+
50+
public function getDim($k)
51+
{
52+
return $this->arr[$k] ?? 'MISSING';
53+
}
54+
55+
public function getN()
56+
{
57+
return $this->n;
58+
}
59+
}
60+
61+
class B extends A
62+
{
63+
public $arr = [];
64+
public $n = 100;
65+
}
66+
67+
for ($i = 0; $i < 50; $i++) {
68+
(new A)->setDim('x', 1)->getDim('x');
69+
(new A)->setAll(['x' => 1])->getDim('x');
70+
(new A)->inc()->getN();
71+
(new A)->addN(5)->getN();
72+
73+
$b = new B;
74+
$b->arr = ['keep' => 1];
75+
$b->n = 100;
76+
77+
$r = $b->setDim('x', 2);
78+
if ($r->getDim('x') !== 2 || $r->arr !== ['keep' => 1]) {
79+
echo "dim-assign i=$i private=";
80+
var_dump($r->getDim('x'));
81+
echo "dim-assign i=$i public=";
82+
var_dump($r->arr);
83+
exit(1);
84+
}
85+
86+
$r = $b->setAll(['y' => 4]);
87+
if ($r->getDim('y') !== 4 || $r->arr !== ['keep' => 1]) {
88+
echo "assign i=$i private=";
89+
var_dump($r->getDim('y'));
90+
echo "assign i=$i public=";
91+
var_dump($r->arr);
92+
exit(1);
93+
}
94+
95+
$r = $b->inc();
96+
if ($r->getN() !== 1 || $r->n !== 100) {
97+
echo "inc i=$i private=";
98+
var_dump($r->getN());
99+
echo "inc i=$i public=";
100+
var_dump($r->n);
101+
exit(1);
102+
}
103+
104+
$r = $b->addN(5);
105+
if ($r->getN() !== 5 || $r->n !== 100) {
106+
echo "assign-op i=$i private=";
107+
var_dump($r->getN());
108+
echo "assign-op i=$i public=";
109+
var_dump($r->n);
110+
exit(1);
111+
}
112+
}
113+
echo "ok\n";
114+
?>
115+
--EXPECT--
116+
ok

0 commit comments

Comments
 (0)