Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ PHP NEWS
(Girgias)
. Fixed bug GH-19801 (leaks in var_dump() and debug_zval_dump()).
(alexandre-daubois)
. Fixed GH-14402 (SplPriorityQueue, SplMinHeap, and SplMaxHeap lost their
data on serialize()). (alexandre-daubois)

- URI:
. Fixed bug GH-19780 (InvalidUrlException should check $errors argument).
Expand Down
241 changes: 241 additions & 0 deletions ext/spl/spl_heap.c
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,247 @@ PHP_METHOD(SplPriorityQueue, __debugInfo)
RETURN_ARR(spl_heap_object_get_debug_info(spl_ce_SplPriorityQueue, Z_OBJ_P(ZEND_THIS)));
} /* }}} */

static void spl_heap_serialize_internal_state(zval *return_value, spl_heap_object *intern, bool is_pqueue)
{
zval heap_elements;
int heap_count = intern->heap->count;

array_init(return_value);
add_assoc_long(return_value, "flags", intern->flags);

array_init_size(&heap_elements, heap_count);

if (heap_count == 0) {
add_assoc_zval(return_value, "heap_elements", &heap_elements);
return;
}

for (int heap_idx = 0; heap_idx < heap_count; ++heap_idx) {
if (is_pqueue) {
spl_pqueue_elem *elem = spl_heap_elem(intern->heap, heap_idx);
zval entry;
array_init(&entry);
add_assoc_zval_ex(&entry, "data", strlen("data"), &elem->data);
Z_TRY_ADDREF(elem->data);
add_assoc_zval_ex(&entry, "priority", strlen("priority"), &elem->priority);
Z_TRY_ADDREF(elem->priority);
zend_hash_next_index_insert(Z_ARRVAL(heap_elements), &entry);
} else {
zval *elem = spl_heap_elem(intern->heap, heap_idx);
zend_hash_next_index_insert(Z_ARRVAL(heap_elements), elem);
Z_TRY_ADDREF_P(elem);
}
}

add_assoc_zval(return_value, "heap_elements", &heap_elements);
}

static zend_result spl_heap_unserialize_internal_state(HashTable *state_ht, spl_heap_object *intern, zval *this_ptr, bool is_pqueue)
{
zval *flags_val = zend_hash_str_find(state_ht, "flags", strlen("flags"));
if (!flags_val || Z_TYPE_P(flags_val) != IS_LONG) {
return FAILURE;
}

zend_long flags_value = Z_LVAL_P(flags_val);

if (is_pqueue) {
flags_value &= SPL_PQUEUE_EXTR_MASK;
if (!flags_value) {
return FAILURE;
}
} else if (flags_value != 0) { /* Regular heaps should not have user-visible flags */
return FAILURE;
}

intern->flags = (int) flags_value;

zval *heap_elements = zend_hash_str_find(state_ht, "heap_elements", strlen("heap_elements"));
if (!heap_elements) {
return FAILURE;
}

if (Z_TYPE_P(heap_elements) != IS_ARRAY) {
return FAILURE;
}

ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(heap_elements), zval *val) {
if (is_pqueue) {
/* PriorityQueue elements are serialized as arrays with 'data' and 'priority' keys */
if (Z_TYPE_P(val) != IS_ARRAY || zend_hash_num_elements(Z_ARRVAL_P(val)) != 2) {
return FAILURE;
}

zval *data_val = zend_hash_str_find(Z_ARRVAL_P(val), "data", strlen("data") );
zval *priority_val = zend_hash_str_find(Z_ARRVAL_P(val), "priority", strlen("priority"));

if (!data_val || !priority_val) {
return FAILURE;
}

spl_pqueue_elem elem;
ZVAL_COPY(&elem.data, data_val);
ZVAL_COPY(&elem.priority, priority_val);
spl_ptr_heap_insert(intern->heap, &elem, this_ptr);
if (EG(exception)) {
return FAILURE;
}
} else {
Z_TRY_ADDREF_P(val);
spl_ptr_heap_insert(intern->heap, val, this_ptr);
if (EG(exception)) {
return FAILURE;
}
}
} ZEND_HASH_FOREACH_END();

return SUCCESS;
}

PHP_METHOD(SplPriorityQueue, __serialize)
{
spl_heap_object *intern = Z_SPLHEAP_P(ZEND_THIS);
zval props, state;

ZEND_PARSE_PARAMETERS_NONE();

if (UNEXPECTED(spl_heap_consistency_validations(intern, false) != SUCCESS)) {
RETURN_THROWS();
}

if (intern->heap->flags & SPL_HEAP_WRITE_LOCKED) {
zend_throw_exception(spl_ce_RuntimeException, "Cannot serialize heap while it is being modified.", 0);
RETURN_THROWS();
}

array_init(return_value);

ZVAL_ARR(&props, zend_std_get_properties(&intern->std));
Z_TRY_ADDREF(props);
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &props);

spl_heap_serialize_internal_state(&state, intern, true);
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &state);
}

PHP_METHOD(SplPriorityQueue, __unserialize)
{
HashTable *data;
spl_heap_object *intern = Z_SPLHEAP_P(ZEND_THIS);

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_HT(data)
ZEND_PARSE_PARAMETERS_END();

if (zend_hash_num_elements(data) != 2) {
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
RETURN_THROWS();
}

zval *props = zend_hash_index_find(data, 0);
if (!props || Z_TYPE_P(props) != IS_ARRAY) {
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
RETURN_THROWS();
}

object_properties_load(&intern->std, Z_ARRVAL_P(props));
if (EG(exception)) {
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
RETURN_THROWS();
}

zval *state = zend_hash_index_find(data, 1);
if (!state || Z_TYPE_P(state) != IS_ARRAY) {
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
RETURN_THROWS();
}

if (spl_heap_unserialize_internal_state(Z_ARRVAL_P(state), intern, ZEND_THIS, true) != SUCCESS) {
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
RETURN_THROWS();
}

if (EG(exception)) {
RETURN_THROWS();
}

if (UNEXPECTED(spl_heap_consistency_validations(intern, false) != SUCCESS)) {
RETURN_THROWS();
}
}

PHP_METHOD(SplHeap, __serialize)
{
spl_heap_object *intern = Z_SPLHEAP_P(ZEND_THIS);
zval props, state;

ZEND_PARSE_PARAMETERS_NONE();

if (UNEXPECTED(spl_heap_consistency_validations(intern, false) != SUCCESS)) {
RETURN_THROWS();
}

if (intern->heap->flags & SPL_HEAP_WRITE_LOCKED) {
zend_throw_exception(spl_ce_RuntimeException, "Cannot serialize heap while it is being modified.", 0);
RETURN_THROWS();
}

array_init(return_value);

ZVAL_ARR(&props, zend_std_get_properties(&intern->std));
Z_TRY_ADDREF(props);
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &props);

spl_heap_serialize_internal_state(&state, intern, false);
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &state);
}

PHP_METHOD(SplHeap, __unserialize)
{
HashTable *data;
spl_heap_object *intern = Z_SPLHEAP_P(ZEND_THIS);

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_HT(data)
ZEND_PARSE_PARAMETERS_END();

if (zend_hash_num_elements(data) != 2) {
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
RETURN_THROWS();
}

zval *props = zend_hash_index_find(data, 0);
if (!props || Z_TYPE_P(props) != IS_ARRAY) {
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
RETURN_THROWS();
}

object_properties_load(&intern->std, Z_ARRVAL_P(props));
if (EG(exception)) {
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
RETURN_THROWS();
}

zval *state = zend_hash_index_find(data, 1);
if (!state || Z_TYPE_P(state) != IS_ARRAY) {
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
RETURN_THROWS();
}

if (spl_heap_unserialize_internal_state(Z_ARRVAL_P(state), intern, ZEND_THIS, false) != SUCCESS) {
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
RETURN_THROWS();
}

if (EG(exception)) {
RETURN_THROWS();
}

if (UNEXPECTED(spl_heap_consistency_validations(intern, false) != SUCCESS)) {
RETURN_THROWS();
}
}

/* iterator handler table */
static const zend_object_iterator_funcs spl_heap_it_funcs = {
spl_heap_it_dtor,
Expand Down
12 changes: 12 additions & 0 deletions ext/spl/spl_heap.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ public function getExtractFlags(): int {}

/** @tentative-return-type */
public function __debugInfo(): array {}

/** @tentative-return-type */
public function __serialize(): array {}

/** @tentative-return-type */
public function __unserialize(array $data): void {}
}

abstract class SplHeap implements Iterator, Countable
Expand Down Expand Up @@ -127,6 +133,12 @@ public function isCorrupted(): bool {}

/** @tentative-return-type */
public function __debugInfo(): array {}

/** @tentative-return-type */
public function __serialize(): array {}

/** @tentative-return-type */
public function __unserialize(array $data): void {}
}

class SplMinHeap extends SplHeap
Expand Down
20 changes: 19 additions & 1 deletion ext/spl/spl_heap_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading