Skip to content

Commit

Permalink
Merge branch 'PHP-8.1' into PHP-8.2
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
nielsdos committed Oct 9, 2023
2 parents 5a276bf + 24e5e4e commit 5e1058b
Show file tree
Hide file tree
Showing 9 changed files with 269 additions and 9 deletions.
3 changes: 3 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ PHP NEWS

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

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

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

- PCRE:
. Fixed bug GH-11956 (Backport upstream fix, PCRE regular expressions with
Expand Down
21 changes: 21 additions & 0 deletions ext/dom/node.c
Original file line number Diff line number Diff line change
Expand Up @@ -1786,4 +1786,25 @@ PHP_METHOD(DOMNode, getLineNo)
}
/* }}} */

/**
* We want to block the serialization and unserialization of DOM classes.
* However, using @not-serializable makes the child classes also not serializable, even if the user implements the methods.
* So instead, we implement the methods wherein we throw exceptions.
* The reason we choose these methods is because:
* - If the user implements __serialize / __unserialize, the respective throwing methods are not called.
* - If the user implements __sleep / __wakeup, then it's also not a problem because they will not enter the throwing methods.
*/

PHP_METHOD(DOMNode, __sleep)
{
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));
RETURN_THROWS();
}

PHP_METHOD(DOMNode, __wakeup)
{
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));
RETURN_THROWS();
}

#endif
12 changes: 10 additions & 2 deletions ext/dom/php_dom.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,6 @@ public function after(...$nodes): void;
public function replaceWith(...$nodes): void;
}

/** @not-serializable */
class DOMNode
{
/** @readonly */
Expand Down Expand Up @@ -339,6 +338,10 @@ class DOMNode

public string $textContent;

public function __sleep(): array {}

public function __wakeup(): void {}

/** @return DOMNode|false */
public function appendChild(DOMNode $node) {}

Expand Down Expand Up @@ -391,7 +394,6 @@ public function removeChild(DOMNode $child) {}
public function replaceChild(DOMNode $node, DOMNode $child) {}
}

/** @not-serializable */
class DOMNameSpaceNode
{
/** @readonly */
Expand All @@ -417,6 +419,12 @@ class DOMNameSpaceNode

/** @readonly */
public ?DOMNode $parentNode;

/** @implementation-alias DOMNode::__sleep */
public function __sleep(): array {}

/** @implementation-alias DOMNode::__wakeup */
public function __wakeup(): void {}
}

class DOMImplementation
Expand Down
19 changes: 16 additions & 3 deletions ext/dom/php_dom_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

120 changes: 120 additions & 0 deletions ext/dom/tests/gh8996.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
--TEST--
GH-8996: DOMNode serialization on PHP ^8.1
--EXTENSIONS--
dom
--FILE--
<?php

echo "=== __sleep and __wakeup ===\n";

class SerializableDomDocumentSleepWakeup extends DOMDocument
{
private $xmlData;

public function __sleep(): array
{
$this->xmlData = $this->saveXML();
return ['xmlData'];
}

public function __wakeup(): void
{
$this->loadXML($this->xmlData);
}
}

$dom = new SerializableDomDocumentSleepWakeup('1.0', 'UTF-8');
$dom->loadXML('<tag>value</tag>');

$serialized = serialize($dom);
var_dump($serialized);
$unserialized = unserialize($serialized);

echo "Serialized:\n-----------\n$serialized\n-----------\nRestored:\n-----------\n{$unserialized->saveXml()}";

echo "=== __serialize and __unserialize ===\n";

class SerializableDomDocument__Serialize__Unserialize extends DOMDocument
{
public function __serialize(): array
{
return ['xmlData' => $this->saveXML()];
}

public function __unserialize(array $data): void
{
$this->loadXML($data['xmlData']);
}
}

$dom = new SerializableDomDocument__Serialize__Unserialize('1.0', 'UTF-8');
$dom->loadXML('<tag>value</tag>');

$serialized = serialize($dom);
$unserialized = unserialize($serialized);

echo "Serialized:\n-----------\n$serialized\n-----------\nRestored:\n-----------\n{$unserialized->saveXml()}";

echo "=== serialize and unserialize ===\n";

class SerializableDomDocumentSerializeUnserialize extends DOMDocument implements Serializable
{
public function serialize(): ?string
{
return $this->saveXML();
}

public function unserialize(string $data): void
{
$this->loadXML($data);
}
}

$dom = new SerializableDomDocumentSerializeUnserialize('1.0', 'UTF-8');
$dom->loadXML('<tag>value</tag>');

$serialized = serialize($dom);
$unserialized = unserialize($serialized);

echo "Serialized:\n-----------\n$serialized\n-----------\nRestored:\n-----------\n{$unserialized->saveXml()}";

?>
--EXPECTF--
=== __sleep and __wakeup ===
string(144) "O:34:"SerializableDomDocumentSleepWakeup":1:{s:43:"%0SerializableDomDocumentSleepWakeup%0xmlData";s:39:"<?xml version="1.0"?>
<tag>value</tag>
";}"
Serialized:
-----------
O:34:"SerializableDomDocumentSleepWakeup":1:{s:43:"%0SerializableDomDocumentSleepWakeup%0xmlData";s:39:"<?xml version="1.0"?>
<tag>value</tag>
";}
-----------
Restored:
-----------
<?xml version="1.0"?>
<tag>value</tag>
=== __serialize and __unserialize ===
Serialized:
-----------
O:47:"SerializableDomDocument__Serialize__Unserialize":1:{s:7:"xmlData";s:39:"<?xml version="1.0"?>
<tag>value</tag>
";}
-----------
Restored:
-----------
<?xml version="1.0"?>
<tag>value</tag>
=== serialize and unserialize ===

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
Serialized:
-----------
C:43:"SerializableDomDocumentSerializeUnserialize":39:{<?xml version="1.0"?>
<tag>value</tag>
}
-----------
Restored:
-----------
<?xml version="1.0"?>
<tag>value</tag>
6 changes: 3 additions & 3 deletions ext/dom/tests/not_serializable.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ try {

?>
--EXPECT--
Serialization of 'DOMDocument' is not allowed
Serialization of 'DOMElement' is not allowed
Serialization of 'DOMDocument' is not allowed, unless serialization methods are implemented in a subclass
Serialization of 'DOMElement' is not allowed, unless serialization methods are implemented in a subclass
Serialization of 'DOMXPath' is not allowed
Serialization of 'DOMNameSpaceNode' is not allowed
Serialization of 'DOMNameSpaceNode' is not allowed, unless serialization methods are implemented in a subclass
29 changes: 29 additions & 0 deletions ext/dom/tests/not_unserializable.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
--TEST--
DOM classes are not unserializable
--EXTENSIONS--
dom
--FILE--
<?php

$classes = [
"DOMXPath",
"DOMDocument",
"DOMNode",
"DOMNameSpaceNode",
];

foreach ($classes as $class)
{
try {
unserialize('O:' . strlen($class) . ':"' . $class . '":0:{}');
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}
}

?>
--EXPECT--
Unserialization of 'DOMXPath' is not allowed
Unserialization of 'DOMDocument' is not allowed, unless unserialization methods are implemented in a subclass
Unserialization of 'DOMNode' is not allowed, unless unserialization methods are implemented in a subclass
Unserialization of 'DOMNameSpaceNode' is not allowed, unless unserialization methods are implemented in a subclass
6 changes: 5 additions & 1 deletion ext/opcache/jit/zend_jit.c
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,11 @@ static zend_property_info* zend_get_known_property_info(const zend_op_array *op_
return info;
} else if (on_this) {
if (ce == info->ce) {
return info;
if (ce == op_array->scope) {
return info;
} else {
return NULL;
}
} else if ((info->flags & ZEND_ACC_PROTECTED)
&& instanceof_function_slow(ce, info->ce)) {
return info;
Expand Down
62 changes: 62 additions & 0 deletions ext/opcache/tests/jit/gh12380.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
--TEST--
GH-12380: JIT+private array property access inside closure accesses private property in child class
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.file_update_protection=0
opcache.jit_buffer_size=1M
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

abstract class a
{
private int $v = 1;

public function test(): void
{
var_dump($this->v);
(function (): void {
var_dump($this->v);
})();
}
}

final class b extends a {
private int $v = 0;
}
$a = new b;

for ($i = 0; $i < 10; $i++) {
$a->test();
}

?>
--EXPECT--
int(1)
int(1)
int(1)
int(1)
int(1)
int(1)
int(1)
int(1)
int(1)
int(1)
int(1)
int(1)
int(1)
int(1)
int(1)
int(1)
int(1)
int(1)
int(1)
int(1)

0 comments on commit 5e1058b

Please sign in to comment.