diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 765e54fb56ee8..cc5a2bd547bd7 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -239,7 +239,7 @@ typedef struct _zend_oparray_context { /* or IS_CONSTANT_VISITED_MARK | | | */ #define ZEND_CLASS_CONST_IS_CASE (1 << 6) /* | | | X */ /* | | | */ -/* Class Flags (unused: 30,31) | | | */ +/* Class Flags (unused: 31) | | | */ /* =========== | | | */ /* | | | */ /* Special class types | | | */ @@ -305,7 +305,12 @@ typedef struct _zend_oparray_context { /* Class cannot be serialized or unserialized | | | */ #define ZEND_ACC_NOT_SERIALIZABLE (1 << 29) /* X | | | */ /* | | | */ -/* Function Flags (unused: 29-30) | | | */ +/* Subclass can be serialized or unserialized even if | | | */ +/* the parent cannot be, on the condition that the | | | */ +/* subclass implements the appropriate methods. | | | */ +#define ZEND_ACC_SUBCLASS_SERIALIZABLE (1 << 30) /* X | | | */ +/* | | | */ +/* Function Flags (unused: 28-30) | | | */ /* ============== | | | */ /* | | | */ /* deprecation flag | | | */ diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index d1c66b310a6b4..43d626314fba2 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1672,7 +1672,16 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par ce->ce_flags |= ZEND_ACC_EXPLICIT_ABSTRACT_CLASS; } } - ce->ce_flags |= parent_ce->ce_flags & (ZEND_HAS_STATIC_IN_METHODS | ZEND_ACC_HAS_TYPE_HINTS | ZEND_ACC_HAS_READONLY_PROPS | ZEND_ACC_USE_GUARDS | ZEND_ACC_NOT_SERIALIZABLE | ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES); + ce->ce_flags |= parent_ce->ce_flags & (ZEND_HAS_STATIC_IN_METHODS | ZEND_ACC_HAS_TYPE_HINTS | ZEND_ACC_HAS_READONLY_PROPS | ZEND_ACC_USE_GUARDS | ZEND_ACC_NOT_SERIALIZABLE | ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES | ZEND_ACC_SUBCLASS_SERIALIZABLE); + + if ((parent_ce->ce_flags & (ZEND_ACC_NOT_SERIALIZABLE | ZEND_ACC_SUBCLASS_SERIALIZABLE)) == (ZEND_ACC_NOT_SERIALIZABLE | ZEND_ACC_SUBCLASS_SERIALIZABLE)) { + if (ce->__serialize || ce->__unserialize + || ce->serialize || ce->unserialize + || zend_hash_find_known_hash(&ce->function_table, ZSTR_KNOWN(ZEND_STR_SLEEP)) + || zend_hash_find_known_hash(&ce->function_table, ZSTR_KNOWN(ZEND_STR_WAKEUP))) { + ce->ce_flags &= ~(ZEND_ACC_NOT_SERIALIZABLE | ZEND_ACC_SUBCLASS_SERIALIZABLE); + } + } } /* }}} */ diff --git a/build/gen_stub.php b/build/gen_stub.php index 84d7944a786a7..5906cb95ab0cc 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -23,7 +23,8 @@ const PHP_81_VERSION_ID = 80100; const PHP_82_VERSION_ID = 80200; const PHP_83_VERSION_ID = 80300; -const ALL_PHP_VERSION_IDS = [PHP_70_VERSION_ID, PHP_80_VERSION_ID, PHP_81_VERSION_ID, PHP_82_VERSION_ID, PHP_83_VERSION_ID]; +const PHP_84_VERSION_ID = 80400; +const ALL_PHP_VERSION_IDS = [PHP_70_VERSION_ID, PHP_80_VERSION_ID, PHP_81_VERSION_ID, PHP_82_VERSION_ID, PHP_83_VERSION_ID, PHP_84_VERSION_ID]; /** * @return FileInfo[] @@ -1845,6 +1846,7 @@ protected function getFlagsByPhpVersion(): array PHP_81_VERSION_ID => [$flags], PHP_82_VERSION_ID => [$flags], PHP_83_VERSION_ID => [$flags], + PHP_84_VERSION_ID => [$flags], ]; } @@ -2528,6 +2530,7 @@ class ClassInfo { /** @var AttributeInfo[] */ public array $attributes; public bool $isNotSerializable; + public bool $isSubclassSerializable; /** @var Name[] */ public array $extends; /** @var Name[] */ @@ -2563,6 +2566,7 @@ public function __construct( bool $isStrictProperties, array $attributes, bool $isNotSerializable, + bool $isSubclassSerializable, array $extends, array $implements, array $constInfos, @@ -2582,6 +2586,7 @@ public function __construct( $this->isStrictProperties = $isStrictProperties; $this->attributes = $attributes; $this->isNotSerializable = $isNotSerializable; + $this->isSubclassSerializable = $isSubclassSerializable; $this->extends = $extends; $this->implements = $implements; $this->constInfos = $constInfos; @@ -2795,12 +2800,19 @@ private function getFlagsByPhpVersion(): array $php83Flags = $php82Flags; + $php84Flags = $php83Flags; + + if ($this->isSubclassSerializable) { + $php84Flags[] = "ZEND_ACC_SUBCLASS_SERIALIZABLE"; + } + return [ PHP_70_VERSION_ID => $php70Flags, PHP_80_VERSION_ID => $php80Flags, PHP_81_VERSION_ID => $php81Flags, PHP_82_VERSION_ID => $php82Flags, PHP_83_VERSION_ID => $php83Flags, + PHP_84_VERSION_ID => $php84Flags, ]; } @@ -3712,6 +3724,7 @@ function parseClass( $isDeprecated = false; $isStrictProperties = false; $isNotSerializable = false; + $isSubclassSerializable = false; $allowsDynamicProperties = false; $attributes = []; @@ -3728,6 +3741,8 @@ function parseClass( $isNotSerializable = true; } else if ($tag->name === 'undocumentable') { $isUndocumentable = true; + } else if ($tag->name == 'subclass-serializable') { + $isSubclassSerializable = true; } } } @@ -3745,6 +3760,10 @@ function parseClass( throw new Exception("A class may not have '@strict-properties' and '#[\\AllowDynamicProperties]' at the same time."); } + if (!$isNotSerializable && $isSubclassSerializable) { + throw new Exception("@subclass-serializable without @not-serializable is a no-op"); + } + $extends = []; $implements = []; @@ -3783,6 +3802,7 @@ function parseClass( $isStrictProperties, $attributes, $isNotSerializable, + $isSubclassSerializable, $extends, $implements, $consts, diff --git a/ext/dom/php_dom.stub.php b/ext/dom/php_dom.stub.php index fb8faaab10f57..93b2afb7e8aa1 100644 --- a/ext/dom/php_dom.stub.php +++ b/ext/dom/php_dom.stub.php @@ -294,7 +294,10 @@ public function after(...$nodes): void; public function replaceWith(...$nodes): void; } -/** @not-serializable */ +/** + * @not-serializable + * @subclass-serializable + */ class DOMNode { public const int DOCUMENT_POSITION_DISCONNECTED = 0x01; @@ -415,7 +418,10 @@ public function getRootNode(?array $options = null): DOMNode {} public function compareDocumentPosition(DOMNode $other): int {} } -/** @not-serializable */ +/** + * @not-serializable + * @subclass-serializable + */ class DOMNameSpaceNode { /** @readonly */ @@ -977,7 +983,10 @@ public function __construct(string $name, string $value = "") {} } #ifdef LIBXML_XPATH_ENABLED -/** @not-serializable */ +/** + * @not-serializable + * @subclass-serializable + */ class DOMXPath { /** @readonly */ diff --git a/ext/dom/php_dom_arginfo.h b/ext/dom/php_dom_arginfo.h index 9ed5aa443cb54..64641d25ea845 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: 9142d10743d4ec2f5e587c67bd111155c6149efd */ + * Stub hash: 5b1f6242a564eb790ec28541055fd25ee56dfeba */ 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) @@ -1093,7 +1093,7 @@ 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; + class_entry->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE|ZEND_ACC_SUBCLASS_SERIALIZABLE; zval const_DOCUMENT_POSITION_DISCONNECTED_value; ZVAL_LONG(&const_DOCUMENT_POSITION_DISCONNECTED_value, 0x1); @@ -1257,7 +1257,7 @@ 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; + class_entry->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE|ZEND_ACC_SUBCLASS_SERIALIZABLE; zval property_nodeName_default_value; ZVAL_UNDEF(&property_nodeName_default_value); @@ -1835,7 +1835,7 @@ static zend_class_entry *register_class_DOMXPath(void) INIT_CLASS_ENTRY(ce, "DOMXPath", class_DOMXPath_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); - class_entry->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE; + class_entry->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE|ZEND_ACC_SUBCLASS_SERIALIZABLE; zval property_document_default_value; ZVAL_UNDEF(&property_document_default_value); diff --git a/ext/dom/tests/gh8996.phpt b/ext/dom/tests/gh8996.phpt new file mode 100644 index 0000000000000..995d195b04569 Binary files /dev/null and b/ext/dom/tests/gh8996.phpt differ diff --git a/ext/dom/tests/not_serializable.phpt b/ext/dom/tests/not_serializable.phpt index 9869a8c87e35a..fb6ca6ab4bdfb 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 'DOMXPath' is not allowed -Serialization of 'DOMNameSpaceNode' is not allowed +Serialization of 'DOMDocument' is not allowed, unless you extend the class and provide a serialisation method +Serialization of 'DOMElement' is not allowed, unless you extend the class and provide a serialisation method +Serialization of 'DOMXPath' is not allowed, unless you extend the class and provide a serialisation method +Serialization of 'DOMNameSpaceNode' is not allowed, unless you extend the class and provide a serialisation method diff --git a/ext/intl/formatter/formatter.stub.php b/ext/intl/formatter/formatter.stub.php index 84ae31b518380..698c8310b5053 100644 --- a/ext/intl/formatter/formatter.stub.php +++ b/ext/intl/formatter/formatter.stub.php @@ -2,7 +2,10 @@ /** @generate-class-entries */ -/** @not-serializable */ +/** + * @not-serializable + * @subclass-serializable + */ class NumberFormatter { /* UNumberFormatStyle constants */ diff --git a/ext/intl/formatter/formatter_arginfo.h b/ext/intl/formatter/formatter_arginfo.h index 27851b180514f..782e309cd1c6b 100644 --- a/ext/intl/formatter/formatter_arginfo.h +++ b/ext/intl/formatter/formatter_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 3b050eaf6f2f54e3726b04a17d40b06ad610a724 */ + * Stub hash: 2c83a6c7f0aeaaaee5327e6a5f4112a673034b2f */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_NumberFormatter___construct, 0, 0, 2) ZEND_ARG_TYPE_INFO(0, locale, IS_STRING, 0) @@ -126,7 +126,7 @@ static zend_class_entry *register_class_NumberFormatter(void) INIT_CLASS_ENTRY(ce, "NumberFormatter", class_NumberFormatter_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); - class_entry->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE; + class_entry->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE|ZEND_ACC_SUBCLASS_SERIALIZABLE; zval const_PATTERN_DECIMAL_value; ZVAL_LONG(&const_PATTERN_DECIMAL_value, UNUM_PATTERN_DECIMAL); diff --git a/ext/intl/tests/bug74063.phpt b/ext/intl/tests/bug74063.phpt index b563dea40117b..58f693a599e3f 100644 --- a/ext/intl/tests/bug74063.phpt +++ b/ext/intl/tests/bug74063.phpt @@ -12,4 +12,4 @@ try { } ?> --EXPECT-- -Serialization of 'NumberFormatter' is not allowed +Serialization of 'NumberFormatter' is not allowed, unless you extend the class and provide a serialisation method diff --git a/ext/simplexml/simplexml.stub.php b/ext/simplexml/simplexml.stub.php index 4735573521587..8c8e98b247a73 100644 --- a/ext/simplexml/simplexml.stub.php +++ b/ext/simplexml/simplexml.stub.php @@ -8,7 +8,10 @@ function simplexml_load_string(string $data, ?string $class_name = SimpleXMLElem function simplexml_import_dom(SimpleXMLElement|DOMNode $node, ?string $class_name = SimpleXMLElement::class): ?SimpleXMLElement {} -/** @not-serializable */ +/** + * @not-serializable + * @subclass-serializable + */ class SimpleXMLElement implements Stringable, Countable, RecursiveIterator { /** @tentative-return-type */ diff --git a/ext/simplexml/simplexml_arginfo.h b/ext/simplexml/simplexml_arginfo.h index f6100eb243e31..0c79044764d95 100644 --- a/ext/simplexml/simplexml_arginfo.h +++ b/ext/simplexml/simplexml_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 06c88dc2fb5582a6d21c11aee6ac0a0538e70cbc */ + * Stub hash: 373d138420453f0b7a7e6fdd711b869229e97c5a */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_simplexml_load_file, 0, 1, SimpleXMLElement, MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) @@ -170,7 +170,7 @@ static zend_class_entry *register_class_SimpleXMLElement(zend_class_entry *class INIT_CLASS_ENTRY(ce, "SimpleXMLElement", class_SimpleXMLElement_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); - class_entry->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE; + class_entry->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE|ZEND_ACC_SUBCLASS_SERIALIZABLE; zend_class_implements(class_entry, 3, class_entry_Stringable, class_entry_Countable, class_entry_RecursiveIterator); return class_entry; diff --git a/ext/standard/var.c b/ext/standard/var.c index 4f04ff6c0deb3..8334a5eb3bdd0 100644 --- a/ext/standard/var.c +++ b/ext/standard/var.c @@ -1054,8 +1054,13 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_ uint32_t count; if (ce->ce_flags & ZEND_ACC_NOT_SERIALIZABLE) { - zend_throw_exception_ex(NULL, 0, "Serialization of '%s' is not allowed", - ZSTR_VAL(ce->name)); + if (ce->ce_flags & ZEND_ACC_SUBCLASS_SERIALIZABLE) { + zend_throw_exception_ex(NULL, 0, "Serialization of '%s' is not allowed, unless you extend the class and provide a serialisation method", + ZSTR_VAL(ce->name)); + } else { + zend_throw_exception_ex(NULL, 0, "Serialization of '%s' is not allowed", + ZSTR_VAL(ce->name)); + } return; } diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index a050fb5f74a70..10c165f0c19d6 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -1277,8 +1277,13 @@ object ":" uiv ":" ["] { *p = YYCURSOR; if (ce->ce_flags & ZEND_ACC_NOT_SERIALIZABLE) { - zend_throw_exception_ex(NULL, 0, "Unserialization of '%s' is not allowed", - ZSTR_VAL(ce->name)); + if (ce->ce_flags & ZEND_ACC_SUBCLASS_SERIALIZABLE) { + zend_throw_exception_ex(NULL, 0, "Unserialization of '%s' is not allowed, unless you extend the class and provide a unserialisation method", + ZSTR_VAL(ce->name)); + } else { + zend_throw_exception_ex(NULL, 0, "Unserialization of '%s' is not allowed", + ZSTR_VAL(ce->name)); + } zend_string_release_ex(class_name, 0); return 0; }