Skip to content

Commit e2ea0f1

Browse files
committed
Fix bug #77866: Port Serializable SPL classes to use __unserialize()
Payloads created using Serializable are still supported.
1 parent cb145e1 commit e2ea0f1

13 files changed

+406
-21
lines changed

UPGRADING

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ PHP 7.4 UPGRADE NOTES
8787
. SplPriorityQueue::setExtractFlags() will throw an exception if zero is
8888
passed. Previously this would generate a recoverable fatal error on the
8989
next extraction operation.
90+
. ArrayObject, ArrayIterator, SplDoublyLinkedList and SplObjectStorage now
91+
support the __serialize() + __unserialize() mechanism in addition to the
92+
Serializable interface. This means that serialization payloads created on
93+
older PHP versions can still be unserialized, but new payloads created by
94+
PHP 7.4 will not be understood by older versions.
9095

9196
- Standard:
9297
. The "o" serialization format has been removed. As it is never produced by

ext/spl/spl_array.c

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1812,6 +1812,80 @@ SPL_METHOD(Array, unserialize)
18121812

18131813
} /* }}} */
18141814

1815+
/* {{{ proto array ArrayObject::__serialize() */
1816+
SPL_METHOD(Array, __serialize)
1817+
{
1818+
spl_array_object *intern = Z_SPLARRAY_P(ZEND_THIS);
1819+
zval tmp;
1820+
1821+
if (zend_parse_parameters_none_throw() == FAILURE) {
1822+
return;
1823+
}
1824+
1825+
array_init(return_value);
1826+
1827+
/* flags */
1828+
ZVAL_LONG(&tmp, (intern->ar_flags & SPL_ARRAY_CLONE_MASK));
1829+
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
1830+
1831+
/* storage */
1832+
if (intern->ar_flags & SPL_ARRAY_IS_SELF) {
1833+
ZVAL_NULL(&tmp);
1834+
} else {
1835+
ZVAL_COPY(&tmp, &intern->array);
1836+
}
1837+
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
1838+
1839+
/* members */
1840+
ZVAL_ARR(&tmp, zend_std_get_properties(ZEND_THIS));
1841+
Z_TRY_ADDREF(tmp);
1842+
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
1843+
}
1844+
/* }}} */
1845+
1846+
1847+
/* {{{ proto void ArrayObject::__unserialize(array data) */
1848+
SPL_METHOD(Array, __unserialize)
1849+
{
1850+
spl_array_object *intern = Z_SPLARRAY_P(ZEND_THIS);
1851+
HashTable *data;
1852+
zval *flags_zv, *storage_zv, *members_zv;
1853+
zend_long flags;
1854+
1855+
if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "h", &data) == FAILURE) {
1856+
return;
1857+
}
1858+
1859+
flags_zv = zend_hash_index_find(data, 0);
1860+
storage_zv = zend_hash_index_find(data, 1);
1861+
members_zv = zend_hash_index_find(data, 2);
1862+
if (!flags_zv || !storage_zv || !members_zv ||
1863+
Z_TYPE_P(flags_zv) != IS_LONG || Z_TYPE_P(members_zv) != IS_ARRAY) {
1864+
zend_throw_exception(spl_ce_UnexpectedValueException,
1865+
"Incomplete or ill-typed serialization data", 0);
1866+
return;
1867+
}
1868+
1869+
flags = Z_LVAL_P(flags_zv);
1870+
intern->ar_flags &= ~SPL_ARRAY_CLONE_MASK;
1871+
intern->ar_flags |= flags & SPL_ARRAY_CLONE_MASK;
1872+
1873+
if (flags & SPL_ARRAY_IS_SELF) {
1874+
zval_ptr_dtor(&intern->array);
1875+
ZVAL_UNDEF(&intern->array);
1876+
} else if (Z_TYPE_P(storage_zv) == IS_ARRAY) {
1877+
zval_ptr_dtor(&intern->array);
1878+
ZVAL_COPY_VALUE(&intern->array, storage_zv);
1879+
ZVAL_NULL(storage_zv);
1880+
SEPARATE_ARRAY(&intern->array);
1881+
} else {
1882+
spl_array_set_array(ZEND_THIS, intern, storage_zv, 0L, 1);
1883+
}
1884+
1885+
object_properties_load(&intern->std, Z_ARRVAL_P(members_zv));
1886+
}
1887+
/* }}} */
1888+
18151889
/* {{{ arginfo and function table */
18161890
ZEND_BEGIN_ARG_INFO_EX(arginfo_array___construct, 0, 0, 0)
18171891
ZEND_ARG_INFO(0, input)
@@ -1884,6 +1958,8 @@ static const zend_function_entry spl_funcs_ArrayObject[] = {
18841958
SPL_ME(Array, natcasesort, arginfo_array_void, ZEND_ACC_PUBLIC)
18851959
SPL_ME(Array, unserialize, arginfo_array_unserialize, ZEND_ACC_PUBLIC)
18861960
SPL_ME(Array, serialize, arginfo_array_void, ZEND_ACC_PUBLIC)
1961+
SPL_ME(Array, __unserialize, arginfo_array_unserialize, ZEND_ACC_PUBLIC)
1962+
SPL_ME(Array, __serialize, arginfo_array_void, ZEND_ACC_PUBLIC)
18871963
/* ArrayObject specific */
18881964
SPL_ME(Array, getIterator, arginfo_array_void, ZEND_ACC_PUBLIC)
18891965
SPL_ME(Array, exchangeArray, arginfo_array_exchangeArray, ZEND_ACC_PUBLIC)
@@ -1911,6 +1987,8 @@ static const zend_function_entry spl_funcs_ArrayIterator[] = {
19111987
SPL_ME(Array, natcasesort, arginfo_array_void, ZEND_ACC_PUBLIC)
19121988
SPL_ME(Array, unserialize, arginfo_array_unserialize, ZEND_ACC_PUBLIC)
19131989
SPL_ME(Array, serialize, arginfo_array_void, ZEND_ACC_PUBLIC)
1990+
SPL_ME(Array, __unserialize, arginfo_array_unserialize, ZEND_ACC_PUBLIC)
1991+
SPL_ME(Array, __serialize, arginfo_array_void, ZEND_ACC_PUBLIC)
19141992
/* ArrayIterator specific */
19151993
SPL_ME(Array, rewind, arginfo_array_void, ZEND_ACC_PUBLIC)
19161994
SPL_ME(Array, current, arginfo_array_void, ZEND_ACC_PUBLIC)

ext/spl/spl_dllist.c

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,6 +1220,67 @@ SPL_METHOD(SplDoublyLinkedList, unserialize)
12201220

12211221
} /* }}} */
12221222

1223+
/* {{{ proto array SplDoublyLinkedList::__serialize() */
1224+
SPL_METHOD(SplDoublyLinkedList, __serialize)
1225+
{
1226+
spl_dllist_object *intern = Z_SPLDLLIST_P(ZEND_THIS);
1227+
spl_ptr_llist_element *current = intern->llist->head;
1228+
zval tmp;
1229+
1230+
if (zend_parse_parameters_none_throw() == FAILURE) {
1231+
return;
1232+
}
1233+
1234+
array_init(return_value);
1235+
1236+
/* flags */
1237+
ZVAL_LONG(&tmp, intern->flags);
1238+
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
1239+
1240+
/* elements */
1241+
array_init_size(&tmp, intern->llist->count);
1242+
while (current) {
1243+
zend_hash_next_index_insert(Z_ARRVAL(tmp), &current->data);
1244+
current = current->next;
1245+
}
1246+
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
1247+
1248+
/* members */
1249+
ZVAL_ARR(&tmp, zend_std_get_properties(ZEND_THIS));
1250+
Z_TRY_ADDREF(tmp);
1251+
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
1252+
} /* }}} */
1253+
1254+
/* {{{ proto void SplDoublyLinkedList::__unserialize(array serialized) */
1255+
SPL_METHOD(SplDoublyLinkedList, __unserialize) {
1256+
spl_dllist_object *intern = Z_SPLDLLIST_P(ZEND_THIS);
1257+
HashTable *data;
1258+
zval *flags_zv, *storage_zv, *members_zv, *elem;
1259+
1260+
if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "h", &data) == FAILURE) {
1261+
return;
1262+
}
1263+
1264+
flags_zv = zend_hash_index_find(data, 0);
1265+
storage_zv = zend_hash_index_find(data, 1);
1266+
members_zv = zend_hash_index_find(data, 2);
1267+
if (!flags_zv || !storage_zv || !members_zv ||
1268+
Z_TYPE_P(flags_zv) != IS_LONG || Z_TYPE_P(storage_zv) != IS_ARRAY ||
1269+
Z_TYPE_P(members_zv) != IS_ARRAY) {
1270+
zend_throw_exception(spl_ce_UnexpectedValueException,
1271+
"Incomplete or ill-typed serialization data", 0);
1272+
return;
1273+
}
1274+
1275+
intern->flags = (int) Z_LVAL_P(flags_zv);
1276+
1277+
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(storage_zv), elem) {
1278+
spl_ptr_llist_push(intern->llist, elem);
1279+
} ZEND_HASH_FOREACH_END();
1280+
1281+
object_properties_load(&intern->std, Z_ARRVAL_P(members_zv));
1282+
} /* }}} */
1283+
12231284
/* {{{ proto void SplDoublyLinkedList::add(mixed index, mixed newval)
12241285
Inserts a new entry before the specified $index consisting of $newval. */
12251286
SPL_METHOD(SplDoublyLinkedList, add)
@@ -1374,6 +1435,8 @@ static const zend_function_entry spl_funcs_SplDoublyLinkedList[] = {
13741435
/* Serializable */
13751436
SPL_ME(SplDoublyLinkedList, unserialize, arginfo_dllist_serialized, ZEND_ACC_PUBLIC)
13761437
SPL_ME(SplDoublyLinkedList, serialize, arginfo_dllist_void, ZEND_ACC_PUBLIC)
1438+
SPL_ME(SplDoublyLinkedList, __unserialize, arginfo_dllist_serialized, ZEND_ACC_PUBLIC)
1439+
SPL_ME(SplDoublyLinkedList, __serialize, arginfo_dllist_void, ZEND_ACC_PUBLIC)
13771440
PHP_FE_END
13781441
};
13791442
/* }}} */

ext/spl/spl_observer.c

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,78 @@ SPL_METHOD(SplObjectStorage, unserialize)
868868

869869
} /* }}} */
870870

871+
/* {{{ proto auto SplObjectStorage::__serialize() */
872+
SPL_METHOD(SplObjectStorage, __serialize)
873+
{
874+
spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
875+
spl_SplObjectStorageElement *elem;
876+
zval tmp;
877+
878+
if (zend_parse_parameters_none_throw() == FAILURE) {
879+
return;
880+
}
881+
882+
array_init(return_value);
883+
884+
/* storage */
885+
array_init_size(&tmp, 2 * zend_hash_num_elements(&intern->storage));
886+
ZEND_HASH_FOREACH_PTR(&intern->storage, elem) {
887+
Z_TRY_ADDREF(elem->obj);
888+
zend_hash_next_index_insert(Z_ARRVAL(tmp), &elem->obj);
889+
Z_TRY_ADDREF(elem->inf);
890+
zend_hash_next_index_insert(Z_ARRVAL(tmp), &elem->inf);
891+
} ZEND_HASH_FOREACH_END();
892+
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
893+
894+
/* members */
895+
ZVAL_ARR(&tmp, zend_std_get_properties(ZEND_THIS));
896+
Z_TRY_ADDREF(tmp);
897+
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
898+
} /* }}} */
899+
900+
/* {{{ proto void SplObjectStorage::__unserialize(array serialized) */
901+
SPL_METHOD(SplObjectStorage, __unserialize)
902+
{
903+
spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
904+
HashTable *data;
905+
zval *storage_zv, *members_zv, *key, *val;
906+
907+
if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "h", &data) == FAILURE) {
908+
return;
909+
}
910+
911+
storage_zv = zend_hash_index_find(data, 0);
912+
members_zv = zend_hash_index_find(data, 1);
913+
if (!storage_zv || !members_zv ||
914+
Z_TYPE_P(storage_zv) != IS_ARRAY || Z_TYPE_P(members_zv) != IS_ARRAY) {
915+
zend_throw_exception(spl_ce_UnexpectedValueException,
916+
"Incomplete or ill-typed serialization data", 0);
917+
return;
918+
}
919+
920+
if (zend_hash_num_elements(Z_ARRVAL_P(storage_zv)) % 2 != 0) {
921+
zend_throw_exception(spl_ce_UnexpectedValueException, "Odd number of elements", 0);
922+
return;
923+
}
924+
925+
key = NULL;
926+
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(storage_zv), val) {
927+
if (key) {
928+
if (Z_TYPE_P(key) != IS_OBJECT) {
929+
zend_throw_exception(spl_ce_UnexpectedValueException, "Non-object key", 0);
930+
return;
931+
}
932+
933+
spl_object_storage_attach(intern, ZEND_THIS, key, val);
934+
key = NULL;
935+
} else {
936+
key = val;
937+
}
938+
} ZEND_HASH_FOREACH_END();
939+
940+
object_properties_load(&intern->std, Z_ARRVAL_P(members_zv));
941+
}
942+
871943
ZEND_BEGIN_ARG_INFO(arginfo_Object, 0)
872944
ZEND_ARG_INFO(0, object)
873945
ZEND_END_ARG_INFO();
@@ -917,6 +989,8 @@ static const zend_function_entry spl_funcs_SplObjectStorage[] = {
917989
/* Serializable */
918990
SPL_ME(SplObjectStorage, unserialize, arginfo_Serialized, 0)
919991
SPL_ME(SplObjectStorage, serialize, arginfo_splobject_void,0)
992+
SPL_ME(SplObjectStorage, __unserialize, arginfo_Serialized, 0)
993+
SPL_ME(SplObjectStorage, __serialize, arginfo_splobject_void,0)
920994
/* ArrayAccess */
921995
SPL_MA(SplObjectStorage, offsetExists, SplObjectStorage, contains, arginfo_offsetGet, 0)
922996
SPL_MA(SplObjectStorage, offsetSet, SplObjectStorage, attach, arginfo_attach, 0)

ext/spl/tests/SplDoublyLinkedList_serialization.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ object(SplQueue)#%d (2) {
2929
string(1) "b"
3030
}
3131
}
32-
string(42) "C:8:"SplQueue":22:{i:4;:s:1:"a";:s:1:"b";}"
32+
string(71) "O:8:"SplQueue":3:{i:0;i:4;i:1;a:2:{i:0;s:1:"a";i:1;s:1:"b";}i:2;a:0:{}}"
3333
object(SplQueue)#%d (2) {
3434
["flags":"SplDoublyLinkedList":private]=>
3535
int(4)
@@ -52,7 +52,7 @@ object(SplStack)#%d (2) {
5252
string(1) "b"
5353
}
5454
}
55-
string(42) "C:8:"SplStack":22:{i:6;:s:1:"a";:s:1:"b";}"
55+
string(71) "O:8:"SplStack":3:{i:0;i:6;i:1;a:2:{i:0;s:1:"a";i:1;s:1:"b";}i:2;a:0:{}}"
5656
object(SplStack)#%d (2) {
5757
["flags":"SplDoublyLinkedList":private]=>
5858
int(6)

ext/spl/tests/SplObjectStorage_unserialize_nested.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ echo $s."\n";
1818
$so1 = unserialize($s);
1919
var_dump($so1);
2020
--EXPECTF--
21-
C:16:"SplObjectStorage":76:{x:i:2;O:8:"stdClass":1:{s:1:"a";O:8:"stdClass":0:{}},i:1;;r:4;,i:2;;m:a:0:{}}
21+
O:16:"SplObjectStorage":2:{i:0;a:4:{i:0;O:8:"stdClass":1:{s:1:"a";O:8:"stdClass":0:{}}i:1;i:1;i:2;r:4;i:3;i:2;}i:1;a:0:{}}
2222
object(SplObjectStorage)#4 (1) {
2323
["storage":"SplObjectStorage":private]=>
2424
array(2) {

ext/spl/tests/array_025.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ ArrayObject Object
2424
)
2525

2626
)
27-
C:11:"ArrayObject":76:{x:i:0;C:11:"ArrayObject":37:{x:i:0;a:2:{i:0;i:1;i:1;i:2;};m:a:0:{}};m:a:0:{}}
27+
O:11:"ArrayObject":3:{i:0;i:0;i:1;O:11:"ArrayObject":3:{i:0;i:0;i:1;a:2:{i:0;i:1;i:1;i:2;}i:2;a:0:{}}i:2;a:0:{}}
2828
ArrayObject Object
2929
(
3030
[storage:ArrayObject:private] => ArrayObject Object

ext/spl/tests/bug45826.phpt

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ var_dump($o2[2][2] === $o2[2]);
3131
echo "#### Extending ArrayObject\n";
3232
unset($o,$x,$s1,$s2,$o1,$o2);
3333
class ArrayObject2 extends ArrayObject {
34-
public function serialize() {
35-
return parent::serialize();
34+
public function __serialize() {
35+
return parent::__serialize();
3636
}
3737

38-
public function unserialize($s) {
39-
return parent::unserialize($s);
38+
public function __unserialize($s) {
39+
return parent::__unserialize($s);
4040
}
4141
}
4242

@@ -50,17 +50,17 @@ var_dump($o[0] === $o[1]);
5050
var_dump($o[2] === $o);
5151

5252
$s1 = serialize($o);
53-
$s2 = $o->serialize();
53+
$s2 = $o->__serialize();
5454
var_dump($s1);
5555
var_dump($s2);
5656

57-
$o1 =unserialize($s1);
57+
$o1 = unserialize($s1);
5858

5959
var_dump($o1[0] === $o1[1]);
6060
var_dump($o1[2] === $o1);
6161

6262
$o2 = new ArrayObject2();
63-
$o2->unserialize($s2);
63+
$o2->__unserialize($s2);
6464

6565
var_dump($o2[0] === $o2[1]);
6666
var_dump($o2[2] !== $o2);
@@ -69,8 +69,8 @@ var_dump($o2[2][2] === $o2[2]);
6969
--EXPECT--
7070
bool(true)
7171
bool(true)
72-
string(84) "C:11:"ArrayObject":60:{x:i:0;a:3:{i:0;O:8:"stdClass":0:{}i:1;r:4;i:2;r:1;};m:a:0:{}}"
73-
string(125) "x:i:0;a:3:{i:0;O:8:"stdClass":0:{}i:1;r:3;i:2;C:11:"ArrayObject":45:{x:i:0;a:3:{i:0;r:3;i:1;r:3;i:2;r:5;};m:a:0:{}}};m:a:0:{}"
72+
string(90) "O:11:"ArrayObject":3:{i:0;i:0;i:1;a:3:{i:0;O:8:"stdClass":0:{}i:1;r:4;i:2;r:1;}i:2;a:0:{}}"
73+
string(131) "x:i:0;a:3:{i:0;O:8:"stdClass":0:{}i:1;r:3;i:2;O:11:"ArrayObject":3:{i:0;i:0;i:1;a:3:{i:0;r:3;i:1;r:3;i:2;r:5;}i:2;a:0:{}}};m:a:0:{}"
7474
bool(true)
7575
bool(true)
7676
bool(true)
@@ -79,8 +79,28 @@ bool(true)
7979
#### Extending ArrayObject
8080
bool(true)
8181
bool(true)
82-
string(85) "C:12:"ArrayObject2":60:{x:i:0;a:3:{i:0;O:8:"stdClass":0:{}i:1;r:4;i:2;r:1;};m:a:0:{}}"
83-
string(126) "x:i:0;a:3:{i:0;O:8:"stdClass":0:{}i:1;r:3;i:2;C:12:"ArrayObject2":45:{x:i:0;a:3:{i:0;r:3;i:1;r:3;i:2;r:5;};m:a:0:{}}};m:a:0:{}"
82+
string(91) "O:12:"ArrayObject2":3:{i:0;i:0;i:1;a:3:{i:0;O:8:"stdClass":0:{}i:1;r:4;i:2;r:1;}i:2;a:0:{}}"
83+
array(3) {
84+
[0]=>
85+
int(0)
86+
[1]=>
87+
array(3) {
88+
[0]=>
89+
object(stdClass)#8 (0) {
90+
}
91+
[1]=>
92+
object(stdClass)#8 (0) {
93+
}
94+
[2]=>
95+
object(ArrayObject2)#5 (1) {
96+
["storage":"ArrayObject":private]=>
97+
*RECURSION*
98+
}
99+
}
100+
[2]=>
101+
array(0) {
102+
}
103+
}
84104
bool(true)
85105
bool(true)
86106
bool(true)

0 commit comments

Comments
 (0)