-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Fix GH-14402: Add support for serialization in SplPriorityQueue, SplMinHeap and SplMaxHeap #19447
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
2c9bfbb
2236ca2
5f5c0d9
04bb7cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -1075,6 +1075,212 @@ 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); | ||||||||
|
||||||||
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 void 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) { | ||||||||
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name)); | ||||||||
return; | ||||||||
} | ||||||||
|
||||||||
zend_long flags_value = Z_LVAL_P(flags_val); | ||||||||
|
||||||||
if (is_pqueue) { | ||||||||
flags_value &= SPL_PQUEUE_EXTR_MASK; | ||||||||
if (!flags_value) { | ||||||||
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name)); | ||||||||
return; | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
intern->flags = (int) flags_value; | ||||||||
|
||||||||
zval *heap_elements = zend_hash_str_find(state_ht, "heap_elements", strlen("heap_elements")); | ||||||||
if (!heap_elements) { | ||||||||
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name)); | ||||||||
return; | ||||||||
} | ||||||||
|
||||||||
if (Z_TYPE_P(heap_elements) != IS_ARRAY) { | ||||||||
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name)); | ||||||||
return; | ||||||||
} | ||||||||
|
||||||||
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) { | ||||||||
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name)); | ||||||||
return; | ||||||||
} | ||||||||
|
||||||||
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) { | ||||||||
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name)); | ||||||||
return; | ||||||||
} | ||||||||
|
||||||||
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); | ||||||||
} else { | ||||||||
Z_TRY_ADDREF_P(val); | ||||||||
spl_ptr_heap_insert(intern->heap, val, this_ptr); | ||||||||
} | ||||||||
} ZEND_HASH_FOREACH_END(); | ||||||||
} | ||||||||
|
||||||||
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(); | ||||||||
} | ||||||||
|
||||||||
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(); | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For this one you should throw to wrap the exception into the “Invalid serialization data for %s object” one. When throwing while an exception is already active, the engine will automatically set
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In case object_properties_load() sets one in the executor globals, alright I get it. PR updated |
||||||||
} | ||||||||
|
||||||||
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(); | ||||||||
} | ||||||||
|
||||||||
spl_heap_unserialize_internal_state(Z_ARRVAL_P(state), intern, ZEND_THIS, true); | ||||||||
if (EG(exception)) { | ||||||||
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(); | ||||||||
} | ||||||||
|
||||||||
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(); | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||||||||
} | ||||||||
|
||||||||
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(); | ||||||||
} | ||||||||
|
||||||||
spl_heap_unserialize_internal_state(Z_ARRVAL_P(state), intern, ZEND_THIS, false); | ||||||||
if (EG(exception)) { | ||||||||
RETURN_THROWS(); | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
/* iterator handler table */ | ||||||||
static const zend_object_iterator_funcs spl_heap_it_funcs = { | ||||||||
spl_heap_it_dtor, | ||||||||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
--TEST-- | ||
SplHeap and SplPriorityQueue serialization fails when corrupted | ||
--FILE-- | ||
<?php | ||
|
||
class ThrowingHeap extends SplMaxHeap { | ||
public function compare($a, $b): int { | ||
if ($a === 'throw' || $b === 'throw') { | ||
throw new Exception('Comparison failed'); | ||
} | ||
return parent::compare($a, $b); | ||
} | ||
} | ||
|
||
$heap = new ThrowingHeap(); | ||
$heap->insert(1); | ||
$heap->insert(2); | ||
|
||
try { | ||
$heap->insert('throw'); | ||
} catch (Exception $e) { | ||
// no-op, heap should now be corrupted | ||
} | ||
|
||
echo "Heap is corrupted: " . ($heap->isCorrupted() ? 'YES' : 'NO') . "\n"; | ||
|
||
try { | ||
serialize($heap); | ||
echo "FAIL: Serialization should have thrown\n"; | ||
} catch (Exception $e) { | ||
echo "Serialization failed: " . $e->getMessage() . "\n"; | ||
} | ||
|
||
class ThrowingPQ extends SplPriorityQueue { | ||
public function compare($priority1, $priority2): int { | ||
if ($priority1 === 'throw' || $priority2 === 'throw') { | ||
throw new Exception('Priority comparison failed'); | ||
} | ||
return parent::compare($priority1, $priority2); | ||
} | ||
} | ||
|
||
$pq = new ThrowingPQ(); | ||
$pq->insert('data1', 1); | ||
$pq->insert('data2', 2); | ||
|
||
try { | ||
$pq->insert('data3', 'throw'); | ||
} catch (Exception $e) { | ||
// no-op, queue is corrupted | ||
} | ||
|
||
echo "PriorityQueue is corrupted: " . ($pq->isCorrupted() ? 'YES' : 'NO') . "\n"; | ||
|
||
try { | ||
serialize($pq); | ||
echo "FAIL: PQ Serialization should have thrown\n"; | ||
} catch (Exception $e) { | ||
echo "PQ Serialization failed: " . $e->getMessage() . "\n"; | ||
} | ||
|
||
?> | ||
--EXPECT-- | ||
Heap is corrupted: YES | ||
Serialization failed: Heap is corrupted, heap properties are no longer ensured. | ||
PriorityQueue is corrupted: YES | ||
PQ Serialization failed: Heap is corrupted, heap properties are no longer ensured. |
Uh oh!
There was an error while loading. Please reload this page.