Merge branch 'PHP-8.2' into PHP-8.3

* PHP-8.2:
  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
This commit is contained in:
Niels Dossche 2023-10-09 22:12:05 +02:00
commit 58a1103bee
9 changed files with 270 additions and 11 deletions

3
NEWS
View file

@ -17,6 +17,7 @@ PHP NEWS
. Restore old namespace reconciliation behaviour. (nielsdos)
. Fix broken cache invalidation with deallocated and reallocated document
node. (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)
@ -31,6 +32,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)
- SimpleXML:
. Apply iterator fixes only on master. (nielsdos)

View file

@ -2030,4 +2030,25 @@ PHP_METHOD(DOMNode, getRootNode)
}
/* }}} */
/**
* 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

View file

@ -294,7 +294,6 @@ interface DOMChildNode
public function replaceWith(...$nodes): void;
}
/** @not-serializable */
class DOMNode
{
/** @readonly */
@ -348,6 +347,10 @@ class DOMNode
public string $textContent;
public function __sleep(): array {}
public function __wakeup(): void {}
/** @return DOMNode|false */
public function appendChild(DOMNode $node) {}
@ -406,7 +409,6 @@ class DOMNode
public function getRootNode(?array $options = null): DOMNode {}
}
/** @not-serializable */
class DOMNameSpaceNode
{
/** @readonly */
@ -438,6 +440,12 @@ class DOMNameSpaceNode
/** @readonly */
public ?DOMElement $parentElement;
/** @implementation-alias DOMNode::__sleep */
public function __sleep(): array {}
/** @implementation-alias DOMNode::__wakeup */
public function __wakeup(): void {}
}
class DOMImplementation

View file

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: ebe9bcbd185e1973b5447beb306bd9d93051f415 */
* Stub hash: a20d21c1796ebb43028856f0ec2d53dcaded6cc0 */
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_dom_import_simplexml, 0, 1, DOMElement, 0)
ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0)
@ -30,6 +30,11 @@ ZEND_END_ARG_INFO()
#define arginfo_class_DOMChildNode_replaceWith arginfo_class_DOMParentNode_append
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DOMNode___sleep, 0, 0, IS_ARRAY, 0)
ZEND_END_ARG_INFO()
#define arginfo_class_DOMNode___wakeup arginfo_class_DOMChildNode_remove
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_DOMNode_appendChild, 0, 0, 1)
ZEND_ARG_OBJ_INFO(0, node, DOMNode, 0)
ZEND_END_ARG_INFO()
@ -114,6 +119,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_DOMNode_getRootNode, 0, 0,
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 1, "null")
ZEND_END_ARG_INFO()
#define arginfo_class_DOMNameSpaceNode___sleep arginfo_class_DOMNode___sleep
#define arginfo_class_DOMNameSpaceNode___wakeup arginfo_class_DOMChildNode_remove
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_DOMImplementation_getFeature, 0, 2, IS_NEVER, 0)
ZEND_ARG_TYPE_INFO(0, feature, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, version, IS_STRING, 0)
@ -205,8 +214,7 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_DOMElement_getAt
ZEND_ARG_TYPE_INFO(0, qualifiedName, IS_STRING, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DOMElement_getAttributeNames, 0, 0, IS_ARRAY, 0)
ZEND_END_ARG_INFO()
#define arginfo_class_DOMElement_getAttributeNames arginfo_class_DOMNode___sleep
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_DOMElement_getAttributeNS, 0, 2, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, namespace, IS_STRING, 1)
@ -533,6 +541,8 @@ ZEND_END_ARG_INFO()
ZEND_FUNCTION(dom_import_simplexml);
ZEND_METHOD(DOMCdataSection, __construct);
ZEND_METHOD(DOMComment, __construct);
ZEND_METHOD(DOMNode, __sleep);
ZEND_METHOD(DOMNode, __wakeup);
ZEND_METHOD(DOMNode, appendChild);
ZEND_METHOD(DOMNode, C14N);
ZEND_METHOD(DOMNode, C14NFile);
@ -725,6 +735,8 @@ static const zend_function_entry class_DOMChildNode_methods[] = {
static const zend_function_entry class_DOMNode_methods[] = {
ZEND_ME(DOMNode, __sleep, arginfo_class_DOMNode___sleep, ZEND_ACC_PUBLIC)
ZEND_ME(DOMNode, __wakeup, arginfo_class_DOMNode___wakeup, ZEND_ACC_PUBLIC)
ZEND_ME(DOMNode, appendChild, arginfo_class_DOMNode_appendChild, ZEND_ACC_PUBLIC)
ZEND_ME(DOMNode, C14N, arginfo_class_DOMNode_C14N, ZEND_ACC_PUBLIC)
ZEND_ME(DOMNode, C14NFile, arginfo_class_DOMNode_C14NFile, ZEND_ACC_PUBLIC)
@ -750,6 +762,8 @@ static const zend_function_entry class_DOMNode_methods[] = {
static const zend_function_entry class_DOMNameSpaceNode_methods[] = {
ZEND_MALIAS(DOMNode, __sleep, __sleep, arginfo_class_DOMNameSpaceNode___sleep, ZEND_ACC_PUBLIC)
ZEND_MALIAS(DOMNode, __wakeup, __wakeup, arginfo_class_DOMNameSpaceNode___wakeup, ZEND_ACC_PUBLIC)
ZEND_FE_END
};
@ -1096,7 +1110,6 @@ static zend_class_entry *register_class_DOMNode(void)
INIT_CLASS_ENTRY(ce, "DOMNode", class_DOMNode_methods);
class_entry = zend_register_internal_class_ex(&ce, NULL);
class_entry->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE;
zval property_nodeName_default_value;
ZVAL_UNDEF(&property_nodeName_default_value);
@ -1224,7 +1237,6 @@ static zend_class_entry *register_class_DOMNameSpaceNode(void)
INIT_CLASS_ENTRY(ce, "DOMNameSpaceNode", class_DOMNameSpaceNode_methods);
class_entry = zend_register_internal_class_ex(&ce, NULL);
class_entry->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE;
zval property_nodeName_default_value;
ZVAL_UNDEF(&property_nodeName_default_value);

120
ext/dom/tests/gh8996.phpt Normal file
View file

@ -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>

View file

@ -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

View file

@ -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

View file

@ -682,7 +682,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;

View file

@ -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)