diff --git a/NEWS b/NEWS index fcbdcc9c24e..01984977e23 100644 --- a/NEWS +++ b/NEWS @@ -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) diff --git a/ext/dom/node.c b/ext/dom/node.c index e2b4977d892..4a797c573a4 100644 --- a/ext/dom/node.c +++ b/ext/dom/node.c @@ -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 diff --git a/ext/dom/php_dom.stub.php b/ext/dom/php_dom.stub.php index 7316f842669..b588d31d1d3 100644 --- a/ext/dom/php_dom.stub.php +++ b/ext/dom/php_dom.stub.php @@ -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 diff --git a/ext/dom/php_dom_arginfo.h b/ext/dom/php_dom_arginfo.h index 76d8b0af2b0..9eae1c58f14 100644 --- a/ext/dom/php_dom_arginfo.h +++ b/ext/dom/php_dom_arginfo.h @@ -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); diff --git a/ext/dom/tests/gh8996.phpt b/ext/dom/tests/gh8996.phpt new file mode 100644 index 00000000000..62b7955bacf --- /dev/null +++ b/ext/dom/tests/gh8996.phpt @@ -0,0 +1,120 @@ +--TEST-- +GH-8996: DOMNode serialization on PHP ^8.1 +--EXTENSIONS-- +dom +--FILE-- +xmlData = $this->saveXML(); + return ['xmlData']; + } + + public function __wakeup(): void + { + $this->loadXML($this->xmlData); + } +} + +$dom = new SerializableDomDocumentSleepWakeup('1.0', 'UTF-8'); +$dom->loadXML('value'); + +$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('value'); + +$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('value'); + +$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:" +value +";}" +Serialized: +----------- +O:34:"SerializableDomDocumentSleepWakeup":1:{s:43:"%0SerializableDomDocumentSleepWakeup%0xmlData";s:39:" +value +";} +----------- +Restored: +----------- + +value +=== __serialize and __unserialize === +Serialized: +----------- +O:47:"SerializableDomDocument__Serialize__Unserialize":1:{s:7:"xmlData";s:39:" +value +";} +----------- +Restored: +----------- + +value +=== 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:{ +value +} +----------- +Restored: +----------- + +value diff --git a/ext/dom/tests/not_serializable.phpt b/ext/dom/tests/not_serializable.phpt index 9869a8c87e3..3d542861db0 100644 --- a/ext/dom/tests/not_serializable.phpt +++ b/ext/dom/tests/not_serializable.phpt @@ -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 diff --git a/ext/dom/tests/not_unserializable.phpt b/ext/dom/tests/not_unserializable.phpt new file mode 100644 index 00000000000..a25a2737e85 --- /dev/null +++ b/ext/dom/tests/not_unserializable.phpt @@ -0,0 +1,29 @@ +--TEST-- +DOM classes are not unserializable +--EXTENSIONS-- +dom +--FILE-- +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 diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 1df2b9af92b..3d086cd27c3 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -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; diff --git a/ext/opcache/tests/jit/gh12380.phpt b/ext/opcache/tests/jit/gh12380.phpt new file mode 100644 index 00000000000..75a0a9cc418 --- /dev/null +++ b/ext/opcache/tests/jit/gh12380.phpt @@ -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-- +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)