Skip to content

Commit cc9fb7b

Browse files
Fix GH-14402: Add support for serialization in SplPriorityQueue, SplMinHeap and SplMaxHeap
1 parent 4bc060c commit cc9fb7b

12 files changed

+827
-0
lines changed

ext/spl/spl_heap.c

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,6 +1070,206 @@ PHP_METHOD(SplPriorityQueue, __debugInfo)
10701070
RETURN_ARR(spl_heap_object_get_debug_info(spl_ce_SplPriorityQueue, Z_OBJ_P(ZEND_THIS)));
10711071
} /* }}} */
10721072

1073+
static void spl_heap_serialize_properties(zval *return_value, spl_heap_object *intern)
1074+
{
1075+
HashTable *props = zend_std_get_properties(&intern->std);
1076+
1077+
ZVAL_ARR(return_value, props ? zend_array_dup(props) : zend_new_array(0));
1078+
}
1079+
1080+
static void spl_heap_serialize_internal_state(zval *return_value, spl_heap_object *intern, bool is_pqueue)
1081+
{
1082+
zval heap_elements;
1083+
int heap_count = intern->heap->count;
1084+
1085+
array_init(return_value);
1086+
add_assoc_long(return_value, "flags", intern->flags);
1087+
1088+
if (heap_count == 0) {
1089+
return;
1090+
}
1091+
1092+
array_init_size(&heap_elements, heap_count);
1093+
1094+
for (int heap_idx = 0; heap_idx < heap_count; ++heap_idx) {
1095+
if (is_pqueue) {
1096+
spl_pqueue_elem *elem = spl_heap_elem(intern->heap, heap_idx);
1097+
zval entry;
1098+
array_init(&entry);
1099+
add_assoc_zval_ex(&entry, "data", sizeof("data") - 1, &elem->data);
1100+
Z_TRY_ADDREF(elem->data);
1101+
add_assoc_zval_ex(&entry, "priority", sizeof("priority") - 1, &elem->priority);
1102+
Z_TRY_ADDREF(elem->priority);
1103+
zend_hash_next_index_insert(Z_ARRVAL(heap_elements), &entry);
1104+
} else {
1105+
zval *elem = spl_heap_elem(intern->heap, heap_idx);
1106+
zend_hash_next_index_insert(Z_ARRVAL(heap_elements), elem);
1107+
Z_TRY_ADDREF_P(elem);
1108+
}
1109+
}
1110+
1111+
add_assoc_zval(return_value, "heap_elements", &heap_elements);
1112+
}
1113+
1114+
static void spl_heap_unserialize_properties(HashTable *props_ht, spl_heap_object *intern)
1115+
{
1116+
object_properties_load(&intern->std, props_ht);
1117+
if (!EG(exception)) {
1118+
return;
1119+
}
1120+
1121+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
1122+
}
1123+
1124+
static void spl_heap_unserialize_internal_state(HashTable *state_ht, spl_heap_object *intern, zval *this_ptr, bool is_pqueue)
1125+
{
1126+
zval *flags_val = zend_hash_str_find(state_ht, "flags", sizeof("flags") - 1);
1127+
if (!flags_val || Z_TYPE_P(flags_val) != IS_LONG) {
1128+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object: missing or invalid flags", ZSTR_VAL(intern->std.ce->name));
1129+
return;
1130+
}
1131+
1132+
intern->flags = (int) Z_LVAL_P(flags_val);
1133+
1134+
zval *heap_elements = zend_hash_str_find(state_ht, "heap_elements", sizeof("heap_elements") - 1);
1135+
if (!heap_elements) {
1136+
return;
1137+
}
1138+
1139+
if (Z_TYPE_P(heap_elements) != IS_ARRAY) {
1140+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object: heap_elements must be an array", ZSTR_VAL(intern->std.ce->name));
1141+
return;
1142+
}
1143+
1144+
zval *val;
1145+
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(heap_elements), val) {
1146+
if (is_pqueue) {
1147+
if (Z_TYPE_P(val) != IS_ARRAY) {
1148+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object: priority queue elements must be arrays", ZSTR_VAL(intern->std.ce->name));
1149+
return;
1150+
}
1151+
1152+
zval *data_val = zend_hash_str_find(Z_ARRVAL_P(val), "data", sizeof("data") - 1);
1153+
zval *priority_val = zend_hash_str_find(Z_ARRVAL_P(val), "priority", sizeof("priority") - 1);
1154+
1155+
if (!data_val || !priority_val) {
1156+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object: priority queue elements must have data and priority", ZSTR_VAL(intern->std.ce->name));
1157+
return;
1158+
}
1159+
1160+
spl_pqueue_elem elem;
1161+
ZVAL_COPY(&elem.data, data_val);
1162+
ZVAL_COPY(&elem.priority, priority_val);
1163+
spl_ptr_heap_insert(intern->heap, &elem, this_ptr);
1164+
} else {
1165+
Z_TRY_ADDREF_P(val);
1166+
spl_ptr_heap_insert(intern->heap, val, this_ptr);
1167+
}
1168+
} ZEND_HASH_FOREACH_END();
1169+
}
1170+
1171+
PHP_METHOD(SplPriorityQueue, __serialize)
1172+
{
1173+
spl_heap_object *intern = Z_SPLHEAP_P(ZEND_THIS);
1174+
zval props, state;
1175+
1176+
ZEND_PARSE_PARAMETERS_NONE();
1177+
1178+
array_init(return_value);
1179+
1180+
spl_heap_serialize_properties(&props, intern);
1181+
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &props);
1182+
1183+
spl_heap_serialize_internal_state(&state, intern, true);
1184+
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &state);
1185+
}
1186+
1187+
PHP_METHOD(SplPriorityQueue, __unserialize)
1188+
{
1189+
HashTable *data;
1190+
spl_heap_object *intern = Z_SPLHEAP_P(ZEND_THIS);
1191+
zval *props, *state;
1192+
1193+
ZEND_PARSE_PARAMETERS_START(1, 1)
1194+
Z_PARAM_ARRAY_HT(data)
1195+
ZEND_PARSE_PARAMETERS_END();
1196+
1197+
if (zend_hash_num_elements(data) != 2) {
1198+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
1199+
RETURN_THROWS();
1200+
}
1201+
1202+
props = zend_hash_index_find(data, 0);
1203+
if (!props || Z_TYPE_P(props) != IS_ARRAY) {
1204+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
1205+
RETURN_THROWS();
1206+
}
1207+
1208+
spl_heap_unserialize_properties(Z_ARRVAL_P(props), intern);
1209+
if (EG(exception)) {
1210+
RETURN_THROWS();
1211+
}
1212+
1213+
state = zend_hash_index_find(data, 1);
1214+
if (!state || Z_TYPE_P(state) != IS_ARRAY) {
1215+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
1216+
RETURN_THROWS();
1217+
}
1218+
1219+
spl_heap_unserialize_internal_state(Z_ARRVAL_P(state), intern, ZEND_THIS, true);
1220+
}
1221+
1222+
PHP_METHOD(SplHeap, __serialize)
1223+
{
1224+
spl_heap_object *intern = Z_SPLHEAP_P(ZEND_THIS);
1225+
zval props, state;
1226+
1227+
ZEND_PARSE_PARAMETERS_NONE();
1228+
1229+
array_init(return_value);
1230+
1231+
spl_heap_serialize_properties(&props, intern);
1232+
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &props);
1233+
1234+
spl_heap_serialize_internal_state(&state, intern, false);
1235+
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &state);
1236+
}
1237+
1238+
PHP_METHOD(SplHeap, __unserialize)
1239+
{
1240+
HashTable *data;
1241+
spl_heap_object *intern = Z_SPLHEAP_P(ZEND_THIS);
1242+
zval *props, *state;
1243+
1244+
ZEND_PARSE_PARAMETERS_START(1, 1)
1245+
Z_PARAM_ARRAY_HT(data)
1246+
ZEND_PARSE_PARAMETERS_END();
1247+
1248+
if (zend_hash_num_elements(data) != 2) {
1249+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
1250+
RETURN_THROWS();
1251+
}
1252+
1253+
props = zend_hash_index_find(data, 0);
1254+
if (!props || Z_TYPE_P(props) != IS_ARRAY) {
1255+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
1256+
RETURN_THROWS();
1257+
}
1258+
1259+
spl_heap_unserialize_properties(Z_ARRVAL_P(props), intern);
1260+
if (EG(exception)) {
1261+
RETURN_THROWS();
1262+
}
1263+
1264+
state = zend_hash_index_find(data, 1);
1265+
if (!state || Z_TYPE_P(state) != IS_ARRAY) {
1266+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
1267+
RETURN_THROWS();
1268+
}
1269+
1270+
spl_heap_unserialize_internal_state(Z_ARRVAL_P(state), intern, ZEND_THIS, false);
1271+
}
1272+
10731273
/* iterator handler table */
10741274
static const zend_object_iterator_funcs spl_heap_it_funcs = {
10751275
spl_heap_it_dtor,

ext/spl/spl_heap.stub.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ public function getExtractFlags(): int {}
8282

8383
/** @tentative-return-type */
8484
public function __debugInfo(): array {}
85+
86+
/** @tentative-return-type */
87+
public function __serialize(): array {}
88+
89+
/** @tentative-return-type */
90+
public function __unserialize(array $data): void {}
8591
}
8692

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

128134
/** @tentative-return-type */
129135
public function __debugInfo(): array {}
136+
137+
/** @tentative-return-type */
138+
public function __serialize(): array {}
139+
140+
/** @tentative-return-type */
141+
public function __unserialize(array $data): void {}
130142
}
131143

132144
class SplMinHeap extends SplHeap
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
--TEST--
2+
SplHeap serialization format - indexed array format prevents conflicts
3+
--FILE--
4+
<?php
5+
6+
$heap = new SplMaxHeap();
7+
$heap->insert(100);
8+
$heap->insert(50);
9+
$heap->insert(200);
10+
11+
$serialized_data = $heap->__serialize();
12+
13+
echo "Serialization data structure:\n";
14+
echo "Is indexed array: " . (isset($serialized_data[0], $serialized_data[1]) && count($serialized_data) === 2 ? 'YES' : 'NO') . "\n";
15+
echo "Index 0 (properties) is array: " . (is_array($serialized_data[0]) ? 'YES' : 'NO') . "\n";
16+
echo "Index 1 (internal state) is array: " . (is_array($serialized_data[1]) ? 'YES' : 'NO') . "\n";
17+
18+
$internal_state = $serialized_data[1];
19+
echo "Internal state contains 'flags': " . (array_key_exists('flags', $internal_state) ? 'YES' : 'NO') . "\n";
20+
echo "Internal state contains 'heap_elements': " . (array_key_exists('heap_elements', $internal_state) ? 'YES' : 'NO') . "\n";
21+
echo "Flags value is numeric: " . (is_numeric($internal_state['flags']) ? 'YES' : 'NO') . "\n";
22+
echo "Heap elements is array: " . (is_array($internal_state['heap_elements']) ? 'YES' : 'NO') . "\n";
23+
echo "Heap elements count: " . count($internal_state['heap_elements']) . "\n";
24+
25+
$min_heap = new SplMinHeap();
26+
$min_heap->insert(100);
27+
$min_heap->insert(50);
28+
29+
$min_data = $min_heap->__serialize();
30+
31+
$pq = new SplPriorityQueue();
32+
$pq->insert('data', 10);
33+
$pq->setExtractFlags(SplPriorityQueue::EXTR_DATA);
34+
35+
$pq_data = $pq->__serialize();
36+
37+
$pq_internal = $pq_data[1];
38+
if (isset($pq_internal['heap_elements']) && is_array($pq_internal['heap_elements'])) {
39+
$first_element = $pq_internal['heap_elements'][0];
40+
echo "PQ elements have 'data' key: " . (array_key_exists('data', $first_element) ? 'YES' : 'NO') . "\n";
41+
echo "PQ elements have 'priority' key: " . (array_key_exists('priority', $first_element) ? 'YES' : 'NO') . "\n";
42+
}
43+
44+
?>
45+
--EXPECT--
46+
Serialization data structure:
47+
Is indexed array: YES
48+
Index 0 (properties) is array: YES
49+
Index 1 (internal state) is array: YES
50+
Internal state contains 'flags': YES
51+
Internal state contains 'heap_elements': YES
52+
Flags value is numeric: YES
53+
Heap elements is array: YES
54+
Heap elements count: 3
55+
PQ elements have 'data' key: YES
56+
PQ elements have 'priority' key: YES
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
--TEST--
2+
SplHeap and SplPriorityQueue serialization error handling
3+
--FILE--
4+
<?php
5+
6+
try {
7+
$heap = new SplMaxHeap();
8+
$heap->__unserialize([]);
9+
echo "FAIL: Should have thrown exception\n";
10+
} catch (Exception $e) {
11+
echo "Wrong element count - " . $e->getMessage() . "\n";
12+
}
13+
14+
try {
15+
$heap = new SplMinHeap();
16+
$heap->__unserialize(['not_array', []]);
17+
echo "FAIL: Should have thrown exception\n";
18+
} catch (Exception $e) {
19+
echo "Invalid properties type - " . $e->getMessage() . "\n";
20+
}
21+
22+
try {
23+
$heap = new SplMaxHeap();
24+
$heap->__unserialize([[], 'not_array']);
25+
echo "FAIL: Should have thrown exception\n";
26+
} catch (Exception $e) {
27+
echo "Invalid state type - " . $e->getMessage() . "\n";
28+
}
29+
30+
try {
31+
$heap = new SplMaxHeap();
32+
$heap->__unserialize([[], []]);
33+
echo "FAIL: Should have thrown exception\n";
34+
} catch (Exception $e) {
35+
echo "Missing flags - " . $e->getMessage() . "\n";
36+
}
37+
38+
try {
39+
$heap = new SplMaxHeap();
40+
$heap->__unserialize([[], ['flags' => 0, 'heap_elements' => 'not_array']]);
41+
echo "FAIL: Should have thrown exception\n";
42+
} catch (Exception $e) {
43+
echo "Invalid heap elements - " . $e->getMessage() . "\n";
44+
}
45+
46+
try {
47+
$pq = new SplPriorityQueue();
48+
$pq->__unserialize([[], [], []]);
49+
echo "FAIL: Should have thrown exception\n";
50+
} catch (Exception $e) {
51+
echo "PQ wrong element count - " . $e->getMessage() . "\n";
52+
}
53+
54+
try {
55+
$pq = new SplPriorityQueue();
56+
$pq->__unserialize([123, []]);
57+
echo "FAIL: Should have thrown exception\n";
58+
} catch (Exception $e) {
59+
echo "PQ invalid properties - " . $e->getMessage() . "\n";
60+
}
61+
62+
try {
63+
$pq = new SplPriorityQueue();
64+
$pq->__unserialize([[], []]);
65+
echo "FAIL: Should have thrown exception\n";
66+
} catch (Exception $e) {
67+
echo "PQ missing flags - " . $e->getMessage() . "\n";
68+
}
69+
70+
try {
71+
$pq = new SplPriorityQueue();
72+
$pq->__unserialize([[], ['flags' => 0, 'heap_elements' => ['not_array']]]);
73+
echo "FAIL: Should have thrown exception\n";
74+
} catch (Exception $e) {
75+
echo "PQ invalid element structure - " . $e->getMessage() . "\n";
76+
}
77+
78+
try {
79+
$pq = new SplPriorityQueue();
80+
$pq->__unserialize([[], ['flags' => 0, 'heap_elements' => [['data' => 'test']]]]);
81+
echo "FAIL: Should have thrown exception\n";
82+
} catch (Exception $e) {
83+
echo "PQ missing priority - " . $e->getMessage() . "\n";
84+
}
85+
86+
try {
87+
$pq = new SplPriorityQueue();
88+
$pq->__unserialize([[], ['flags' => 0, 'heap_elements' => [['priority' => 1]]]]);
89+
echo "FAIL: Should have thrown exception\n";
90+
} catch (Exception $e) {
91+
echo "PQ missing data - " . $e->getMessage() . "\n";
92+
}
93+
94+
?>
95+
--EXPECT--
96+
Wrong element count - Invalid serialization data for SplMaxHeap object
97+
Invalid properties type - Invalid serialization data for SplMinHeap object
98+
Invalid state type - Invalid serialization data for SplMaxHeap object
99+
Missing flags - Invalid serialization data for SplMaxHeap object: missing or invalid flags
100+
Invalid heap elements - Invalid serialization data for SplMaxHeap object: heap_elements must be an array
101+
PQ wrong element count - Invalid serialization data for SplPriorityQueue object
102+
PQ invalid properties - Invalid serialization data for SplPriorityQueue object
103+
PQ missing flags - Invalid serialization data for SplPriorityQueue object: missing or invalid flags
104+
PQ invalid element structure - Invalid serialization data for SplPriorityQueue object: priority queue elements must be arrays
105+
PQ missing priority - Invalid serialization data for SplPriorityQueue object: priority queue elements must have data and priority
106+
PQ missing data - Invalid serialization data for SplPriorityQueue object: priority queue elements must have data and priority

0 commit comments

Comments
 (0)