Skip to content

Commit 646bcfa

Browse files
committed
Merge branch 'PHP-8.3'
* PHP-8.3: Fix GH-8996: DOMNode serialization on PHP ^8.1 Fix GH-12380: JIT+private array property access inside closure accesses private property in child class
2 parents 986e08e + 58a1103 commit 646bcfa

File tree

8 files changed

+267
-11
lines changed

8 files changed

+267
-11
lines changed

ext/dom/node.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2204,4 +2204,25 @@ disconnected:;
22042204
}
22052205
/* }}} */
22062206

2207+
/**
2208+
* We want to block the serialization and unserialization of DOM classes.
2209+
* However, using @not-serializable makes the child classes also not serializable, even if the user implements the methods.
2210+
* So instead, we implement the methods wherein we throw exceptions.
2211+
* The reason we choose these methods is because:
2212+
* - If the user implements __serialize / __unserialize, the respective throwing methods are not called.
2213+
* - If the user implements __sleep / __wakeup, then it's also not a problem because they will not enter the throwing methods.
2214+
*/
2215+
2216+
PHP_METHOD(DOMNode, __sleep)
2217+
{
2218+
zend_throw_exception_ex(NULL, 0, "Serialization of '%s' is not allowed, unless serialization methods are implemented in a subclass", ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name));
2219+
RETURN_THROWS();
2220+
}
2221+
2222+
PHP_METHOD(DOMNode, __wakeup)
2223+
{
2224+
zend_throw_exception_ex(NULL, 0, "Unserialization of '%s' is not allowed, unless unserialization methods are implemented in a subclass", ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name));
2225+
RETURN_THROWS();
2226+
}
2227+
22072228
#endif

ext/dom/php_dom.stub.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,6 @@ public function after(...$nodes): void;
294294
public function replaceWith(...$nodes): void;
295295
}
296296

297-
/** @not-serializable */
298297
class DOMNode
299298
{
300299
public const int DOCUMENT_POSITION_DISCONNECTED = 0x01;
@@ -355,6 +354,10 @@ class DOMNode
355354

356355
public string $textContent;
357356

357+
public function __sleep(): array {}
358+
359+
public function __wakeup(): void {}
360+
358361
/** @return DOMNode|false */
359362
public function appendChild(DOMNode $node) {}
360363

@@ -415,7 +418,6 @@ public function getRootNode(?array $options = null): DOMNode {}
415418
public function compareDocumentPosition(DOMNode $other): int {}
416419
}
417420

418-
/** @not-serializable */
419421
class DOMNameSpaceNode
420422
{
421423
/** @readonly */
@@ -447,6 +449,12 @@ class DOMNameSpaceNode
447449

448450
/** @readonly */
449451
public ?DOMElement $parentElement;
452+
453+
/** @implementation-alias DOMNode::__sleep */
454+
public function __sleep(): array {}
455+
456+
/** @implementation-alias DOMNode::__wakeup */
457+
public function __wakeup(): void {}
450458
}
451459

452460
class DOMImplementation

ext/dom/php_dom_arginfo.h

Lines changed: 17 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/dom/tests/gh8996.phpt

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
--TEST--
2+
GH-8996: DOMNode serialization on PHP ^8.1
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
echo "=== __sleep and __wakeup ===\n";
9+
10+
class SerializableDomDocumentSleepWakeup extends DOMDocument
11+
{
12+
private $xmlData;
13+
14+
public function __sleep(): array
15+
{
16+
$this->xmlData = $this->saveXML();
17+
return ['xmlData'];
18+
}
19+
20+
public function __wakeup(): void
21+
{
22+
$this->loadXML($this->xmlData);
23+
}
24+
}
25+
26+
$dom = new SerializableDomDocumentSleepWakeup('1.0', 'UTF-8');
27+
$dom->loadXML('<tag>value</tag>');
28+
29+
$serialized = serialize($dom);
30+
var_dump($serialized);
31+
$unserialized = unserialize($serialized);
32+
33+
echo "Serialized:\n-----------\n$serialized\n-----------\nRestored:\n-----------\n{$unserialized->saveXml()}";
34+
35+
echo "=== __serialize and __unserialize ===\n";
36+
37+
class SerializableDomDocument__Serialize__Unserialize extends DOMDocument
38+
{
39+
public function __serialize(): array
40+
{
41+
return ['xmlData' => $this->saveXML()];
42+
}
43+
44+
public function __unserialize(array $data): void
45+
{
46+
$this->loadXML($data['xmlData']);
47+
}
48+
}
49+
50+
$dom = new SerializableDomDocument__Serialize__Unserialize('1.0', 'UTF-8');
51+
$dom->loadXML('<tag>value</tag>');
52+
53+
$serialized = serialize($dom);
54+
$unserialized = unserialize($serialized);
55+
56+
echo "Serialized:\n-----------\n$serialized\n-----------\nRestored:\n-----------\n{$unserialized->saveXml()}";
57+
58+
echo "=== serialize and unserialize ===\n";
59+
60+
class SerializableDomDocumentSerializeUnserialize extends DOMDocument implements Serializable
61+
{
62+
public function serialize(): ?string
63+
{
64+
return $this->saveXML();
65+
}
66+
67+
public function unserialize(string $data): void
68+
{
69+
$this->loadXML($data);
70+
}
71+
}
72+
73+
$dom = new SerializableDomDocumentSerializeUnserialize('1.0', 'UTF-8');
74+
$dom->loadXML('<tag>value</tag>');
75+
76+
$serialized = serialize($dom);
77+
$unserialized = unserialize($serialized);
78+
79+
echo "Serialized:\n-----------\n$serialized\n-----------\nRestored:\n-----------\n{$unserialized->saveXml()}";
80+
81+
?>
82+
--EXPECTF--
83+
=== __sleep and __wakeup ===
84+
string(144) "O:34:"SerializableDomDocumentSleepWakeup":1:{s:43:"%0SerializableDomDocumentSleepWakeup%0xmlData";s:39:"<?xml version="1.0"?>
85+
<tag>value</tag>
86+
";}"
87+
Serialized:
88+
-----------
89+
O:34:"SerializableDomDocumentSleepWakeup":1:{s:43:"%0SerializableDomDocumentSleepWakeup%0xmlData";s:39:"<?xml version="1.0"?>
90+
<tag>value</tag>
91+
";}
92+
-----------
93+
Restored:
94+
-----------
95+
<?xml version="1.0"?>
96+
<tag>value</tag>
97+
=== __serialize and __unserialize ===
98+
Serialized:
99+
-----------
100+
O:47:"SerializableDomDocument__Serialize__Unserialize":1:{s:7:"xmlData";s:39:"<?xml version="1.0"?>
101+
<tag>value</tag>
102+
";}
103+
-----------
104+
Restored:
105+
-----------
106+
<?xml version="1.0"?>
107+
<tag>value</tag>
108+
=== serialize and unserialize ===
109+
110+
Deprecated: SerializableDomDocumentSerializeUnserialize implements the Serializable interface, which is deprecated. Implement __serialize() and __unserialize() instead (or in addition, if support for old PHP versions is necessary) in %s on line %d
111+
Serialized:
112+
-----------
113+
C:43:"SerializableDomDocumentSerializeUnserialize":39:{<?xml version="1.0"?>
114+
<tag>value</tag>
115+
}
116+
-----------
117+
Restored:
118+
-----------
119+
<?xml version="1.0"?>
120+
<tag>value</tag>

ext/dom/tests/not_serializable.phpt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ try {
3636

3737
?>
3838
--EXPECT--
39-
Serialization of 'DOMDocument' is not allowed
40-
Serialization of 'DOMElement' is not allowed
39+
Serialization of 'DOMDocument' is not allowed, unless serialization methods are implemented in a subclass
40+
Serialization of 'DOMElement' is not allowed, unless serialization methods are implemented in a subclass
4141
Serialization of 'DOMXPath' is not allowed
42-
Serialization of 'DOMNameSpaceNode' is not allowed
42+
Serialization of 'DOMNameSpaceNode' is not allowed, unless serialization methods are implemented in a subclass

ext/dom/tests/not_unserializable.phpt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
DOM classes are not unserializable
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
$classes = [
9+
"DOMXPath",
10+
"DOMDocument",
11+
"DOMNode",
12+
"DOMNameSpaceNode",
13+
];
14+
15+
foreach ($classes as $class)
16+
{
17+
try {
18+
unserialize('O:' . strlen($class) . ':"' . $class . '":0:{}');
19+
} catch (Exception $e) {
20+
echo $e->getMessage(), "\n";
21+
}
22+
}
23+
24+
?>
25+
--EXPECT--
26+
Unserialization of 'DOMXPath' is not allowed
27+
Unserialization of 'DOMDocument' is not allowed, unless unserialization methods are implemented in a subclass
28+
Unserialization of 'DOMNode' is not allowed, unless unserialization methods are implemented in a subclass
29+
Unserialization of 'DOMNameSpaceNode' is not allowed, unless unserialization methods are implemented in a subclass

ext/opcache/jit/zend_jit.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,11 @@ static zend_property_info* zend_get_known_property_info(const zend_op_array *op_
682682
return info;
683683
} else if (on_this) {
684684
if (ce == info->ce) {
685-
return info;
685+
if (ce == op_array->scope) {
686+
return info;
687+
} else {
688+
return NULL;
689+
}
686690
} else if ((info->flags & ZEND_ACC_PROTECTED)
687691
&& instanceof_function_slow(ce, info->ce)) {
688692
return info;

ext/opcache/tests/jit/gh12380.phpt

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
--TEST--
2+
GH-12380: JIT+private array property access inside closure accesses private property in child class
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.file_update_protection=0
7+
opcache.jit_buffer_size=1M
8+
opcache.protect_memory=1
9+
opcache.jit=tracing
10+
opcache.jit_hot_loop=1
11+
opcache.jit_hot_func=1
12+
opcache.jit_hot_return=1
13+
opcache.jit_hot_side_exit=1
14+
--EXTENSIONS--
15+
opcache
16+
--FILE--
17+
<?php
18+
19+
abstract class a
20+
{
21+
private int $v = 1;
22+
23+
public function test(): void
24+
{
25+
var_dump($this->v);
26+
(function (): void {
27+
var_dump($this->v);
28+
})();
29+
}
30+
}
31+
32+
final class b extends a {
33+
private int $v = 0;
34+
}
35+
$a = new b;
36+
37+
for ($i = 0; $i < 10; $i++) {
38+
$a->test();
39+
}
40+
41+
?>
42+
--EXPECT--
43+
int(1)
44+
int(1)
45+
int(1)
46+
int(1)
47+
int(1)
48+
int(1)
49+
int(1)
50+
int(1)
51+
int(1)
52+
int(1)
53+
int(1)
54+
int(1)
55+
int(1)
56+
int(1)
57+
int(1)
58+
int(1)
59+
int(1)
60+
int(1)
61+
int(1)
62+
int(1)

0 commit comments

Comments
 (0)