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) . Restore old namespace reconciliation behaviour. (nielsdos)
. Fix broken cache invalidation with deallocated and reallocated document . Fix broken cache invalidation with deallocated and reallocated document
node. (nielsdos) node. (nielsdos)
. Fixed bug GH-8996 (DOMNode serialization on PHP ^8.1). (nielsdos)
- Fileinfo: - Fileinfo:
. Fixed bug GH-11891 (fileinfo returns text/xml for some svg files). (usarise) . Fixed bug GH-11891 (fileinfo returns text/xml for some svg files). (usarise)
@ -31,6 +32,8 @@ PHP NEWS
- Opcache: - Opcache:
. Fixed opcache_invalidate() on deleted file. (mikhainin) . 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: - SimpleXML:
. Apply iterator fixes only on master. (nielsdos) . 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 #endif

View file

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

View file

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead. /* 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_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_dom_import_simplexml, 0, 1, DOMElement, 0)
ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 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 #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_BEGIN_ARG_INFO_EX(arginfo_class_DOMNode_appendChild, 0, 0, 1)
ZEND_ARG_OBJ_INFO(0, node, DOMNode, 0) ZEND_ARG_OBJ_INFO(0, node, DOMNode, 0)
ZEND_END_ARG_INFO() 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_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 1, "null")
ZEND_END_ARG_INFO() 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_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, feature, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, version, 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_ARG_TYPE_INFO(0, qualifiedName, IS_STRING, 0)
ZEND_END_ARG_INFO() ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DOMElement_getAttributeNames, 0, 0, IS_ARRAY, 0) #define arginfo_class_DOMElement_getAttributeNames arginfo_class_DOMNode___sleep
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_DOMElement_getAttributeNS, 0, 2, IS_STRING, 0) 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) ZEND_ARG_TYPE_INFO(0, namespace, IS_STRING, 1)
@ -533,6 +541,8 @@ ZEND_END_ARG_INFO()
ZEND_FUNCTION(dom_import_simplexml); ZEND_FUNCTION(dom_import_simplexml);
ZEND_METHOD(DOMCdataSection, __construct); ZEND_METHOD(DOMCdataSection, __construct);
ZEND_METHOD(DOMComment, __construct); ZEND_METHOD(DOMComment, __construct);
ZEND_METHOD(DOMNode, __sleep);
ZEND_METHOD(DOMNode, __wakeup);
ZEND_METHOD(DOMNode, appendChild); ZEND_METHOD(DOMNode, appendChild);
ZEND_METHOD(DOMNode, C14N); ZEND_METHOD(DOMNode, C14N);
ZEND_METHOD(DOMNode, C14NFile); ZEND_METHOD(DOMNode, C14NFile);
@ -725,6 +735,8 @@ static const zend_function_entry class_DOMChildNode_methods[] = {
static const zend_function_entry class_DOMNode_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, appendChild, arginfo_class_DOMNode_appendChild, ZEND_ACC_PUBLIC)
ZEND_ME(DOMNode, C14N, arginfo_class_DOMNode_C14N, 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) 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[] = { 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 ZEND_FE_END
}; };
@ -1096,7 +1110,6 @@ static zend_class_entry *register_class_DOMNode(void)
INIT_CLASS_ENTRY(ce, "DOMNode", class_DOMNode_methods); INIT_CLASS_ENTRY(ce, "DOMNode", class_DOMNode_methods);
class_entry = zend_register_internal_class_ex(&ce, NULL); class_entry = zend_register_internal_class_ex(&ce, NULL);
class_entry->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE;
zval property_nodeName_default_value; zval property_nodeName_default_value;
ZVAL_UNDEF(&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); INIT_CLASS_ENTRY(ce, "DOMNameSpaceNode", class_DOMNameSpaceNode_methods);
class_entry = zend_register_internal_class_ex(&ce, NULL); class_entry = zend_register_internal_class_ex(&ce, NULL);
class_entry->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE;
zval property_nodeName_default_value; zval property_nodeName_default_value;
ZVAL_UNDEF(&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-- --EXPECT--
Serialization of 'DOMDocument' is not allowed Serialization of 'DOMDocument' is not allowed, unless serialization methods are implemented in a subclass
Serialization of 'DOMElement' is not allowed Serialization of 'DOMElement' is not allowed, unless serialization methods are implemented in a subclass
Serialization of 'DOMXPath' is not allowed 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; return info;
} else if (on_this) { } else if (on_this) {
if (ce == info->ce) { if (ce == info->ce) {
return info; if (ce == op_array->scope) {
return info;
} else {
return NULL;
}
} else if ((info->flags & ZEND_ACC_PROTECTED) } else if ((info->flags & ZEND_ACC_PROTECTED)
&& instanceof_function_slow(ce, info->ce)) { && instanceof_function_slow(ce, info->ce)) {
return info; 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)