Skip to content

Commit 5e1058b

Browse files
committed
Merge branch 'PHP-8.1' into PHP-8.2
* PHP-8.1: 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 5a276bf + 24e5e4e commit 5e1058b

File tree

9 files changed

+269
-9
lines changed

9 files changed

+269
-9
lines changed

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ PHP NEWS
1919

2020
- DOM:
2121
. Restore old namespace reconciliation behaviour. (nielsdos)
22+
. Fixed bug GH-8996 (DOMNode serialization on PHP ^8.1). (nielsdos)
2223

2324
- Fileinfo:
2425
. Fixed bug GH-11891 (fileinfo returns text/xml for some svg files). (usarise)
@@ -42,6 +43,8 @@ PHP NEWS
4243

4344
- Opcache:
4445
. Fixed opcache_invalidate() on deleted file. (mikhainin)
46+
. Fixed bug GH-12380 (JIT+private array property access inside closure
47+
accesses private property in child class). (nielsdos)
4548

4649
- PCRE:
4750
. Fixed bug GH-11956 (Backport upstream fix, PCRE regular expressions with

ext/dom/node.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1786,4 +1786,25 @@ PHP_METHOD(DOMNode, getLineNo)
17861786
}
17871787
/* }}} */
17881788

1789+
/**
1790+
* We want to block the serialization and unserialization of DOM classes.
1791+
* However, using @not-serializable makes the child classes also not serializable, even if the user implements the methods.
1792+
* So instead, we implement the methods wherein we throw exceptions.
1793+
* The reason we choose these methods is because:
1794+
* - If the user implements __serialize / __unserialize, the respective throwing methods are not called.
1795+
* - If the user implements __sleep / __wakeup, then it's also not a problem because they will not enter the throwing methods.
1796+
*/
1797+
1798+
PHP_METHOD(DOMNode, __sleep)
1799+
{
1800+
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));
1801+
RETURN_THROWS();
1802+
}
1803+
1804+
PHP_METHOD(DOMNode, __wakeup)
1805+
{
1806+
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));
1807+
RETURN_THROWS();
1808+
}
1809+
17891810
#endif

ext/dom/php_dom.stub.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,6 @@ public function after(...$nodes): void;
291291
public function replaceWith(...$nodes): void;
292292
}
293293

294-
/** @not-serializable */
295294
class DOMNode
296295
{
297296
/** @readonly */
@@ -339,6 +338,10 @@ class DOMNode
339338

340339
public string $textContent;
341340

341+
public function __sleep(): array {}
342+
343+
public function __wakeup(): void {}
344+
342345
/** @return DOMNode|false */
343346
public function appendChild(DOMNode $node) {}
344347

@@ -391,7 +394,6 @@ public function removeChild(DOMNode $child) {}
391394
public function replaceChild(DOMNode $node, DOMNode $child) {}
392395
}
393396

394-
/** @not-serializable */
395397
class DOMNameSpaceNode
396398
{
397399
/** @readonly */
@@ -417,6 +419,12 @@ class DOMNameSpaceNode
417419

418420
/** @readonly */
419421
public ?DOMNode $parentNode;
422+
423+
/** @implementation-alias DOMNode::__sleep */
424+
public function __sleep(): array {}
425+
426+
/** @implementation-alias DOMNode::__wakeup */
427+
public function __wakeup(): void {}
420428
}
421429

422430
class DOMImplementation

ext/dom/php_dom_arginfo.h

Lines changed: 16 additions & 3 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
@@ -655,7 +655,11 @@ static zend_property_info* zend_get_known_property_info(const zend_op_array *op_
655655
return info;
656656
} else if (on_this) {
657657
if (ce == info->ce) {
658-
return info;
658+
if (ce == op_array->scope) {
659+
return info;
660+
} else {
661+
return NULL;
662+
}
659663
} else if ((info->flags & ZEND_ACC_PROTECTED)
660664
&& instanceof_function_slow(ce, info->ce)) {
661665
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)