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)