Skip to content

Commit 5809b35

Browse files
committed
Introduce a way to (de)serialize subclasses of un(de)serializable classes if the appropriate methods are present
This introduces the class flag ZEND_ACC_SUBCLASS_SERIALIZABLE, which allows classes which are not serializable / deserializable to become that if a subclass implements the appropriate methods. Setting this flag through the stubs can be done using @subclass-serializable. Introducing this through behaviour a new flag allows for opt-in behaviour, which is backwards compatible. Fixes GH-8996.
1 parent 57a8f63 commit 5809b35

13 files changed

+85
-26
lines changed

Zend/zend_compile.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ typedef struct _zend_oparray_context {
239239
/* or IS_CONSTANT_VISITED_MARK | | | */
240240
#define ZEND_CLASS_CONST_IS_CASE (1 << 6) /* | | | X */
241241
/* | | | */
242-
/* Class Flags (unused: 30,31) | | | */
242+
/* Class Flags (unused: 31) | | | */
243243
/* =========== | | | */
244244
/* | | | */
245245
/* Special class types | | | */
@@ -305,7 +305,12 @@ typedef struct _zend_oparray_context {
305305
/* Class cannot be serialized or unserialized | | | */
306306
#define ZEND_ACC_NOT_SERIALIZABLE (1 << 29) /* X | | | */
307307
/* | | | */
308-
/* Function Flags (unused: 29-30) | | | */
308+
/* Subclass can be serialized or unserialized even if | | | */
309+
/* the parent cannot be, on the condition that the | | | */
310+
/* subclass implements the appropriate methods. | | | */
311+
#define ZEND_ACC_SUBCLASS_SERIALIZABLE (1 << 30) /* X | | | */
312+
/* | | | */
313+
/* Function Flags (unused: 28-30) | | | */
309314
/* ============== | | | */
310315
/* | | | */
311316
/* deprecation flag | | | */

Zend/zend_inheritance.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1672,7 +1672,16 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par
16721672
ce->ce_flags |= ZEND_ACC_EXPLICIT_ABSTRACT_CLASS;
16731673
}
16741674
}
1675-
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);
1675+
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);
1676+
1677+
if ((parent_ce->ce_flags & (ZEND_ACC_NOT_SERIALIZABLE | ZEND_ACC_SUBCLASS_SERIALIZABLE)) == (ZEND_ACC_NOT_SERIALIZABLE | ZEND_ACC_SUBCLASS_SERIALIZABLE)) {
1678+
if (ce->__serialize || ce->__unserialize
1679+
|| ce->serialize || ce->unserialize
1680+
|| zend_hash_find_known_hash(&ce->function_table, ZSTR_KNOWN(ZEND_STR_SLEEP))
1681+
|| zend_hash_find_known_hash(&ce->function_table, ZSTR_KNOWN(ZEND_STR_WAKEUP))) {
1682+
ce->ce_flags &= ~(ZEND_ACC_NOT_SERIALIZABLE | ZEND_ACC_SUBCLASS_SERIALIZABLE);
1683+
}
1684+
}
16761685
}
16771686
/* }}} */
16781687

build/gen_stub.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
const PHP_81_VERSION_ID = 80100;
2424
const PHP_82_VERSION_ID = 80200;
2525
const PHP_83_VERSION_ID = 80300;
26-
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];
26+
const PHP_84_VERSION_ID = 80400;
27+
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];
2728

2829
/**
2930
* @return FileInfo[]
@@ -1845,6 +1846,7 @@ protected function getFlagsByPhpVersion(): array
18451846
PHP_81_VERSION_ID => [$flags],
18461847
PHP_82_VERSION_ID => [$flags],
18471848
PHP_83_VERSION_ID => [$flags],
1849+
PHP_84_VERSION_ID => [$flags],
18481850
];
18491851
}
18501852

@@ -2528,6 +2530,7 @@ class ClassInfo {
25282530
/** @var AttributeInfo[] */
25292531
public array $attributes;
25302532
public bool $isNotSerializable;
2533+
public bool $isSubclassSerializable;
25312534
/** @var Name[] */
25322535
public array $extends;
25332536
/** @var Name[] */
@@ -2563,6 +2566,7 @@ public function __construct(
25632566
bool $isStrictProperties,
25642567
array $attributes,
25652568
bool $isNotSerializable,
2569+
bool $isSubclassSerializable,
25662570
array $extends,
25672571
array $implements,
25682572
array $constInfos,
@@ -2582,6 +2586,7 @@ public function __construct(
25822586
$this->isStrictProperties = $isStrictProperties;
25832587
$this->attributes = $attributes;
25842588
$this->isNotSerializable = $isNotSerializable;
2589+
$this->isSubclassSerializable = $isSubclassSerializable;
25852590
$this->extends = $extends;
25862591
$this->implements = $implements;
25872592
$this->constInfos = $constInfos;
@@ -2795,12 +2800,19 @@ private function getFlagsByPhpVersion(): array
27952800

27962801
$php83Flags = $php82Flags;
27972802

2803+
$php84Flags = $php83Flags;
2804+
2805+
if ($this->isSubclassSerializable) {
2806+
$php84Flags[] = "ZEND_ACC_SUBCLASS_SERIALIZABLE";
2807+
}
2808+
27982809
return [
27992810
PHP_70_VERSION_ID => $php70Flags,
28002811
PHP_80_VERSION_ID => $php80Flags,
28012812
PHP_81_VERSION_ID => $php81Flags,
28022813
PHP_82_VERSION_ID => $php82Flags,
28032814
PHP_83_VERSION_ID => $php83Flags,
2815+
PHP_84_VERSION_ID => $php84Flags,
28042816
];
28052817
}
28062818

@@ -3712,6 +3724,7 @@ function parseClass(
37123724
$isDeprecated = false;
37133725
$isStrictProperties = false;
37143726
$isNotSerializable = false;
3727+
$isSubclassSerializable = false;
37153728
$allowsDynamicProperties = false;
37163729
$attributes = [];
37173730

@@ -3728,6 +3741,8 @@ function parseClass(
37283741
$isNotSerializable = true;
37293742
} else if ($tag->name === 'undocumentable') {
37303743
$isUndocumentable = true;
3744+
} else if ($tag->name == 'subclass-serializable') {
3745+
$isSubclassSerializable = true;
37313746
}
37323747
}
37333748
}
@@ -3745,6 +3760,10 @@ function parseClass(
37453760
throw new Exception("A class may not have '@strict-properties' and '#[\\AllowDynamicProperties]' at the same time.");
37463761
}
37473762

3763+
if (!$isNotSerializable && $isSubclassSerializable) {
3764+
throw new Exception("@subclass-serializable without @not-serializable is a no-op");
3765+
}
3766+
37483767
$extends = [];
37493768
$implements = [];
37503769

@@ -3783,6 +3802,7 @@ function parseClass(
37833802
$isStrictProperties,
37843803
$attributes,
37853804
$isNotSerializable,
3805+
$isSubclassSerializable,
37863806
$extends,
37873807
$implements,
37883808
$consts,

ext/dom/php_dom.stub.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,10 @@ public function after(...$nodes): void;
294294
public function replaceWith(...$nodes): void;
295295
}
296296

297-
/** @not-serializable */
297+
/**
298+
* @not-serializable
299+
* @subclass-serializable
300+
*/
298301
class DOMNode
299302
{
300303
public const int DOCUMENT_POSITION_DISCONNECTED = 0x01;
@@ -415,7 +418,10 @@ public function getRootNode(?array $options = null): DOMNode {}
415418
public function compareDocumentPosition(DOMNode $other): int {}
416419
}
417420

418-
/** @not-serializable */
421+
/**
422+
* @not-serializable
423+
* @subclass-serializable
424+
*/
419425
class DOMNameSpaceNode
420426
{
421427
/** @readonly */
@@ -977,7 +983,10 @@ public function __construct(string $name, string $value = "") {}
977983
}
978984

979985
#ifdef LIBXML_XPATH_ENABLED
980-
/** @not-serializable */
986+
/**
987+
* @not-serializable
988+
* @subclass-serializable
989+
*/
981990
class DOMXPath
982991
{
983992
/** @readonly */

ext/dom/php_dom_arginfo.h

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/dom/tests/not_serializable.phpt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ try {
3636

3737
?>
3838
--EXPECT--
39-
Serialization of 'DOMDocument' is not allowed
40-
Serialization of 'DOMElement' is not allowed
41-
Serialization of 'DOMXPath' is not allowed
42-
Serialization of 'DOMNameSpaceNode' is not allowed
39+
Serialization of 'DOMDocument' is not allowed, unless you extend the class and provide a serialisation method
40+
Serialization of 'DOMElement' is not allowed, unless you extend the class and provide a serialisation method
41+
Serialization of 'DOMXPath' is not allowed, unless you extend the class and provide a serialisation method
42+
Serialization of 'DOMNameSpaceNode' is not allowed, unless you extend the class and provide a serialisation method

ext/intl/formatter/formatter.stub.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
/** @generate-class-entries */
44

5-
/** @not-serializable */
5+
/**
6+
* @not-serializable
7+
* @subclass-serializable
8+
*/
69
class NumberFormatter
710
{
811
/* UNumberFormatStyle constants */

ext/intl/formatter/formatter_arginfo.h

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/intl/tests/bug74063.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ try {
1212
}
1313
?>
1414
--EXPECT--
15-
Serialization of 'NumberFormatter' is not allowed
15+
Serialization of 'NumberFormatter' is not allowed, unless you extend the class and provide a serialisation method

ext/simplexml/simplexml.stub.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ function simplexml_load_string(string $data, ?string $class_name = SimpleXMLElem
88

99
function simplexml_import_dom(SimpleXMLElement|DOMNode $node, ?string $class_name = SimpleXMLElement::class): ?SimpleXMLElement {}
1010

11-
/** @not-serializable */
11+
/**
12+
* @not-serializable
13+
* @subclass-serializable
14+
*/
1215
class SimpleXMLElement implements Stringable, Countable, RecursiveIterator
1316
{
1417
/** @tentative-return-type */

ext/simplexml/simplexml_arginfo.h

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/standard/var.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,8 +1054,13 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_
10541054
uint32_t count;
10551055

10561056
if (ce->ce_flags & ZEND_ACC_NOT_SERIALIZABLE) {
1057-
zend_throw_exception_ex(NULL, 0, "Serialization of '%s' is not allowed",
1058-
ZSTR_VAL(ce->name));
1057+
if (ce->ce_flags & ZEND_ACC_SUBCLASS_SERIALIZABLE) {
1058+
zend_throw_exception_ex(NULL, 0, "Serialization of '%s' is not allowed, unless you extend the class and provide a serialisation method",
1059+
ZSTR_VAL(ce->name));
1060+
} else {
1061+
zend_throw_exception_ex(NULL, 0, "Serialization of '%s' is not allowed",
1062+
ZSTR_VAL(ce->name));
1063+
}
10591064
return;
10601065
}
10611066

ext/standard/var_unserializer.re

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1277,8 +1277,13 @@ object ":" uiv ":" ["] {
12771277
*p = YYCURSOR;
12781278

12791279
if (ce->ce_flags & ZEND_ACC_NOT_SERIALIZABLE) {
1280-
zend_throw_exception_ex(NULL, 0, "Unserialization of '%s' is not allowed",
1281-
ZSTR_VAL(ce->name));
1280+
if (ce->ce_flags & ZEND_ACC_SUBCLASS_SERIALIZABLE) {
1281+
zend_throw_exception_ex(NULL, 0, "Unserialization of '%s' is not allowed, unless you extend the class and provide a unserialisation method",
1282+
ZSTR_VAL(ce->name));
1283+
} else {
1284+
zend_throw_exception_ex(NULL, 0, "Unserialization of '%s' is not allowed",
1285+
ZSTR_VAL(ce->name));
1286+
}
12821287
zend_string_release_ex(class_name, 0);
12831288
return 0;
12841289
}

0 commit comments

Comments
 (0)