diff --git a/NEWS b/NEWS index 34d5b7d65d8f1..c144a9238616b 100644 --- a/NEWS +++ b/NEWS @@ -84,6 +84,8 @@ PHP NEWS opened directory has been deprecated. (Girgias) . Fixed bug GH-19153 (#[\Attribute] validation should error on trait/interface/enum/abstract class). (DanielEScherzer) + . Fixed GH-14402 (SplPriorityQueue, SplMinHeap, and SplMaxHeap lost their + data on serialize()). (alexandre-daubois) - XML: . The xml_parser_free() function has been deprecated. (DanielEScherzer) diff --git a/ext/spl/spl_heap.c b/ext/spl/spl_heap.c index d4450da42009c..da8b5fdc3460d 100644 --- a/ext/spl/spl_heap.c +++ b/ext/spl/spl_heap.c @@ -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(); + } + + 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(); + } + + 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, diff --git a/ext/spl/spl_heap.stub.php b/ext/spl/spl_heap.stub.php index c545d27dfc0f9..dd38cfb983432 100644 --- a/ext/spl/spl_heap.stub.php +++ b/ext/spl/spl_heap.stub.php @@ -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 @@ -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 diff --git a/ext/spl/spl_heap_arginfo.h b/ext/spl/spl_heap_arginfo.h index 31a3f79fe2c75..be5318a68f8c6 100644 --- a/ext/spl/spl_heap_arginfo.h +++ b/ext/spl/spl_heap_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 47273e114c9c7089bf708a2f18f2e9e522abceb6 */ + * Stub hash: 3256398ed9e798f141fd3cb73370c0d8b2dbd0f1 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_SplPriorityQueue_compare, 0, 2, IS_LONG, 0) ZEND_ARG_TYPE_INFO(0, priority1, IS_MIXED, 0) @@ -47,6 +47,12 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_SplPriorityQueue___debugInfo, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() +#define arginfo_class_SplPriorityQueue___serialize arginfo_class_SplPriorityQueue___debugInfo + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_SplPriorityQueue___unserialize, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, data, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + #define arginfo_class_SplHeap_extract arginfo_class_SplPriorityQueue_top ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_SplHeap_insert, 0, 1, IS_TRUE, 0) @@ -80,6 +86,10 @@ ZEND_END_ARG_INFO() #define arginfo_class_SplHeap___debugInfo arginfo_class_SplPriorityQueue___debugInfo +#define arginfo_class_SplHeap___serialize arginfo_class_SplPriorityQueue___debugInfo + +#define arginfo_class_SplHeap___unserialize arginfo_class_SplPriorityQueue___unserialize + #define arginfo_class_SplMinHeap_compare arginfo_class_SplHeap_compare #define arginfo_class_SplMaxHeap_compare arginfo_class_SplHeap_compare @@ -100,11 +110,15 @@ ZEND_METHOD(SplHeap, recoverFromCorruption); ZEND_METHOD(SplHeap, isCorrupted); ZEND_METHOD(SplPriorityQueue, getExtractFlags); ZEND_METHOD(SplPriorityQueue, __debugInfo); +ZEND_METHOD(SplPriorityQueue, __serialize); +ZEND_METHOD(SplPriorityQueue, __unserialize); ZEND_METHOD(SplHeap, extract); ZEND_METHOD(SplHeap, insert); ZEND_METHOD(SplHeap, top); ZEND_METHOD(SplHeap, current); ZEND_METHOD(SplHeap, __debugInfo); +ZEND_METHOD(SplHeap, __serialize); +ZEND_METHOD(SplHeap, __unserialize); ZEND_METHOD(SplMinHeap, compare); ZEND_METHOD(SplMaxHeap, compare); @@ -125,6 +139,8 @@ static const zend_function_entry class_SplPriorityQueue_methods[] = { ZEND_RAW_FENTRY("isCorrupted", zim_SplHeap_isCorrupted, arginfo_class_SplPriorityQueue_isCorrupted, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_ME(SplPriorityQueue, getExtractFlags, arginfo_class_SplPriorityQueue_getExtractFlags, ZEND_ACC_PUBLIC) ZEND_ME(SplPriorityQueue, __debugInfo, arginfo_class_SplPriorityQueue___debugInfo, ZEND_ACC_PUBLIC) + ZEND_ME(SplPriorityQueue, __serialize, arginfo_class_SplPriorityQueue___serialize, ZEND_ACC_PUBLIC) + ZEND_ME(SplPriorityQueue, __unserialize, arginfo_class_SplPriorityQueue___unserialize, ZEND_ACC_PUBLIC) ZEND_FE_END }; @@ -143,6 +159,8 @@ static const zend_function_entry class_SplHeap_methods[] = { ZEND_RAW_FENTRY("compare", NULL, arginfo_class_SplHeap_compare, ZEND_ACC_PROTECTED|ZEND_ACC_ABSTRACT, NULL, NULL) ZEND_ME(SplHeap, isCorrupted, arginfo_class_SplHeap_isCorrupted, ZEND_ACC_PUBLIC) ZEND_ME(SplHeap, __debugInfo, arginfo_class_SplHeap___debugInfo, ZEND_ACC_PUBLIC) + ZEND_ME(SplHeap, __serialize, arginfo_class_SplHeap___serialize, ZEND_ACC_PUBLIC) + ZEND_ME(SplHeap, __unserialize, arginfo_class_SplHeap___unserialize, ZEND_ACC_PUBLIC) ZEND_FE_END }; diff --git a/ext/spl/tests/SplHeap_serialize_corrupted.phpt b/ext/spl/tests/SplHeap_serialize_corrupted.phpt new file mode 100644 index 0000000000000..8763a4c082937 --- /dev/null +++ b/ext/spl/tests/SplHeap_serialize_corrupted.phpt @@ -0,0 +1,67 @@ +--TEST-- +SplHeap and SplPriorityQueue serialization fails when corrupted +--FILE-- +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. diff --git a/ext/spl/tests/SplHeap_serialize_error_handling.phpt b/ext/spl/tests/SplHeap_serialize_error_handling.phpt new file mode 100644 index 0000000000000..458f27db5ce03 --- /dev/null +++ b/ext/spl/tests/SplHeap_serialize_error_handling.phpt @@ -0,0 +1,84 @@ +--TEST-- +SplHeap and SplPriorityQueue unserialization error handling +--FILE-- + 'not_int']], + + // Missing heap_elements + [[], ['flags' => 0]], + + // Invalid heap_elements type + [[], ['flags' => 0, 'heap_elements' => 'not_array']], + [[], ['flags' => 0, 'heap_elements' => 123]], +]; + +foreach ($invalid_cases as $i => $case) { + try { + $heap = new SplMaxHeap(); + $heap->__unserialize($case); + echo "Case $i: UNEXPECTED SUCCESS\n"; + } catch (Exception $e) { + echo "Case $i: " . $e->getMessage() . "\n"; + } +} + +$pq_invalid_cases = [ + // Invalid flags for PQ + [[], ['flags' => 0]], + + // Invalid element structure + [[], ['flags' => 1, 'heap_elements' => ['not_array']]], + + // Missing data/priority keys + [[], ['flags' => 1, 'heap_elements' => [['data' => 'test']]]], + [[], ['flags' => 1, 'heap_elements' => [['priority' => 1]]]], + [[], ['flags' => 1, 'heap_elements' => [[]]]], +]; + +foreach ($pq_invalid_cases as $i => $case) { + try { + $pq = new SplPriorityQueue(); + $pq->__unserialize($case); + echo "PQ Case $i: UNEXPECTED SUCCESS\n"; + } catch (Exception $e) { + echo "PQ Case $i: " . $e->getMessage() . "\n"; + } +} + +?> +--EXPECT-- +Case 0: Invalid serialization data for SplMaxHeap object +Case 1: Invalid serialization data for SplMaxHeap object +Case 2: Invalid serialization data for SplMaxHeap object +Case 3: Invalid serialization data for SplMaxHeap object +Case 4: Invalid serialization data for SplMaxHeap object +Case 5: Invalid serialization data for SplMaxHeap object +Case 6: Invalid serialization data for SplMaxHeap object +Case 7: Invalid serialization data for SplMaxHeap object +Case 8: Invalid serialization data for SplMaxHeap object +Case 9: Invalid serialization data for SplMaxHeap object +Case 10: Invalid serialization data for SplMaxHeap object +PQ Case 0: Invalid serialization data for SplPriorityQueue object +PQ Case 1: Invalid serialization data for SplPriorityQueue object +PQ Case 2: Invalid serialization data for SplPriorityQueue object +PQ Case 3: Invalid serialization data for SplPriorityQueue object +PQ Case 4: Invalid serialization data for SplPriorityQueue object diff --git a/ext/spl/tests/SplHeap_serialize_format.phpt b/ext/spl/tests/SplHeap_serialize_format.phpt new file mode 100644 index 0000000000000..aaa1b56e2695a --- /dev/null +++ b/ext/spl/tests/SplHeap_serialize_format.phpt @@ -0,0 +1,131 @@ +--TEST-- +SplHeap serialization binary format +--FILE-- +insert(100); +$heap->insert(50); +$heap->insert(200); +$s = serialize($heap); +echo $s . "\n"; +$unserialized = unserialize($s); +var_dump($unserialized); + +// Test SplMinHeap +$min = new SplMinHeap(); +$min->insert(30); +$min->insert(10); +$min->insert(20); +$s = serialize($min); +echo $s . "\n"; +var_dump(unserialize($s)); + +// Test SplPriorityQueue empty +$pq = new SplPriorityQueue(); +$s = serialize($pq); +echo $s . "\n"; +var_dump(unserialize($s)); + +// Test SplPriorityQueue with data +$pq = new SplPriorityQueue(); +$pq->insert('low', 1); +$pq->insert('high', 10); +$pq->insert('medium', 5); +$pq->setExtractFlags(SplPriorityQueue::EXTR_BOTH); +$s = serialize($pq); +echo $s . "\n"; +var_dump(unserialize($s)); + +?> +--EXPECT-- +O:10:"SplMaxHeap":2:{i:0;a:0:{}i:1;a:2:{s:5:"flags";i:0;s:13:"heap_elements";a:0:{}}} +object(SplMaxHeap)#2 (3) { + ["flags":"SplHeap":private]=> + int(0) + ["isCorrupted":"SplHeap":private]=> + bool(false) + ["heap":"SplHeap":private]=> + array(0) { + } +} +O:10:"SplMaxHeap":2:{i:0;a:0:{}i:1;a:2:{s:5:"flags";i:0;s:13:"heap_elements";a:3:{i:0;i:200;i:1;i:50;i:2;i:100;}}} +object(SplMaxHeap)#1 (3) { + ["flags":"SplHeap":private]=> + int(0) + ["isCorrupted":"SplHeap":private]=> + bool(false) + ["heap":"SplHeap":private]=> + array(3) { + [0]=> + int(200) + [1]=> + int(50) + [2]=> + int(100) + } +} +O:10:"SplMinHeap":2:{i:0;a:0:{}i:1;a:2:{s:5:"flags";i:0;s:13:"heap_elements";a:3:{i:0;i:10;i:1;i:30;i:2;i:20;}}} +object(SplMinHeap)#4 (3) { + ["flags":"SplHeap":private]=> + int(0) + ["isCorrupted":"SplHeap":private]=> + bool(false) + ["heap":"SplHeap":private]=> + array(3) { + [0]=> + int(10) + [1]=> + int(30) + [2]=> + int(20) + } +} +O:16:"SplPriorityQueue":2:{i:0;a:0:{}i:1;a:2:{s:5:"flags";i:1;s:13:"heap_elements";a:0:{}}} +object(SplPriorityQueue)#5 (3) { + ["flags":"SplPriorityQueue":private]=> + int(1) + ["isCorrupted":"SplPriorityQueue":private]=> + bool(false) + ["heap":"SplPriorityQueue":private]=> + array(0) { + } +} +O:16:"SplPriorityQueue":2:{i:0;a:0:{}i:1;a:2:{s:5:"flags";i:3;s:13:"heap_elements";a:3:{i:0;a:2:{s:4:"data";s:4:"high";s:8:"priority";i:10;}i:1;a:2:{s:4:"data";s:3:"low";s:8:"priority";i:1;}i:2;a:2:{s:4:"data";s:6:"medium";s:8:"priority";i:5;}}}} +object(SplPriorityQueue)#4 (3) { + ["flags":"SplPriorityQueue":private]=> + int(3) + ["isCorrupted":"SplPriorityQueue":private]=> + bool(false) + ["heap":"SplPriorityQueue":private]=> + array(3) { + [0]=> + array(2) { + ["data"]=> + string(4) "high" + ["priority"]=> + int(10) + } + [1]=> + array(2) { + ["data"]=> + string(3) "low" + ["priority"]=> + int(1) + } + [2]=> + array(2) { + ["data"]=> + string(6) "medium" + ["priority"]=> + int(5) + } + } +} diff --git a/ext/spl/tests/SplHeap_serialize_indexed_format.phpt b/ext/spl/tests/SplHeap_serialize_indexed_format.phpt new file mode 100644 index 0000000000000..c201feb5716f6 --- /dev/null +++ b/ext/spl/tests/SplHeap_serialize_indexed_format.phpt @@ -0,0 +1,91 @@ +--TEST-- +SplHeap serialization format - indexed array format prevents conflicts +--FILE-- +insert(100); +$heap->insert(50); +$heap->insert(200); + +$serialized_data = $heap->__serialize(); +var_dump($serialized_data); + +$pq = new SplPriorityQueue(); +$pq->insert('data', 10); +$pq->setExtractFlags(SplPriorityQueue::EXTR_DATA); + +$pq_data = $pq->__serialize(); +var_dump($pq_data); + +class CustomHeap extends SplMaxHeap { + public $flags = 'user_property'; + public $heap_elements = 'user_property'; +} + +$custom = new CustomHeap(); +$custom->insert(42); + +$custom_data = $custom->__serialize(); +var_dump($custom_data); + +?> +--EXPECT-- +array(2) { + [0]=> + array(0) { + } + [1]=> + array(2) { + ["flags"]=> + int(0) + ["heap_elements"]=> + array(3) { + [0]=> + int(200) + [1]=> + int(50) + [2]=> + int(100) + } + } +} +array(2) { + [0]=> + array(0) { + } + [1]=> + array(2) { + ["flags"]=> + int(1) + ["heap_elements"]=> + array(1) { + [0]=> + array(2) { + ["data"]=> + string(4) "data" + ["priority"]=> + int(10) + } + } + } +} +array(2) { + [0]=> + array(2) { + ["flags"]=> + UNKNOWN:0 + ["heap_elements"]=> + UNKNOWN:0 + } + [1]=> + array(2) { + ["flags"]=> + int(0) + ["heap_elements"]=> + array(1) { + [0]=> + int(42) + } + } +} diff --git a/ext/spl/tests/SplHeap_serialize_inheritance_safety.phpt b/ext/spl/tests/SplHeap_serialize_inheritance_safety.phpt new file mode 100644 index 0000000000000..c90b278e45b62 --- /dev/null +++ b/ext/spl/tests/SplHeap_serialize_inheritance_safety.phpt @@ -0,0 +1,117 @@ +--TEST-- +SplHeap and SplPriorityQueue inheritance safety with conflicting property names +--FILE-- +flags = 'modified_user_flags'; +$heap->heap_elements = 'modified_user_elements'; +$heap->custom_prop = 'modified_custom'; + +$heap->insert(100); +$heap->insert(50); +$heap->insert(200); + +$serialized = serialize($heap); +echo $serialized . "\n"; +$unserialized = unserialize($serialized); +var_dump($unserialized); + +class CustomPriorityQueue extends SplPriorityQueue { + public $flags = 'user_flags_property'; + public $heap_elements = 'user_heap_elements_property'; + public $custom_data = 'custom_data_value'; + protected $protected_priority = 'protected_priority_value'; +} + +$pq = new CustomPriorityQueue(); +$pq->flags = 'modified_user_flags'; +$pq->heap_elements = 'modified_user_elements'; +$pq->custom_data = 'modified_custom_data'; + +$pq->insert('low_priority', 1); +$pq->insert('high_priority', 10); +$pq->insert('medium_priority', 5); + +$pq->setExtractFlags(SplPriorityQueue::EXTR_BOTH); + +$serialized_pq = serialize($pq); +echo $serialized_pq . "\n"; +$unserialized_pq = unserialize($serialized_pq); +var_dump($unserialized_pq); + +?> +--EXPECTF-- +O:10:"CustomHeap":2:{i:0;a:5:{s:5:"flags";s:19:"modified_user_flags";s:13:"heap_elements";s:22:"modified_user_elements";s:11:"custom_prop";s:15:"modified_custom";s:17:"%0*%0protected_prop";s:15:"protected_value";s:24:"%0CustomHeap%0private_prop";s:13:"private_value";}i:1;a:2:{s:5:"flags";i:0;s:13:"heap_elements";a:3:{i:0;i:200;i:1;i:50;i:2;i:100;}}} +object(CustomHeap)#2 (8) { + ["flags"]=> + string(19) "modified_user_flags" + ["heap_elements"]=> + string(22) "modified_user_elements" + ["custom_prop"]=> + string(15) "modified_custom" + ["protected_prop":protected]=> + string(15) "protected_value" + ["private_prop":"CustomHeap":private]=> + string(13) "private_value" + ["flags":"SplHeap":private]=> + int(0) + ["isCorrupted":"SplHeap":private]=> + bool(false) + ["heap":"SplHeap":private]=> + array(3) { + [0]=> + int(200) + [1]=> + int(50) + [2]=> + int(100) + } +} +O:19:"CustomPriorityQueue":2:{i:0;a:4:{s:5:"flags";s:19:"modified_user_flags";s:13:"heap_elements";s:22:"modified_user_elements";s:11:"custom_data";s:20:"modified_custom_data";s:21:"%0*%0protected_priority";s:24:"protected_priority_value";}i:1;a:2:{s:5:"flags";i:3;s:13:"heap_elements";a:3:{i:0;a:2:{s:4:"data";s:13:"high_priority";s:8:"priority";i:10;}i:1;a:2:{s:4:"data";s:12:"low_priority";s:8:"priority";i:1;}i:2;a:2:{s:4:"data";s:15:"medium_priority";s:8:"priority";i:5;}}}} +object(CustomPriorityQueue)#4 (7) { + ["flags"]=> + string(19) "modified_user_flags" + ["heap_elements"]=> + string(22) "modified_user_elements" + ["custom_data"]=> + string(20) "modified_custom_data" + ["protected_priority":protected]=> + string(24) "protected_priority_value" + ["flags":"SplPriorityQueue":private]=> + int(3) + ["isCorrupted":"SplPriorityQueue":private]=> + bool(false) + ["heap":"SplPriorityQueue":private]=> + array(3) { + [0]=> + array(2) { + ["data"]=> + string(13) "high_priority" + ["priority"]=> + int(10) + } + [1]=> + array(2) { + ["data"]=> + string(12) "low_priority" + ["priority"]=> + int(1) + } + [2]=> + array(2) { + ["data"]=> + string(15) "medium_priority" + ["priority"]=> + int(5) + } + } +} diff --git a/ext/spl/tests/SplMaxHeap_serialize_complex.phpt b/ext/spl/tests/SplMaxHeap_serialize_complex.phpt new file mode 100644 index 0000000000000..9e83d779c416a --- /dev/null +++ b/ext/spl/tests/SplMaxHeap_serialize_complex.phpt @@ -0,0 +1,89 @@ +--TEST-- +SplMaxHeap serialization with complex data types +--FILE-- +insert(['type' => 'array1', 'value' => 10]); +$heap->insert(['type' => 'array2', 'value' => 20]); +$heap->insert(['type' => 'array3', 'value' => 5]); + +$serialized = serialize($heap); +echo $serialized . "\n"; + +$unserialized = unserialize($serialized); +var_dump($unserialized); + +class TestObj { + public $val; + public function __construct($v) { $this->val = $v; } +} + +$heap2 = new SplMaxHeap(); +$heap2->insert(new TestObj(30)); +$heap2->insert(new TestObj(10)); +$heap2->insert(new TestObj(20)); + +$serialized2 = serialize($heap2); +echo $serialized2 . "\n"; + +$unserialized2 = unserialize($serialized2); +var_dump($unserialized2); + +?> +--EXPECT-- +O:10:"SplMaxHeap":2:{i:0;a:0:{}i:1;a:2:{s:5:"flags";i:0;s:13:"heap_elements";a:3:{i:0;a:2:{s:4:"type";s:6:"array3";s:5:"value";i:5;}i:1;a:2:{s:4:"type";s:6:"array1";s:5:"value";i:10;}i:2;a:2:{s:4:"type";s:6:"array2";s:5:"value";i:20;}}}} +object(SplMaxHeap)#2 (3) { + ["flags":"SplHeap":private]=> + int(0) + ["isCorrupted":"SplHeap":private]=> + bool(false) + ["heap":"SplHeap":private]=> + array(3) { + [0]=> + array(2) { + ["type"]=> + string(6) "array3" + ["value"]=> + int(5) + } + [1]=> + array(2) { + ["type"]=> + string(6) "array1" + ["value"]=> + int(10) + } + [2]=> + array(2) { + ["type"]=> + string(6) "array2" + ["value"]=> + int(20) + } + } +} +O:10:"SplMaxHeap":2:{i:0;a:0:{}i:1;a:2:{s:5:"flags";i:0;s:13:"heap_elements";a:3:{i:0;O:7:"TestObj":1:{s:3:"val";i:30;}i:1;O:7:"TestObj":1:{s:3:"val";i:10;}i:2;O:7:"TestObj":1:{s:3:"val";i:20;}}}} +object(SplMaxHeap)#7 (3) { + ["flags":"SplHeap":private]=> + int(0) + ["isCorrupted":"SplHeap":private]=> + bool(false) + ["heap":"SplHeap":private]=> + array(3) { + [0]=> + object(TestObj)#8 (1) { + ["val"]=> + int(30) + } + [1]=> + object(TestObj)#9 (1) { + ["val"]=> + int(10) + } + [2]=> + object(TestObj)#10 (1) { + ["val"]=> + int(20) + } + } +} diff --git a/ext/spl/tests/SplMinHeap_serialize_complex.phpt b/ext/spl/tests/SplMinHeap_serialize_complex.phpt new file mode 100644 index 0000000000000..0f33fa3ae700b --- /dev/null +++ b/ext/spl/tests/SplMinHeap_serialize_complex.phpt @@ -0,0 +1,63 @@ +--TEST-- +SplMinHeap serialization with nested arrays +--FILE-- +insert(['name' => 'Alice', 'nested' => ['age' => 25, 'city' => 'NYC']]); +$heap->insert(['name' => 'Bob', 'nested' => ['age' => 30, 'city' => 'LA']]); +$heap->insert(['name' => 'Charlie', 'nested' => ['age' => 35, 'city' => 'SF']]); + +$serialized = serialize($heap); +echo $serialized . "\n"; + +$unserialized = unserialize($serialized); +var_dump($unserialized); + +?> +--EXPECT-- +O:10:"SplMinHeap":2:{i:0;a:0:{}i:1;a:2:{s:5:"flags";i:0;s:13:"heap_elements";a:3:{i:0;a:2:{s:4:"name";s:5:"Alice";s:6:"nested";a:2:{s:3:"age";i:25;s:4:"city";s:3:"NYC";}}i:1;a:2:{s:4:"name";s:3:"Bob";s:6:"nested";a:2:{s:3:"age";i:30;s:4:"city";s:2:"LA";}}i:2;a:2:{s:4:"name";s:7:"Charlie";s:6:"nested";a:2:{s:3:"age";i:35;s:4:"city";s:2:"SF";}}}}} +object(SplMinHeap)#2 (3) { + ["flags":"SplHeap":private]=> + int(0) + ["isCorrupted":"SplHeap":private]=> + bool(false) + ["heap":"SplHeap":private]=> + array(3) { + [0]=> + array(2) { + ["name"]=> + string(5) "Alice" + ["nested"]=> + array(2) { + ["age"]=> + int(25) + ["city"]=> + string(3) "NYC" + } + } + [1]=> + array(2) { + ["name"]=> + string(3) "Bob" + ["nested"]=> + array(2) { + ["age"]=> + int(30) + ["city"]=> + string(2) "LA" + } + } + [2]=> + array(2) { + ["name"]=> + string(7) "Charlie" + ["nested"]=> + array(2) { + ["age"]=> + int(35) + ["city"]=> + string(2) "SF" + } + } + } +} diff --git a/ext/spl/tests/SplPriorityQueue_serialize_complex.phpt b/ext/spl/tests/SplPriorityQueue_serialize_complex.phpt new file mode 100644 index 0000000000000..61a3cb1aa8699 --- /dev/null +++ b/ext/spl/tests/SplPriorityQueue_serialize_complex.phpt @@ -0,0 +1,113 @@ +--TEST-- +SplPriorityQueue serialization with mixed data types and extract flags +--FILE-- +setExtractFlags(SplPriorityQueue::EXTR_BOTH); + +$array1 = ['name' => 'John', 'hobbies' => ['reading', 'gaming']]; +$queue->insert($array1, 10); + +class TestClass { + public $prop = 'test'; +} +$obj1 = new TestClass(); +$queue->insert($obj1, 15); + +$queue->insert(3.14159, 12); +$queue->insert(true, 20); +$queue->insert(null, 3); + +$serialized = serialize($queue); +echo $serialized . "\n"; + +$unserialized = unserialize($serialized); +var_dump($unserialized); + +$queue2 = new SplPriorityQueue(); +$queue2->setExtractFlags(SplPriorityQueue::EXTR_PRIORITY); +$queue2->insert("data", 42); + +$serialized2 = serialize($queue2); +echo $serialized2 . "\n"; + +$unserialized2 = unserialize($serialized2); +var_dump($unserialized2); + +?> +--EXPECT-- +O:16:"SplPriorityQueue":2:{i:0;a:0:{}i:1;a:2:{s:5:"flags";i:3;s:13:"heap_elements";a:5:{i:0;a:2:{s:4:"data";b:1;s:8:"priority";i:20;}i:1;a:2:{s:4:"data";O:9:"TestClass":1:{s:4:"prop";s:4:"test";}s:8:"priority";i:15;}i:2;a:2:{s:4:"data";d:3.14159;s:8:"priority";i:12;}i:3;a:2:{s:4:"data";a:2:{s:4:"name";s:4:"John";s:7:"hobbies";a:2:{i:0;s:7:"reading";i:1;s:6:"gaming";}}s:8:"priority";i:10;}i:4;a:2:{s:4:"data";N;s:8:"priority";i:3;}}}} +object(SplPriorityQueue)#3 (3) { + ["flags":"SplPriorityQueue":private]=> + int(3) + ["isCorrupted":"SplPriorityQueue":private]=> + bool(false) + ["heap":"SplPriorityQueue":private]=> + array(5) { + [0]=> + array(2) { + ["data"]=> + bool(true) + ["priority"]=> + int(20) + } + [1]=> + array(2) { + ["data"]=> + object(TestClass)#4 (1) { + ["prop"]=> + string(4) "test" + } + ["priority"]=> + int(15) + } + [2]=> + array(2) { + ["data"]=> + float(3.14159) + ["priority"]=> + int(12) + } + [3]=> + array(2) { + ["data"]=> + array(2) { + ["name"]=> + string(4) "John" + ["hobbies"]=> + array(2) { + [0]=> + string(7) "reading" + [1]=> + string(6) "gaming" + } + } + ["priority"]=> + int(10) + } + [4]=> + array(2) { + ["data"]=> + NULL + ["priority"]=> + int(3) + } + } +} +O:16:"SplPriorityQueue":2:{i:0;a:0:{}i:1;a:2:{s:5:"flags";i:2;s:13:"heap_elements";a:1:{i:0;a:2:{s:4:"data";s:4:"data";s:8:"priority";i:42;}}}} +object(SplPriorityQueue)#6 (3) { + ["flags":"SplPriorityQueue":private]=> + int(2) + ["isCorrupted":"SplPriorityQueue":private]=> + bool(false) + ["heap":"SplPriorityQueue":private]=> + array(1) { + [0]=> + array(2) { + ["data"]=> + string(4) "data" + ["priority"]=> + int(42) + } + } +} diff --git a/ext/spl/tests/SplPriorityQueue_unserialize_invalid_flags.phpt b/ext/spl/tests/SplPriorityQueue_unserialize_invalid_flags.phpt new file mode 100644 index 0000000000000..8c785606c7fe7 --- /dev/null +++ b/ext/spl/tests/SplPriorityQueue_unserialize_invalid_flags.phpt @@ -0,0 +1,80 @@ +--TEST-- +SplPriorityQueue unserialization with invalid flags should throw exception +--FILE-- + 4, // invalid flag value (4 & 3 = 0) + 'heap_elements' => [] + ] + ]; + + $queue = new SplPriorityQueue(); + $queue->__unserialize($data); + echo "Should have thrown exception for invalid flags\n"; +} catch (Exception $e) { + echo "Exception thrown for invalid flags: " . $e->getMessage() . "\n"; +} + +try { + $data = [ + [], + [ + 'flags' => 0, + 'heap_elements' => [] + ] + ]; + + $queue = new SplPriorityQueue(); + $queue->__unserialize($data); + echo "Should have thrown exception for zero flags\n"; +} catch (Exception $e) { + echo "Exception thrown for zero flags: " . $e->getMessage() . "\n"; +} + +try { + $data = [ + [], + [ + 'flags' => SplPriorityQueue::EXTR_DATA, + 'heap_elements' => [] + ] + ]; + + $queue = new SplPriorityQueue(); + $queue->__unserialize($data); + echo "Valid flags accepted\n"; +} catch (Exception $e) { + echo "Valid flags rejected: " . $e->getMessage() . "\n"; +} + +try { + $data = [ + [], + [ + 'flags' => 999, // extra bits that should be masked to 3 (EXTR_BOTH) + 'heap_elements' => [] + ] + ]; + + $queue = new SplPriorityQueue(); + $queue->__unserialize($data); + + if ($queue->getExtractFlags() === SplPriorityQueue::EXTR_BOTH) { + echo "Flags properly masked\n"; + } else { + echo "Flags not properly masked, got: " . $queue->getExtractFlags() . "\n"; + } +} catch (Exception $e) { + echo "Flags with extra bits should be masked: " . $e->getMessage() . "\n"; +} + +?> +--EXPECT-- +Exception thrown for invalid flags: Invalid serialization data for SplPriorityQueue object +Exception thrown for zero flags: Invalid serialization data for SplPriorityQueue object +Valid flags accepted +Flags properly masked