Skip to content

Commit

Permalink
Fix bug #77866: Port Serializable SPL classes to use __unserialize()
Browse files Browse the repository at this point in the history
Payloads created using Serializable are still supported.
  • Loading branch information
nikic committed Apr 10, 2019
1 parent cb145e1 commit e2ea0f1
Show file tree
Hide file tree
Showing 13 changed files with 406 additions and 21 deletions.
5 changes: 5 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ PHP 7.4 UPGRADE NOTES
. SplPriorityQueue::setExtractFlags() will throw an exception if zero is
passed. Previously this would generate a recoverable fatal error on the
next extraction operation.
. ArrayObject, ArrayIterator, SplDoublyLinkedList and SplObjectStorage now
support the __serialize() + __unserialize() mechanism in addition to the
Serializable interface. This means that serialization payloads created on
older PHP versions can still be unserialized, but new payloads created by
PHP 7.4 will not be understood by older versions.

- Standard:
. The "o" serialization format has been removed. As it is never produced by
Expand Down
78 changes: 78 additions & 0 deletions ext/spl/spl_array.c
Original file line number Diff line number Diff line change
Expand Up @@ -1812,6 +1812,80 @@ SPL_METHOD(Array, unserialize)

} /* }}} */

/* {{{ proto array ArrayObject::__serialize() */
SPL_METHOD(Array, __serialize)
{
spl_array_object *intern = Z_SPLARRAY_P(ZEND_THIS);
zval tmp;

if (zend_parse_parameters_none_throw() == FAILURE) {
return;
}

array_init(return_value);

/* flags */
ZVAL_LONG(&tmp, (intern->ar_flags & SPL_ARRAY_CLONE_MASK));
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);

/* storage */
if (intern->ar_flags & SPL_ARRAY_IS_SELF) {
ZVAL_NULL(&tmp);
} else {
ZVAL_COPY(&tmp, &intern->array);
}
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);

/* members */
ZVAL_ARR(&tmp, zend_std_get_properties(ZEND_THIS));
Z_TRY_ADDREF(tmp);
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
}
/* }}} */


/* {{{ proto void ArrayObject::__unserialize(array data) */
SPL_METHOD(Array, __unserialize)
{
spl_array_object *intern = Z_SPLARRAY_P(ZEND_THIS);
HashTable *data;
zval *flags_zv, *storage_zv, *members_zv;
zend_long flags;

if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "h", &data) == FAILURE) {
return;
}

flags_zv = zend_hash_index_find(data, 0);
storage_zv = zend_hash_index_find(data, 1);
members_zv = zend_hash_index_find(data, 2);
if (!flags_zv || !storage_zv || !members_zv ||
Z_TYPE_P(flags_zv) != IS_LONG || Z_TYPE_P(members_zv) != IS_ARRAY) {
zend_throw_exception(spl_ce_UnexpectedValueException,
"Incomplete or ill-typed serialization data", 0);
return;
}

flags = Z_LVAL_P(flags_zv);
intern->ar_flags &= ~SPL_ARRAY_CLONE_MASK;
intern->ar_flags |= flags & SPL_ARRAY_CLONE_MASK;

if (flags & SPL_ARRAY_IS_SELF) {
zval_ptr_dtor(&intern->array);
ZVAL_UNDEF(&intern->array);
} else if (Z_TYPE_P(storage_zv) == IS_ARRAY) {
zval_ptr_dtor(&intern->array);
ZVAL_COPY_VALUE(&intern->array, storage_zv);
ZVAL_NULL(storage_zv);
SEPARATE_ARRAY(&intern->array);
} else {
spl_array_set_array(ZEND_THIS, intern, storage_zv, 0L, 1);
}

object_properties_load(&intern->std, Z_ARRVAL_P(members_zv));
}
/* }}} */

/* {{{ arginfo and function table */
ZEND_BEGIN_ARG_INFO_EX(arginfo_array___construct, 0, 0, 0)
ZEND_ARG_INFO(0, input)
Expand Down Expand Up @@ -1884,6 +1958,8 @@ static const zend_function_entry spl_funcs_ArrayObject[] = {
SPL_ME(Array, natcasesort, arginfo_array_void, ZEND_ACC_PUBLIC)
SPL_ME(Array, unserialize, arginfo_array_unserialize, ZEND_ACC_PUBLIC)
SPL_ME(Array, serialize, arginfo_array_void, ZEND_ACC_PUBLIC)
SPL_ME(Array, __unserialize, arginfo_array_unserialize, ZEND_ACC_PUBLIC)
SPL_ME(Array, __serialize, arginfo_array_void, ZEND_ACC_PUBLIC)
/* ArrayObject specific */
SPL_ME(Array, getIterator, arginfo_array_void, ZEND_ACC_PUBLIC)
SPL_ME(Array, exchangeArray, arginfo_array_exchangeArray, ZEND_ACC_PUBLIC)
Expand Down Expand Up @@ -1911,6 +1987,8 @@ static const zend_function_entry spl_funcs_ArrayIterator[] = {
SPL_ME(Array, natcasesort, arginfo_array_void, ZEND_ACC_PUBLIC)
SPL_ME(Array, unserialize, arginfo_array_unserialize, ZEND_ACC_PUBLIC)
SPL_ME(Array, serialize, arginfo_array_void, ZEND_ACC_PUBLIC)
SPL_ME(Array, __unserialize, arginfo_array_unserialize, ZEND_ACC_PUBLIC)
SPL_ME(Array, __serialize, arginfo_array_void, ZEND_ACC_PUBLIC)
/* ArrayIterator specific */
SPL_ME(Array, rewind, arginfo_array_void, ZEND_ACC_PUBLIC)
SPL_ME(Array, current, arginfo_array_void, ZEND_ACC_PUBLIC)
Expand Down
63 changes: 63 additions & 0 deletions ext/spl/spl_dllist.c
Original file line number Diff line number Diff line change
Expand Up @@ -1220,6 +1220,67 @@ SPL_METHOD(SplDoublyLinkedList, unserialize)

} /* }}} */

/* {{{ proto array SplDoublyLinkedList::__serialize() */
SPL_METHOD(SplDoublyLinkedList, __serialize)
{
spl_dllist_object *intern = Z_SPLDLLIST_P(ZEND_THIS);
spl_ptr_llist_element *current = intern->llist->head;
zval tmp;

if (zend_parse_parameters_none_throw() == FAILURE) {
return;
}

array_init(return_value);

/* flags */
ZVAL_LONG(&tmp, intern->flags);
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);

/* elements */
array_init_size(&tmp, intern->llist->count);
while (current) {
zend_hash_next_index_insert(Z_ARRVAL(tmp), &current->data);
current = current->next;
}
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);

/* members */
ZVAL_ARR(&tmp, zend_std_get_properties(ZEND_THIS));
Z_TRY_ADDREF(tmp);
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
} /* }}} */

/* {{{ proto void SplDoublyLinkedList::__unserialize(array serialized) */
SPL_METHOD(SplDoublyLinkedList, __unserialize) {
spl_dllist_object *intern = Z_SPLDLLIST_P(ZEND_THIS);
HashTable *data;
zval *flags_zv, *storage_zv, *members_zv, *elem;

if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "h", &data) == FAILURE) {
return;
}

flags_zv = zend_hash_index_find(data, 0);
storage_zv = zend_hash_index_find(data, 1);
members_zv = zend_hash_index_find(data, 2);
if (!flags_zv || !storage_zv || !members_zv ||
Z_TYPE_P(flags_zv) != IS_LONG || Z_TYPE_P(storage_zv) != IS_ARRAY ||
Z_TYPE_P(members_zv) != IS_ARRAY) {
zend_throw_exception(spl_ce_UnexpectedValueException,
"Incomplete or ill-typed serialization data", 0);
return;
}

intern->flags = (int) Z_LVAL_P(flags_zv);

ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(storage_zv), elem) {
spl_ptr_llist_push(intern->llist, elem);
} ZEND_HASH_FOREACH_END();

object_properties_load(&intern->std, Z_ARRVAL_P(members_zv));
} /* }}} */

/* {{{ proto void SplDoublyLinkedList::add(mixed index, mixed newval)
Inserts a new entry before the specified $index consisting of $newval. */
SPL_METHOD(SplDoublyLinkedList, add)
Expand Down Expand Up @@ -1374,6 +1435,8 @@ static const zend_function_entry spl_funcs_SplDoublyLinkedList[] = {
/* Serializable */
SPL_ME(SplDoublyLinkedList, unserialize, arginfo_dllist_serialized, ZEND_ACC_PUBLIC)
SPL_ME(SplDoublyLinkedList, serialize, arginfo_dllist_void, ZEND_ACC_PUBLIC)
SPL_ME(SplDoublyLinkedList, __unserialize, arginfo_dllist_serialized, ZEND_ACC_PUBLIC)
SPL_ME(SplDoublyLinkedList, __serialize, arginfo_dllist_void, ZEND_ACC_PUBLIC)
PHP_FE_END
};
/* }}} */
Expand Down
74 changes: 74 additions & 0 deletions ext/spl/spl_observer.c
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,78 @@ SPL_METHOD(SplObjectStorage, unserialize)

} /* }}} */

/* {{{ proto auto SplObjectStorage::__serialize() */
SPL_METHOD(SplObjectStorage, __serialize)
{
spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
spl_SplObjectStorageElement *elem;
zval tmp;

if (zend_parse_parameters_none_throw() == FAILURE) {
return;
}

array_init(return_value);

/* storage */
array_init_size(&tmp, 2 * zend_hash_num_elements(&intern->storage));
ZEND_HASH_FOREACH_PTR(&intern->storage, elem) {
Z_TRY_ADDREF(elem->obj);
zend_hash_next_index_insert(Z_ARRVAL(tmp), &elem->obj);
Z_TRY_ADDREF(elem->inf);
zend_hash_next_index_insert(Z_ARRVAL(tmp), &elem->inf);
} ZEND_HASH_FOREACH_END();
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);

/* members */
ZVAL_ARR(&tmp, zend_std_get_properties(ZEND_THIS));
Z_TRY_ADDREF(tmp);
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
} /* }}} */

/* {{{ proto void SplObjectStorage::__unserialize(array serialized) */
SPL_METHOD(SplObjectStorage, __unserialize)
{
spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
HashTable *data;
zval *storage_zv, *members_zv, *key, *val;

if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "h", &data) == FAILURE) {
return;
}

storage_zv = zend_hash_index_find(data, 0);
members_zv = zend_hash_index_find(data, 1);
if (!storage_zv || !members_zv ||
Z_TYPE_P(storage_zv) != IS_ARRAY || Z_TYPE_P(members_zv) != IS_ARRAY) {
zend_throw_exception(spl_ce_UnexpectedValueException,
"Incomplete or ill-typed serialization data", 0);
return;
}

if (zend_hash_num_elements(Z_ARRVAL_P(storage_zv)) % 2 != 0) {
zend_throw_exception(spl_ce_UnexpectedValueException, "Odd number of elements", 0);
return;
}

key = NULL;
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(storage_zv), val) {
if (key) {
if (Z_TYPE_P(key) != IS_OBJECT) {
zend_throw_exception(spl_ce_UnexpectedValueException, "Non-object key", 0);
return;
}

spl_object_storage_attach(intern, ZEND_THIS, key, val);
key = NULL;
} else {
key = val;
}
} ZEND_HASH_FOREACH_END();

object_properties_load(&intern->std, Z_ARRVAL_P(members_zv));
}

ZEND_BEGIN_ARG_INFO(arginfo_Object, 0)
ZEND_ARG_INFO(0, object)
ZEND_END_ARG_INFO();
Expand Down Expand Up @@ -917,6 +989,8 @@ static const zend_function_entry spl_funcs_SplObjectStorage[] = {
/* Serializable */
SPL_ME(SplObjectStorage, unserialize, arginfo_Serialized, 0)
SPL_ME(SplObjectStorage, serialize, arginfo_splobject_void,0)
SPL_ME(SplObjectStorage, __unserialize, arginfo_Serialized, 0)
SPL_ME(SplObjectStorage, __serialize, arginfo_splobject_void,0)
/* ArrayAccess */
SPL_MA(SplObjectStorage, offsetExists, SplObjectStorage, contains, arginfo_offsetGet, 0)
SPL_MA(SplObjectStorage, offsetSet, SplObjectStorage, attach, arginfo_attach, 0)
Expand Down
4 changes: 2 additions & 2 deletions ext/spl/tests/SplDoublyLinkedList_serialization.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ object(SplQueue)#%d (2) {
string(1) "b"
}
}
string(42) "C:8:"SplQueue":22:{i:4;:s:1:"a";:s:1:"b";}"
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:{}}"
object(SplQueue)#%d (2) {
["flags":"SplDoublyLinkedList":private]=>
int(4)
Expand All @@ -52,7 +52,7 @@ object(SplStack)#%d (2) {
string(1) "b"
}
}
string(42) "C:8:"SplStack":22:{i:6;:s:1:"a";:s:1:"b";}"
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:{}}"
object(SplStack)#%d (2) {
["flags":"SplDoublyLinkedList":private]=>
int(6)
Expand Down
2 changes: 1 addition & 1 deletion ext/spl/tests/SplObjectStorage_unserialize_nested.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ echo $s."\n";
$so1 = unserialize($s);
var_dump($so1);
--EXPECTF--
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:{}}
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:{}}
object(SplObjectStorage)#4 (1) {
["storage":"SplObjectStorage":private]=>
array(2) {
Expand Down
2 changes: 1 addition & 1 deletion ext/spl/tests/array_025.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ ArrayObject Object
)

)
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:{}}
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:{}}
ArrayObject Object
(
[storage:ArrayObject:private] => ArrayObject Object
Expand Down
42 changes: 31 additions & 11 deletions ext/spl/tests/bug45826.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ var_dump($o2[2][2] === $o2[2]);
echo "#### Extending ArrayObject\n";
unset($o,$x,$s1,$s2,$o1,$o2);
class ArrayObject2 extends ArrayObject {
public function serialize() {
return parent::serialize();
public function __serialize() {
return parent::__serialize();
}

public function unserialize($s) {
return parent::unserialize($s);
public function __unserialize($s) {
return parent::__unserialize($s);
}
}

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

$s1 = serialize($o);
$s2 = $o->serialize();
$s2 = $o->__serialize();
var_dump($s1);
var_dump($s2);

$o1 =unserialize($s1);
$o1 = unserialize($s1);

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

$o2 = new ArrayObject2();
$o2->unserialize($s2);
$o2->__unserialize($s2);

var_dump($o2[0] === $o2[1]);
var_dump($o2[2] !== $o2);
Expand All @@ -69,8 +69,8 @@ var_dump($o2[2][2] === $o2[2]);
--EXPECT--
bool(true)
bool(true)
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:{}}"
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:{}"
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:{}}"
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:{}"
bool(true)
bool(true)
bool(true)
Expand All @@ -79,8 +79,28 @@ bool(true)
#### Extending ArrayObject
bool(true)
bool(true)
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:{}}"
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:{}"
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:{}}"
array(3) {
[0]=>
int(0)
[1]=>
array(3) {
[0]=>
object(stdClass)#8 (0) {
}
[1]=>
object(stdClass)#8 (0) {
}
[2]=>
object(ArrayObject2)#5 (1) {
["storage":"ArrayObject":private]=>
*RECURSION*
}
}
[2]=>
array(0) {
}
}
bool(true)
bool(true)
bool(true)
Expand Down
Loading

0 comments on commit e2ea0f1

Please sign in to comment.