Skip to content

Commit 41804ad

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 9f02a11 commit 41804ad

File tree

13 files changed

+84
-25
lines changed

13 files changed

+84
-25
lines changed

Zend/zend_compile.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ typedef struct _zend_oparray_context {
240240
/* or IS_CONSTANT_VISITED_MARK | | | */
241241
#define ZEND_CLASS_CONST_IS_CASE (1 << 6) /* | | | X */
242242
/* | | | */
243-
/* Class Flags (unused: 21,30,31) | | | */
243+
/* Class Flags (unused: 21,31) | | | */
244244
/* =========== | | | */
245245
/* | | | */
246246
/* Special class types | | | */
@@ -304,6 +304,11 @@ typedef struct _zend_oparray_context {
304304
/* Class cannot be serialized or unserialized | | | */
305305
#define ZEND_ACC_NOT_SERIALIZABLE (1 << 29) /* X | | | */
306306
/* | | | */
307+
/* Subclass can be serialized or unserialized even if | | | */
308+
/* the parent cannot be, on the condition that the | | | */
309+
/* subclass implements the appropriate methods. | | | */
310+
#define ZEND_ACC_SUBCLASS_SERIALIZABLE (1 << 30) /* X | | | */
311+
/* | | | */
307312
/* Function Flags (unused: 28-30) | | | */
308313
/* ============== | | | */
309314
/* | | | */

Zend/zend_inheritance.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1629,7 +1629,16 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par
16291629
ce->ce_flags |= ZEND_ACC_EXPLICIT_ABSTRACT_CLASS;
16301630
}
16311631
}
1632-
ce->ce_flags |= parent_ce->ce_flags & (ZEND_HAS_STATIC_IN_METHODS | ZEND_ACC_HAS_TYPE_HINTS | ZEND_ACC_USE_GUARDS | ZEND_ACC_NOT_SERIALIZABLE | ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES);
1632+
ce->ce_flags |= parent_ce->ce_flags & (ZEND_HAS_STATIC_IN_METHODS | ZEND_ACC_HAS_TYPE_HINTS | ZEND_ACC_USE_GUARDS | ZEND_ACC_NOT_SERIALIZABLE | ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES | ZEND_ACC_SUBCLASS_SERIALIZABLE);
1633+
1634+
if ((parent_ce->ce_flags & (ZEND_ACC_NOT_SERIALIZABLE | ZEND_ACC_SUBCLASS_SERIALIZABLE)) == (ZEND_ACC_NOT_SERIALIZABLE | ZEND_ACC_SUBCLASS_SERIALIZABLE)) {
1635+
if (ce->__serialize || ce->__unserialize
1636+
|| ce->serialize || ce->unserialize
1637+
|| zend_hash_find_known_hash(&ce->function_table, ZSTR_KNOWN(ZEND_STR_SLEEP))
1638+
|| zend_hash_find_known_hash(&ce->function_table, ZSTR_KNOWN(ZEND_STR_WAKEUP))) {
1639+
ce->ce_flags &= ~(ZEND_ACC_NOT_SERIALIZABLE | ZEND_ACC_SUBCLASS_SERIALIZABLE);
1640+
}
1641+
}
16331642
}
16341643
/* }}} */
16351644

build/gen_stub.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
const PHP_80_VERSION_ID = 80000;
2222
const PHP_81_VERSION_ID = 80100;
2323
const PHP_82_VERSION_ID = 80200;
24-
const ALL_PHP_VERSION_IDS = [PHP_70_VERSION_ID, PHP_80_VERSION_ID, PHP_81_VERSION_ID, PHP_82_VERSION_ID];
24+
const PHP_83_VERSION_ID = 80300;
25+
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];
2526

2627
/**
2728
* @return FileInfo[]
@@ -1754,6 +1755,7 @@ protected function getFlagsByPhpVersion(): array
17541755
PHP_80_VERSION_ID => [$flags],
17551756
PHP_81_VERSION_ID => [$flags],
17561757
PHP_82_VERSION_ID => [$flags],
1758+
PHP_83_VERSION_ID => [$flags],
17571759
];
17581760
}
17591761

@@ -2372,6 +2374,7 @@ class ClassInfo {
23722374
/** @var AttributeInfo[] */
23732375
public array $attributes;
23742376
public bool $isNotSerializable;
2377+
public bool $isSubclassSerializable;
23752378
/** @var Name[] */
23762379
public array $extends;
23772380
/** @var Name[] */
@@ -2407,6 +2410,7 @@ public function __construct(
24072410
bool $isStrictProperties,
24082411
array $attributes,
24092412
bool $isNotSerializable,
2413+
bool $isSubclassSerializable,
24102414
array $extends,
24112415
array $implements,
24122416
array $constInfos,
@@ -2426,6 +2430,7 @@ public function __construct(
24262430
$this->isStrictProperties = $isStrictProperties;
24272431
$this->attributes = $attributes;
24282432
$this->isNotSerializable = $isNotSerializable;
2433+
$this->isSubclassSerializable = $isSubclassSerializable;
24292434
$this->extends = $extends;
24302435
$this->implements = $implements;
24312436
$this->constInfos = $constInfos;
@@ -2610,11 +2615,18 @@ private function getFlagsByPhpVersion(): array
26102615
}
26112616
}
26122617

2618+
$php83Flags = $php82Flags;
2619+
2620+
if ($this->isSubclassSerializable) {
2621+
$php83Flags[] = "ZEND_ACC_SUBCLASS_SERIALIZABLE";
2622+
}
2623+
26132624
return [
26142625
PHP_70_VERSION_ID => $php70Flags,
26152626
PHP_80_VERSION_ID => $php80Flags,
26162627
PHP_81_VERSION_ID => $php81Flags,
26172628
PHP_82_VERSION_ID => $php82Flags,
2629+
PHP_83_VERSION_ID => $php83Flags,
26182630
];
26192631
}
26202632

@@ -3482,6 +3494,7 @@ function parseClass(
34823494
$isDeprecated = false;
34833495
$isStrictProperties = false;
34843496
$isNotSerializable = false;
3497+
$isSubclassSerializable = false;
34853498
$allowsDynamicProperties = false;
34863499
$attributes = [];
34873500

@@ -3498,6 +3511,8 @@ function parseClass(
34983511
$isNotSerializable = true;
34993512
} else if ($tag->name === 'undocumentable') {
35003513
$isUndocumentable = true;
3514+
} else if ($tag->name == 'subclass-serializable') {
3515+
$isSubclassSerializable = true;
35013516
}
35023517
}
35033518
}
@@ -3517,6 +3532,10 @@ function parseClass(
35173532
throw new Exception("A class may not have '@strict-properties' and '#[\\AllowDynamicProperties]' at the same time.");
35183533
}
35193534

3535+
if (!$isNotSerializable && $isSubclassSerializable) {
3536+
throw new Exception("@subclass-serializable without @not-serializable is a no-op");
3537+
}
3538+
35203539
$extends = [];
35213540
$implements = [];
35223541

@@ -3555,6 +3574,7 @@ function parseClass(
35553574
$isStrictProperties,
35563575
$attributes,
35573576
$isNotSerializable,
3577+
$isSubclassSerializable,
35583578
$extends,
35593579
$implements,
35603580
$consts,

ext/dom/php_dom.stub.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,10 @@ public function after(...$nodes): void;
291291
public function replaceWith(...$nodes): void;
292292
}
293293

294-
/** @not-serializable */
294+
/**
295+
* @not-serializable
296+
* @subclass-serializable
297+
*/
295298
class DOMNode
296299
{
297300
/** @readonly */
@@ -391,7 +394,10 @@ public function removeChild(DOMNode $child) {}
391394
public function replaceChild(DOMNode $node, DOMNode $child) {}
392395
}
393396

394-
/** @not-serializable */
397+
/**
398+
* @not-serializable
399+
* @subclass-serializable
400+
*/
395401
class DOMNameSpaceNode
396402
{
397403
/** @readonly */
@@ -901,7 +907,10 @@ public function __construct(string $name, string $value = "") {}
901907
}
902908

903909
#ifdef LIBXML_XPATH_ENABLED
904-
/** @not-serializable */
910+
/**
911+
* @not-serializable
912+
* @subclass-serializable
913+
*/
905914
class DOMXPath
906915
{
907916
/** @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
@@ -1057,8 +1057,13 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_
10571057
uint32_t count;
10581058

10591059
if (ce->ce_flags & ZEND_ACC_NOT_SERIALIZABLE) {
1060-
zend_throw_exception_ex(NULL, 0, "Serialization of '%s' is not allowed",
1061-
ZSTR_VAL(ce->name));
1060+
if (ce->ce_flags & ZEND_ACC_SUBCLASS_SERIALIZABLE) {
1061+
zend_throw_exception_ex(NULL, 0, "Serialization of '%s' is not allowed, unless you extend the class and provide a serialisation method",
1062+
ZSTR_VAL(ce->name));
1063+
} else {
1064+
zend_throw_exception_ex(NULL, 0, "Serialization of '%s' is not allowed",
1065+
ZSTR_VAL(ce->name));
1066+
}
10621067
return;
10631068
}
10641069

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)