libxml_rs/xml/list/mod.rs
1//! Linked list — public xmlList API (§85 Phase 1).
2//!
3//! Implements the libxml2 linked list.
4//!
5//! # UPSTREAM-PARITY
6//!
7//! The linked list supports:
8//! - Create/delete with custom deallocator
9//! - Push front/back, pop front/back
10//! - Insert/append at arbitrary positions
11//! - Search with custom comparator
12//! - Walk with callback
13//! - Remove first/last/all matching entries
14//! - Clear
15//! - Empty/front/back/size queries
16//! - Sort, reverse, reverse splice, merge
17//!
18//! # Phase 1 status
19//!
20//! Complete — all list operations are implemented.
21//!
22//! # Upstream contract
23//!
24//! Mirrors upstream `list.c` / `list.h` (`SRC-LIBXML2-2.15.0-LIST-C`, parity
25//! target libxml2 2.15.3 oracle): the `xmlList*` API with deallocator,
26//! comparator and walker callbacks.
27//!
28//! # Conceptual behavior
29//!
30//! Implements a doubly-linked list whose nodes are owned by the list and
31//! whose data pointers are owned by the caller unless a deallocator is
32//! registered. `xmlListAppend` does a plain push_back when no comparator is
33//! set but a SORTED insert once a comparator is present (upstream list.c);
34//! `xmlListWalk` / `xmlListReverseWalk` stop when the walker returns 0.
35//!
36//! # Ownership & safety invariants
37//!
38//! The list owns its ListNode storage (freed by `xmlListDelete` / clear
39//! paths); payload data is freed through the registered deallocator only.
40//! Walkers receive borrowed data pointers valid for the walk duration.
41//!
42//! # Historical quirks & epochs
43//!
44//! The sorted-append and 0-stops-walk semantics were misimplemented in the
45//! candidate and corrected to upstream behavior in the 11.1-L callback
46//! audit (R-000162) — both are long-standing list.c contracts, stable
47//! since the 2.6 validation era through the 2.15.3 oracle.
48//!
49//! # Deliberate oddities
50//!
51//! `xmlListAppend` becoming a sorted insert under a comparator is the
52//! upstream oddity that a naive push_back implementation misses; the walk
53//! stop convention (0 = stop, non-zero = continue) is the inverse of the
54//! intuitive C convention and is reproduced deliberately.
55//!
56//! # Proving courts
57//!
58//! CALLBACK-001 (courts/suites/data-abi/callback-family-probe.c) exercises
59//! `xmlListAppend` ordering and both walks against the oracle DSO and
60//! requires byte-identical output (R-000162 evidence); cargo test runs the
61//! list unit suites.
62//!
63//! # Tempting simplifications that would break parity
64//!
65//! Do not make `xmlListAppend` always push_back: with a comparator set,
66//! consumers rely on sorted order. Do not invert the walk stop condition
67//! back to non-zero-stops: the 11.1-L audit proved the oracle stops on 0.
68
69use core::ffi::c_void;
70use core::ptr;
71use std::os::raw::c_int;
72
73use crate::abi::allocator;
74
75// ═══════════════════════════════════════════════════════════════════════════════
76// Types
77// ═══════════════════════════════════════════════════════════════════════════════
78
79/// Deallocator function for list data.
80pub type xmlListDeallocator = unsafe extern "C" fn(*mut c_void);
81
82/// Data comparator function. Returns 0 if equal, non-zero if different.
83pub type xmlListDataCompare = unsafe extern "C" fn(*const c_void, *const c_void) -> c_int;
84
85/// Walker function for xmlListWalk.
86pub type xmlListWalker = unsafe extern "C" fn(*mut c_void, *mut c_void) -> c_int;
87
88/// A linked list node.
89struct ListNode {
90 data: *mut c_void,
91 prev: *mut ListNode,
92 next: *mut ListNode,
93}
94
95/// The linked list struct.
96#[derive(Debug)]
97pub struct List {
98 front: *mut ListNode,
99 back: *mut ListNode,
100 count: usize,
101 deallocator: Option<xmlListDeallocator>,
102 comparator: Option<xmlListDataCompare>,
103}
104
105// ═══════════════════════════════════════════════════════════════════════════════
106// Public API
107// ═══════════════════════════════════════════════════════════════════════════════
108
109/// Create a new linked list.
110///
111/// # UPSTREAM-PARITY
112///
113/// ```c
114/// xmlListPtr xmlListCreate(xmlListDeallocator deallocator,
115/// xmlListDataCompare comparator);
116/// ```
117///
118/// Creates a linked list with the given deallocator and comparator.
119/// Both may be NULL.
120pub fn list_create(
121 deallocator: Option<xmlListDeallocator>,
122 comparator: Option<xmlListDataCompare>,
123) -> *mut List {
124 let list = Box::new(List {
125 front: ptr::null_mut(),
126 back: ptr::null_mut(),
127 count: 0,
128 deallocator,
129 comparator,
130 });
131
132 Box::into_raw(list)
133}
134
135/// Delete a linked list and all its nodes.
136///
137/// # UPSTREAM-PARITY
138///
139/// ```c
140/// void xmlListDelete(xmlListPtr l);
141/// ```
142///
143/// # SAFETY
144///
145/// - `l` must be a valid pointer to a List, or NULL.
146pub unsafe fn list_delete(l: *mut List) {
147 if l.is_null() {
148 return;
149 }
150
151 let list = unsafe { &mut *l };
152 let mut cur = list.front;
153
154 while !cur.is_null() {
155 let next = unsafe { (*cur).next };
156 // UPSTREAM-PARITY (list.c xmlListDeleteInternal): the deallocator
157 // receives the LINK pointer, not the data.
158 if let Some(dealloc) = list.deallocator {
159 unsafe { dealloc(cur as *mut c_void) };
160 }
161 unsafe { allocator::xmlFreeImpl(cur as *mut c_void) };
162 cur = next;
163 }
164
165 drop(Box::from_raw(l));
166}
167
168/// Search the list for data matching the given key.
169///
170/// # UPSTREAM-PARITY
171///
172/// ```c
173/// void *xmlListSearch(xmlListPtr l, void *data);
174/// ```
175///
176/// Uses the list's comparator function. Returns the matching data, or NULL.
177///
178/// # SAFETY
179///
180/// - `l` must be a valid pointer to a List, or NULL.
181pub unsafe fn list_search(l: *mut List, data: *const c_void) -> *mut c_void {
182 if l.is_null() {
183 return ptr::null_mut();
184 }
185
186 let list = unsafe { &*l };
187 let comparator = match list.comparator {
188 Some(c) => c,
189 None => return ptr::null_mut(),
190 };
191
192 let mut cur = list.front;
193 while !cur.is_null() {
194 let node = unsafe { &*cur };
195 if unsafe { comparator(node.data as *const c_void, data) == 0 } {
196 return node.data;
197 }
198 cur = node.next;
199 }
200
201 ptr::null_mut()
202}
203
204/// Return the last element of a list (upstream list.c `xmlListEnd`): the
205/// data of the last node, or NULL.
206///
207/// # SAFETY
208///
209/// - `l` must be a valid list pointer or NULL.
210pub unsafe fn list_end(l: *mut List) -> *mut c_void {
211 if l.is_null() {
212 return ptr::null_mut();
213 }
214 let list = unsafe { &*l };
215 if list.back.is_null() {
216 return ptr::null_mut();
217 }
218 unsafe { (*list.back).data }
219}
220
221/// Reverse-search a list with the comparator (upstream list.c
222/// `xmlListReverseSearch`): scans from the back, returns the first (from
223/// the end) matching node's data, or NULL.
224///
225/// # SAFETY
226///
227/// - `l` must be a valid list pointer or NULL.
228/// - `data` must be valid for the comparator.
229pub unsafe fn list_reverse_search(l: *mut List, data: *const c_void) -> *mut c_void {
230 if l.is_null() {
231 return ptr::null_mut();
232 }
233 let list = unsafe { &*l };
234 let comparator = match list.comparator {
235 Some(c) => c,
236 None => return ptr::null_mut(),
237 };
238 let mut cur = list.back;
239 while !cur.is_null() {
240 // SAFETY: cur is a valid node; comparator is valid.
241 let node_data = unsafe { (*cur).data };
242 if unsafe { comparator(node_data as *const c_void, data) } == 0 {
243 return node_data;
244 }
245 cur = unsafe { (*cur).prev };
246 }
247 ptr::null_mut()
248}
249
250/// Walk a list in reverse with a walker callback (upstream list.c
251/// `xmlListReverseWalk`).
252///
253/// # SAFETY
254///
255/// - `l` must be a valid list pointer or NULL.
256/// - `walker` may be NULL (no-op).
257pub unsafe fn list_reverse_walk(l: *mut List, walker: Option<xmlListWalker>, data: *mut c_void) {
258 if l.is_null() {
259 return;
260 }
261 let walker = match walker {
262 Some(w) => w,
263 None => return,
264 };
265 let list = unsafe { &*l };
266 let mut cur = list.back;
267 while !cur.is_null() {
268 // SAFETY: cur is a valid node; walker is valid.
269 let node_data = unsafe { (*cur).data };
270 // UPSTREAM-PARITY (list.c xmlListReverseWalk): returns 0 to stop.
271 if unsafe { walker(node_data, data) == 0 } {
272 return;
273 }
274 cur = unsafe { (*cur).prev };
275 }
276}
277
278/// Duplicate a list (upstream list.c `xmlListDup`): a shallow copy using
279/// the same deallocator/comparator; node data pointers are copied as-is.
280/// Returns the new list or NULL on allocation failure.
281///
282/// # SAFETY
283///
284/// - `l` must be a valid list pointer or NULL.
285pub unsafe fn list_dup(l: *mut List) -> *mut List {
286 if l.is_null() {
287 return ptr::null_mut();
288 }
289 let list = unsafe { &*l };
290 let new_list = Box::new(List {
291 front: ptr::null_mut(),
292 back: ptr::null_mut(),
293 count: 0,
294 deallocator: list.deallocator,
295 comparator: list.comparator,
296 });
297 let new_ptr = Box::into_raw(new_list);
298 let mut cur = list.front;
299 while !cur.is_null() {
300 // SAFETY: cur is valid; the node data is shallow-copied.
301 let data = unsafe { (*cur).data };
302 if unsafe { list_push_back(new_ptr, data) } != 0 {
303 unsafe { list_delete(new_ptr) };
304 return ptr::null_mut();
305 }
306 cur = unsafe { (*cur).next };
307 }
308 new_ptr
309}
310
311/// Copy the contents of `old` into the existing list `cur` (upstream
312/// list.c `xmlListCopy`): walks `old`'s links and inserts each data pointer
313/// into `cur` using `cur`'s comparator. Returns 0 on success, 1 on error;
314/// on insertion failure the target list is deleted (upstream behavior).
315///
316/// # SAFETY
317///
318/// - `cur`, `old` must be valid list pointers or NULL.
319pub unsafe fn list_copy(cur: *mut List, old: *mut List) -> c_int {
320 if old.is_null() || cur.is_null() {
321 return 1;
322 }
323 let mut lk = unsafe { (*old).front };
324 while !lk.is_null() {
325 // SAFETY: lk is valid; the data pointer is shared (shallow copy,
326 // upstream inserts lk->data verbatim).
327 let data = unsafe { (*lk).data };
328 if unsafe { list_insert(cur, data) } != 0 {
329 unsafe { list_delete(cur) };
330 return 1;
331 }
332 lk = unsafe { (*lk).next };
333 }
334 0
335}
336
337/// Return the data stored in a link (upstream list.c `xmlLinkGetData`).
338///
339/// # SAFETY
340///
341/// - `link` must be a valid link pointer or NULL.
342pub unsafe fn link_get_data(link: *mut c_void) -> *mut c_void {
343 if link.is_null() {
344 return ptr::null_mut();
345 }
346 unsafe { (*(link as *mut ListNode)).data }
347}
348
349/// Walk the list, calling the walker function for each element.
350///
351/// # UPSTREAM-PARITY
352///
353/// ```c
354/// void xmlListWalk(xmlListPtr l, xmlListWalker walker, void *data);
355/// ```
356///
357/// # SAFETY
358///
359/// - `l` must be a valid pointer to a List, or NULL.
360/// - `walker` must be a valid function pointer or NULL.
361pub unsafe fn list_walk(l: *mut List, walker: Option<xmlListWalker>, data: *mut c_void) {
362 if l.is_null() || walker.is_none() {
363 return;
364 }
365 let walker = walker.unwrap();
366
367 let list = unsafe { &*l };
368 let mut cur = list.front;
369 while !cur.is_null() {
370 let node = unsafe { &*cur };
371 // UPSTREAM-PARITY (list.c xmlListWalk): the walker returns 0 to stop.
372 if unsafe { walker(node.data, data) == 0 } {
373 break;
374 }
375 cur = node.next;
376 }
377}
378
379/// Push data to the back of the list.
380///
381/// # UPSTREAM-PARITY
382///
383/// ```c
384/// int xmlListPushBack(xmlListPtr l, void *data);
385/// ```
386///
387/// Returns 0 on success, -1 on failure.
388///
389/// # SAFETY
390///
391/// - `l` must be a valid pointer to a List, or NULL.
392pub unsafe fn list_push_back(l: *mut List, data: *mut c_void) -> c_int {
393 if l.is_null() {
394 return -1;
395 }
396
397 let list = unsafe { &mut *l };
398
399 let node = allocator::xmlMallocZero(size_of::<ListNode>() as usize) as *mut ListNode;
400 if node.is_null() {
401 return -1;
402 }
403
404 unsafe {
405 (*node).data = data;
406 (*node).prev = list.back;
407 (*node).next = ptr::null_mut();
408 }
409
410 if list.back.is_null() {
411 list.front = node;
412 list.back = node;
413 } else {
414 unsafe { (*list.back).next = node };
415 list.back = node;
416 }
417
418 list.count += 1;
419 0
420}
421
422/// Push data to the front of the list.
423///
424/// # UPSTREAM-PARITY
425///
426/// ```c
427/// int xmlListPushFront(xmlListPtr l, void *data);
428/// ```
429///
430/// Returns 0 on success, -1 on failure.
431///
432/// # SAFETY
433///
434/// - `l` must be a valid pointer to a List, or NULL.
435pub unsafe fn list_push_front(l: *mut List, data: *mut c_void) -> c_int {
436 if l.is_null() {
437 return -1;
438 }
439
440 let list = unsafe { &mut *l };
441
442 let node = allocator::xmlMallocZero(size_of::<ListNode>() as usize) as *mut ListNode;
443 if node.is_null() {
444 return -1;
445 }
446
447 unsafe {
448 (*node).data = data;
449 (*node).prev = ptr::null_mut();
450 (*node).next = list.front;
451 }
452
453 if list.front.is_null() {
454 list.front = node;
455 list.back = node;
456 } else {
457 unsafe { (*list.front).prev = node };
458 list.front = node;
459 }
460
461 list.count += 1;
462 0
463}
464
465/// Pop data from the back of the list.
466///
467/// # UPSTREAM-PARITY
468///
469/// ```c
470/// void xmlListPopBack(xmlListPtr l);
471/// ```
472///
473/// # SAFETY
474///
475/// - `l` must be a valid pointer to a List, or NULL.
476pub unsafe fn list_pop_back(l: *mut List) {
477 if l.is_null() {
478 return;
479 }
480
481 let list = unsafe { &mut *l };
482 if list.back.is_null() {
483 return;
484 }
485
486 let node = list.back;
487 let prev = unsafe { (*node).prev };
488
489 if let Some(dealloc) = list.deallocator {
490 unsafe { dealloc((*node).data) };
491 }
492 unsafe { allocator::xmlFreeImpl(node as *mut c_void) };
493
494 list.back = prev;
495 if prev.is_null() {
496 list.front = ptr::null_mut();
497 } else {
498 unsafe { (*prev).next = ptr::null_mut() };
499 }
500
501 list.count = list.count.saturating_sub(1);
502}
503
504/// Pop data from the front of the list.
505///
506/// # UPSTREAM-PARITY
507///
508/// ```c
509/// void xmlListPopFront(xmlListPtr l);
510/// ```
511///
512/// # SAFETY
513///
514/// - `l` must be a valid pointer to a List, or NULL.
515pub unsafe fn list_pop_front(l: *mut List) {
516 if l.is_null() {
517 return;
518 }
519
520 let list = unsafe { &mut *l };
521 if list.front.is_null() {
522 return;
523 }
524
525 let node = list.front;
526 let next = unsafe { (*node).next };
527
528 if let Some(dealloc) = list.deallocator {
529 unsafe { dealloc((*node).data) };
530 }
531 unsafe { allocator::xmlFreeImpl(node as *mut c_void) };
532
533 list.front = next;
534 if next.is_null() {
535 list.back = ptr::null_mut();
536 } else {
537 unsafe { (*next).prev = ptr::null_mut() };
538 }
539
540 list.count = list.count.saturating_sub(1);
541}
542
543/// Insert data into the sorted position.
544///
545/// # UPSTREAM-PARITY
546///
547/// ```c
548/// int xmlListInsert(xmlListPtr l, void *data);
549/// ```
550///
551/// Inserts data in sorted order using the list's comparator.
552/// Returns 0 on success, -1 on failure.
553///
554/// # SAFETY
555///
556/// - `l` must be a valid pointer to a List, or NULL.
557pub unsafe fn list_insert(l: *mut List, data: *mut c_void) -> c_int {
558 if l.is_null() {
559 return -1;
560 }
561
562 let list = unsafe { &mut *l };
563
564 // If no comparator, just push back
565 let comparator = match list.comparator {
566 Some(c) => c,
567 None => return list_push_back(l, data),
568 };
569
570 // Find insertion point
571 let mut cur = list.front;
572 while !cur.is_null() {
573 let node = unsafe { &*cur };
574 if unsafe { comparator(data as *const c_void, node.data as *const c_void) <= 0 } {
575 // Insert before cur
576 let new_node =
577 allocator::xmlMallocZero(size_of::<ListNode>() as usize) as *mut ListNode;
578 if new_node.is_null() {
579 return -1;
580 }
581 unsafe {
582 (*new_node).data = data;
583 (*new_node).prev = node.prev;
584 (*new_node).next = cur;
585 if !node.prev.is_null() {
586 (*node.prev).next = new_node;
587 } else {
588 list.front = new_node;
589 }
590 (*cur).prev = new_node;
591 }
592 list.count += 1;
593 return 0;
594 }
595 cur = node.next;
596 }
597
598 // Append at end
599 list_push_back(l, data)
600}
601
602/// Append data to the end of the list (alias for push_back).
603///
604/// # UPSTREAM-PARITY
605///
606/// ```c
607/// int xmlListAppend(xmlListPtr l, void *data);
608/// ```
609///
610/// # SAFETY
611///
612/// - `l`, `data` must be valid pointers (or NULL
613/// where the upstream C contract allows), obtained from the
614/// matching constructor/owner and not yet freed; the callee may
615/// take or keep ownership exactly as the C API specifies.
616///
617/// The caller must not race this call with concurrent mutation of the
618/// same objects from other threads (per-object state is not internally
619/// synchronized). Violating any of the above is undefined behavior.
620///
621/// Exercised by the C-API differential courts
622/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
623/// courts; those pass byte-for-byte against the upstream oracle.
624pub unsafe fn list_append(l: *mut List, data: *mut c_void) -> c_int {
625 // UPSTREAM-PARITY (list.c xmlListAppend): with a comparator the new
626 // element is inserted in sorted order (before the first node whose data
627 // compares greater); without one it is pushed to the back.
628 if l.is_null() {
629 return -1;
630 }
631 let comparator = unsafe { (*l).comparator };
632 match comparator {
633 None => unsafe { list_push_back(l, data) },
634 Some(cmp) => {
635 unsafe {
636 let mut cur = (*l).front;
637 while !cur.is_null() {
638 if cmp((*cur).data as *const c_void, data as *const c_void) > 0 {
639 break;
640 }
641 cur = (*cur).next;
642 }
643 // Insert before `cur`.
644 let node =
645 allocator::xmlMallocZero(size_of::<ListNode>() as usize) as *mut ListNode;
646 if node.is_null() {
647 return -1;
648 }
649 (*node).data = data;
650 (*node).next = cur;
651 if cur.is_null() {
652 (*node).prev = (*l).back;
653 if !(*l).back.is_null() {
654 (*(*l).back).next = node;
655 }
656 (*l).back = node;
657 if (*l).front.is_null() {
658 (*l).front = node;
659 }
660 } else {
661 (*node).prev = (*cur).prev;
662 if !(*cur).prev.is_null() {
663 (*(*cur).prev).next = node;
664 } else {
665 (*l).front = node;
666 }
667 (*cur).prev = node;
668 }
669 }
670 0
671 }
672 }
673}
674
675/// Remove the first matching element.
676///
677/// # UPSTREAM-PARITY
678///
679/// ```c
680/// int xmlListRemoveFirst(xmlListPtr l, void *data);
681/// ```
682///
683/// Returns 0 on success, -1 if not found.
684///
685/// # SAFETY
686///
687/// - `l` must be a valid pointer to a List, or NULL.
688pub unsafe fn list_remove_first(l: *mut List, data: *const c_void) -> c_int {
689 if l.is_null() {
690 return -1;
691 }
692
693 let list = unsafe { &mut *l };
694 let comparator = match list.comparator {
695 Some(c) => c,
696 None => return -1,
697 };
698
699 let mut cur = list.front;
700 while !cur.is_null() {
701 let node = unsafe { &*cur };
702 let next = node.next;
703 if unsafe { comparator(node.data as *const c_void, data) == 0 } {
704 // Remove this node
705 if !node.prev.is_null() {
706 unsafe { (*node.prev).next = node.next };
707 } else {
708 list.front = node.next;
709 }
710 if !node.next.is_null() {
711 unsafe { (*node.next).prev = node.prev };
712 } else {
713 list.back = node.prev;
714 }
715
716 if let Some(dealloc) = list.deallocator {
717 unsafe { dealloc(node.data) };
718 }
719 unsafe { allocator::xmlFreeImpl(cur as *mut c_void) };
720 list.count = list.count.saturating_sub(1);
721 return 0;
722 }
723 cur = next;
724 }
725
726 -1
727}
728
729/// Remove the last matching element.
730///
731/// # UPSTREAM-PARITY
732///
733/// ```c
734/// int xmlListRemoveLast(xmlListPtr l, void *data);
735/// ```
736///
737/// Returns 0 on success, -1 if not found.
738///
739/// # SAFETY
740///
741/// - `l` must be a valid pointer to a List, or NULL.
742pub unsafe fn list_remove_last(l: *mut List, data: *const c_void) -> c_int {
743 if l.is_null() {
744 return -1;
745 }
746
747 let list = unsafe { &mut *l };
748 let comparator = match list.comparator {
749 Some(c) => c,
750 None => return -1,
751 };
752
753 let mut cur = list.back;
754 while !cur.is_null() {
755 let node = unsafe { &*cur };
756 let prev = node.prev;
757 if unsafe { comparator(node.data as *const c_void, data) == 0 } {
758 // Remove this node
759 if !node.prev.is_null() {
760 unsafe { (*node.prev).next = node.next };
761 } else {
762 list.front = node.next;
763 }
764 if !node.next.is_null() {
765 unsafe { (*node.next).prev = node.prev };
766 } else {
767 list.back = node.prev;
768 }
769
770 if let Some(dealloc) = list.deallocator {
771 unsafe { dealloc(node.data) };
772 }
773 unsafe { allocator::xmlFreeImpl(cur as *mut c_void) };
774 list.count = list.count.saturating_sub(1);
775 return 0;
776 }
777 cur = prev;
778 }
779
780 -1
781}
782
783/// Remove all matching elements.
784///
785/// # UPSTREAM-PARITY
786///
787/// ```c
788/// int xmlListRemoveAll(xmlListPtr l, void *data);
789/// ```
790///
791/// Returns the number of elements removed.
792///
793/// # SAFETY
794///
795/// - `l` must be a valid pointer to a List, or NULL.
796pub unsafe fn list_remove_all(l: *mut List, data: *const c_void) -> c_int {
797 if l.is_null() {
798 return 0;
799 }
800
801 let list = unsafe { &mut *l };
802 let comparator = match list.comparator {
803 Some(c) => c,
804 None => return 0,
805 };
806
807 let mut removed = 0;
808 let mut cur = list.front;
809
810 while !cur.is_null() {
811 let node = unsafe { &*cur };
812 let next = node.next;
813
814 if unsafe { comparator(node.data as *const c_void, data) == 0 } {
815 // Remove this node
816 if !node.prev.is_null() {
817 unsafe { (*node.prev).next = node.next };
818 } else {
819 list.front = node.next;
820 }
821 if !node.next.is_null() {
822 unsafe { (*node.next).prev = node.prev };
823 } else {
824 list.back = node.prev;
825 }
826
827 if let Some(dealloc) = list.deallocator {
828 unsafe { dealloc(node.data) };
829 }
830 unsafe { allocator::xmlFreeImpl(cur as *mut c_void) };
831 list.count = list.count.saturating_sub(1);
832 removed += 1;
833 }
834
835 cur = next;
836 }
837
838 removed
839}
840
841/// Clear the list (remove all elements).
842///
843/// # UPSTREAM-PARITY
844///
845/// ```c
846/// void xmlListClear(xmlListPtr l);
847/// ```
848///
849/// # SAFETY
850///
851/// - `l` must be a valid pointer to a List, or NULL.
852pub unsafe fn list_clear(l: *mut List) {
853 if l.is_null() {
854 return;
855 }
856
857 let list = unsafe { &mut *l };
858 let mut cur = list.front;
859
860 while !cur.is_null() {
861 let next = unsafe { (*cur).next };
862 if let Some(dealloc) = list.deallocator {
863 unsafe { dealloc((*cur).data) };
864 }
865 unsafe { allocator::xmlFreeImpl(cur as *mut c_void) };
866 cur = next;
867 }
868
869 list.front = ptr::null_mut();
870 list.back = ptr::null_mut();
871 list.count = 0;
872}
873
874/// Check if the list is empty.
875///
876/// # UPSTREAM-PARITY
877///
878/// ```c
879/// int xmlListEmpty(xmlListPtr l);
880/// ```
881///
882/// Returns 1 if empty, 0 if not empty.
883///
884/// # Safety
885///
886/// - `l` must be NULL or a valid, initialized `List` that stays alive for
887/// the call; the list is only read.
888pub fn list_empty(l: *mut List) -> c_int {
889 if l.is_null() {
890 return 1;
891 }
892 let list = unsafe { &*l };
893 if list.front.is_null() {
894 1
895 } else {
896 0
897 }
898}
899
900/// Get the data at the front of the list.
901///
902/// # UPSTREAM-PARITY
903///
904/// ```c
905/// void *xmlListFront(xmlListPtr l);
906/// ```
907///
908/// Returns the data at the front, or NULL if the list is empty.
909///
910/// # Safety
911///
912/// - `l` must be NULL or a valid, initialized `List` that stays alive for
913/// the call; the returned payload pointer is borrowed from the list and
914/// must not be freed by the caller.
915pub fn list_front(l: *mut List) -> *mut c_void {
916 if l.is_null() {
917 return ptr::null_mut();
918 }
919 let list = unsafe { &*l };
920 if list.front.is_null() {
921 ptr::null_mut()
922 } else {
923 unsafe { (*list.front).data }
924 }
925}
926
927/// Get the data at the back of the list.
928///
929/// # UPSTREAM-PARITY
930///
931/// ```c
932/// void *xmlListBack(xmlListPtr l);
933/// ```
934///
935/// Returns the data at the back, or NULL if the list is empty.
936///
937/// # Safety
938///
939/// - `l` must be NULL or a valid, initialized `List` that stays alive for
940/// the call; the returned payload pointer is borrowed from the list and
941/// must not be freed by the caller.
942pub fn list_back(l: *mut List) -> *mut c_void {
943 if l.is_null() {
944 return ptr::null_mut();
945 }
946 let list = unsafe { &*l };
947 if list.back.is_null() {
948 ptr::null_mut()
949 } else {
950 unsafe { (*list.back).data }
951 }
952}
953
954/// Get the number of elements in the list.
955///
956/// # UPSTREAM-PARITY
957///
958/// ```c
959/// int xmlListSize(xmlListPtr l);
960/// ```
961///
962/// Returns the number of elements, or -1 if the list is NULL.
963pub fn list_size(l: *mut List) -> c_int {
964 if l.is_null() {
965 return -1;
966 }
967 let list = unsafe { &*l };
968 list.count as c_int
969}
970
971/// Sort the list in-place using the comparator.
972///
973/// # UPSTREAM-PARITY
974///
975/// ```c
976/// void xmlListSort(xmlListPtr l);
977/// ```
978///
979/// # SAFETY
980///
981/// - `l` must be a valid pointer to a List, or NULL.
982pub unsafe fn list_sort(l: *mut List) {
983 if l.is_null() {
984 return;
985 }
986
987 let list = unsafe { &mut *l };
988 if list.count <= 1 {
989 return;
990 }
991
992 let comparator = match list.comparator {
993 Some(c) => c,
994 None => return,
995 };
996
997 // Convert to Vec, sort, rebuild
998 let mut nodes: Vec<*mut ListNode> = Vec::with_capacity(list.count);
999 let mut cur = list.front;
1000 while !cur.is_null() {
1001 nodes.push(cur);
1002 cur = unsafe { (*cur).next };
1003 }
1004
1005 // Bubble sort (simple, matches upstream's simple approach)
1006 for i in 0..nodes.len() {
1007 for j in 0..nodes.len() - 1 - i {
1008 let a = unsafe { &*nodes[j] };
1009 let b = unsafe { &*nodes[j + 1] };
1010 if unsafe { comparator(a.data as *const c_void, b.data as *const c_void) > 0 } {
1011 nodes.swap(j, j + 1);
1012 }
1013 }
1014 }
1015
1016 // Rebuild links
1017 list.front = nodes[0];
1018 list.back = nodes[nodes.len() - 1];
1019
1020 for i in 0..nodes.len() {
1021 unsafe {
1022 (*nodes[i]).prev = if i > 0 { nodes[i - 1] } else { ptr::null_mut() };
1023 (*nodes[i]).next = if i + 1 < nodes.len() {
1024 nodes[i + 1]
1025 } else {
1026 ptr::null_mut()
1027 };
1028 }
1029 }
1030}
1031
1032/// Reverse the list in-place.
1033///
1034/// # UPSTREAM-PARITY
1035///
1036/// ```c
1037/// void xmlListReverse(xmlListPtr l);
1038/// ```
1039///
1040/// # SAFETY
1041///
1042/// - `l` must be a valid pointer to a List, or NULL.
1043pub unsafe fn list_reverse(l: *mut List) {
1044 if l.is_null() {
1045 return;
1046 }
1047
1048 let list = unsafe { &mut *l };
1049
1050 // `cur` keeps the old front (the new back): the walk below traverses the
1051 // old-next chain to the old back, swapping each node's links.
1052 let mut cur = list.front;
1053 std::mem::swap(&mut list.front, &mut list.back);
1054
1055 while !cur.is_null() {
1056 let next = unsafe { (*cur).next };
1057 unsafe {
1058 (*cur).next = (*cur).prev;
1059 (*cur).prev = next;
1060 }
1061 cur = next;
1062 }
1063}
1064
1065/// Reverse splice: move all elements from `l2` to the front of `l1` in reverse order.
1066///
1067/// # UPSTREAM-PARITY
1068///
1069/// ```c
1070/// void xmlListReverseSplice(xmlListPtr l1, xmlListPtr l2);
1071/// ```
1072///
1073/// # SAFETY
1074///
1075/// - `l1`, `l2` must be valid pointers to Lists, or NULL.
1076pub unsafe fn list_reverse_splice(l1: *mut List, l2: *mut List) {
1077 if l1.is_null() || l2.is_null() {
1078 return;
1079 }
1080
1081 let list1 = unsafe { &mut *l1 };
1082 let list2 = unsafe { &mut *l2 };
1083
1084 if list2.front.is_null() {
1085 return;
1086 }
1087
1088 // Reverse l2 first
1089 list_reverse(l2);
1090
1091 // Move all nodes from l2 to front of l1
1092 unsafe {
1093 (*list2.back).next = list1.front;
1094 if !list1.front.is_null() {
1095 (*list1.front).prev = list2.back;
1096 } else {
1097 list1.back = list2.back;
1098 }
1099 list1.front = list2.front;
1100 }
1101
1102 list1.count += list2.count;
1103
1104 // Clear l2
1105 list2.front = ptr::null_mut();
1106 list2.back = ptr::null_mut();
1107 list2.count = 0;
1108}
1109
1110/// Merge two sorted lists into one.
1111///
1112/// # UPSTREAM-PARITY
1113///
1114/// ```c
1115/// void xmlListMerge(xmlListPtr l1, xmlListPtr l2);
1116/// ```
1117///
1118/// Merges `l2` into `l1` in sorted order. `l2` becomes empty.
1119///
1120/// # SAFETY
1121///
1122/// - `l1`, `l2` must be valid pointers to Lists, or NULL.
1123pub unsafe fn list_merge(l1: *mut List, l2: *mut List) {
1124 if l1.is_null() || l2.is_null() {
1125 return;
1126 }
1127
1128 let list1 = unsafe { &mut *l1 };
1129 let list2 = unsafe { &mut *l2 };
1130
1131 if list2.front.is_null() {
1132 return;
1133 }
1134
1135 let comparator = match list1.comparator {
1136 Some(c) => c,
1137 None => {
1138 // No comparator — just append all of l2 to l1
1139 if !list1.back.is_null() {
1140 unsafe { (*list1.back).next = list2.front };
1141 unsafe { (*list2.front).prev = list1.back };
1142 } else {
1143 list1.front = list2.front;
1144 }
1145 list1.back = list2.back;
1146 list1.count += list2.count;
1147 list2.front = ptr::null_mut();
1148 list2.back = ptr::null_mut();
1149 list2.count = 0;
1150 return;
1151 }
1152 };
1153
1154 // Merge sorted lists
1155 let mut cur2 = list2.front;
1156 let mut insert_before = list1.front;
1157
1158 while !cur2.is_null() {
1159 let next2 = unsafe { (*cur2).next };
1160
1161 // Find insertion point
1162 while !insert_before.is_null() {
1163 if unsafe {
1164 comparator(
1165 (*cur2).data as *const c_void,
1166 (*insert_before).data as *const c_void,
1167 ) <= 0
1168 } {
1169 break;
1170 }
1171 insert_before = unsafe { (*insert_before).next };
1172 }
1173
1174 // Insert cur2 before insert_before
1175 if insert_before.is_null() {
1176 // Append at end
1177 if list1.back.is_null() {
1178 list1.front = cur2;
1179 list1.back = cur2;
1180 unsafe {
1181 (*cur2).prev = ptr::null_mut();
1182 (*cur2).next = ptr::null_mut();
1183 }
1184 } else {
1185 unsafe {
1186 (*cur2).prev = list1.back;
1187 (*cur2).next = ptr::null_mut();
1188 (*list1.back).next = cur2;
1189 }
1190 list1.back = cur2;
1191 }
1192 } else {
1193 unsafe {
1194 (*cur2).prev = (*insert_before).prev;
1195 (*cur2).next = insert_before;
1196 if !(*insert_before).prev.is_null() {
1197 (*(*insert_before).prev).next = cur2;
1198 } else {
1199 list1.front = cur2;
1200 }
1201 (*insert_before).prev = cur2;
1202 }
1203 }
1204
1205 list1.count += 1;
1206 cur2 = next2;
1207 }
1208
1209 // Clear l2
1210 list2.front = ptr::null_mut();
1211 list2.back = ptr::null_mut();
1212 list2.count = 0;
1213}
1214
1215// ═══════════════════════════════════════════════════════════════════════════════
1216// Tests
1217// ═══════════════════════════════════════════════════════════════════════════════
1218
1219#[cfg(test)]
1220mod tests {
1221 use super::*;
1222
1223 /// Compare two `i32` payloads for the list comparator callback.
1224 ///
1225 /// # Safety
1226 ///
1227 /// - `a` and `b` must be non-NULL, valid, aligned pointers to `i32` that
1228 /// remain valid for the duration of the call.
1229 unsafe extern "C" fn int_compare(a: *const c_void, b: *const c_void) -> c_int {
1230 let ai = *(a as *const i32);
1231 let bi = *(b as *const i32);
1232 ai.cmp(&bi) as c_int
1233 }
1234
1235 /// Create and delete a list with no callbacks.
1236 ///
1237 /// # Safety
1238 ///
1239 /// - `list` is non-NULL (asserted) and valid until `list_delete`
1240 /// releases it exactly once.
1241 #[test]
1242 fn test_list_create_delete() {
1243 unsafe {
1244 let list = list_create(None, None);
1245 assert!(!list.is_null());
1246 list_delete(list);
1247 }
1248 }
1249
1250 /// Push/pop payloads and verify size and front/back access.
1251 ///
1252 /// # Safety
1253 ///
1254 /// - `list` is non-NULL (asserted); `v1`/`v2` point to stack `i32`s
1255 /// alive for the test; `list_front`/`list_back` return valid
1256 /// payload pointers while the values are read; `list_delete`
1257 /// releases the list exactly once.
1258 #[test]
1259 fn test_list_push_pop() {
1260 unsafe {
1261 let list = list_create(None, None);
1262 let v1 = &mut 1 as *mut c_int as *mut c_void;
1263 let v2 = &mut 2 as *mut c_int as *mut c_void;
1264
1265 list_push_back(list, v1);
1266 list_push_back(list, v2);
1267 assert_eq!(list_size(list), 2);
1268
1269 assert_eq!(*(list_front(list) as *const i32), 1);
1270 assert_eq!(*(list_back(list) as *const i32), 2);
1271
1272 list_pop_back(list);
1273 assert_eq!(list_size(list), 1);
1274 assert_eq!(*(list_back(list) as *const i32), 1);
1275
1276 list_pop_front(list);
1277 assert_eq!(list_size(list), 0);
1278 assert_eq!(list_empty(list), 1);
1279
1280 list_delete(list);
1281 }
1282 }
1283
1284 /// Push payloads at the front and verify ordering.
1285 ///
1286 /// # Safety
1287 ///
1288 /// - `list` is non-NULL (asserted); the payload pointers point to
1289 /// stack `i32`s alive for the test; `list_delete` releases the list
1290 /// exactly once.
1291 #[test]
1292 fn test_list_push_front() {
1293 unsafe {
1294 let list = list_create(None, None);
1295 let v1 = &mut 1 as *mut c_int as *mut c_void;
1296 let v2 = &mut 2 as *mut c_int as *mut c_void;
1297
1298 list_push_front(list, v1);
1299 list_push_front(list, v2);
1300 assert_eq!(*(list_front(list) as *const i32), 2);
1301 assert_eq!(*(list_back(list) as *const i32), 1);
1302
1303 list_delete(list);
1304 }
1305 }
1306
1307 /// Insert payloads with a comparator and verify sorted order.
1308 ///
1309 /// # Safety
1310 ///
1311 /// - `list` is non-NULL (asserted); `int_compare` requires the payload
1312 /// pointers to be valid `i32`s alive for the test; `list_delete`
1313 /// releases the list exactly once.
1314 #[test]
1315 fn test_list_insert_sorted() {
1316 unsafe {
1317 let list = list_create(None, Some(int_compare));
1318 let v2 = &mut 2 as *mut c_int as *mut c_void;
1319 let v1 = &mut 1 as *mut c_int as *mut c_void;
1320 let v3 = &mut 3 as *mut c_int as *mut c_void;
1321
1322 list_insert(list, v2);
1323 list_insert(list, v1);
1324 list_insert(list, v3);
1325
1326 // Should be 1, 2, 3
1327 assert_eq!(*(list_front(list) as *const i32), 1);
1328 assert_eq!(*(list_back(list) as *const i32), 3);
1329 assert_eq!(list_size(list), 3);
1330
1331 list_delete(list);
1332 }
1333 }
1334
1335 /// Remove the first matching payload with a comparator.
1336 ///
1337 /// # Safety
1338 ///
1339 /// - `list` is non-NULL (asserted); payloads and the search key are
1340 /// valid `i32` pointers alive for the test; `list_delete` releases
1341 /// the list exactly once.
1342 #[test]
1343 fn test_list_remove_first() {
1344 unsafe {
1345 let list = list_create(None, Some(int_compare));
1346 let v1 = &mut 1 as *mut c_int as *mut c_void;
1347 let v2 = &mut 2 as *mut c_int as *mut c_void;
1348
1349 list_push_back(list, v1);
1350 list_push_back(list, v2);
1351
1352 let one: i32 = 1;
1353 let result = list_remove_first(list, &one as *const i32 as *const c_void);
1354 assert_eq!(result, 0);
1355 assert_eq!(list_size(list), 1);
1356 assert_eq!(*(list_front(list) as *const i32), 2);
1357
1358 list_delete(list);
1359 }
1360 }
1361
1362 /// Clear a list and verify it becomes empty.
1363 ///
1364 /// # Safety
1365 ///
1366 /// - `list` is non-NULL (asserted) and valid until `list_delete`
1367 /// releases it exactly once; payloads are stack values never freed
1368 /// by the list (no deallocator installed).
1369 #[test]
1370 fn test_list_clear() {
1371 unsafe {
1372 let list = list_create(None, None);
1373 list_push_back(list, &mut 1 as *mut c_int as *mut c_void);
1374 list_push_back(list, &mut 2 as *mut c_int as *mut c_void);
1375 assert_eq!(list_size(list), 2);
1376
1377 list_clear(list);
1378 assert_eq!(list_empty(list), 1);
1379 assert_eq!(list_size(list), 0);
1380
1381 list_delete(list);
1382 }
1383 }
1384
1385 /// Reverse a list and verify the new front/back ordering.
1386 ///
1387 /// # Safety
1388 ///
1389 /// - `list` is non-NULL (asserted); payload pointers point to stack
1390 /// `i32`s alive for the test; `list_delete` releases the list
1391 /// exactly once.
1392 #[test]
1393 fn test_list_reverse() {
1394 unsafe {
1395 let list = list_create(None, None);
1396 let v1 = &mut 1 as *mut c_int as *mut c_void;
1397 let v2 = &mut 2 as *mut c_int as *mut c_void;
1398 let v3 = &mut 3 as *mut c_int as *mut c_void;
1399
1400 list_push_back(list, v1);
1401 list_push_back(list, v2);
1402 list_push_back(list, v3);
1403
1404 list_reverse(list);
1405
1406 assert_eq!(*(list_front(list) as *const i32), 3);
1407 assert_eq!(*(list_back(list) as *const i32), 1);
1408
1409 list_delete(list);
1410 }
1411 }
1412
1413 /// NULL list pointers must be tolerated by the accessors.
1414 ///
1415 /// # Safety
1416 ///
1417 /// - `list_empty`, `list_size`, `list_front`, `list_back`,
1418 /// `list_delete`, `list_clear`, `list_pop_front` and `list_pop_back`
1419 /// handle NULL as documented no-ops; no pointer is dereferenced.
1420 #[test]
1421 fn test_list_null_handling() {
1422 unsafe {
1423 assert_eq!(list_empty(ptr::null_mut()), 1);
1424 assert_eq!(list_size(ptr::null_mut()), -1);
1425 assert!(list_front(ptr::null_mut()).is_null());
1426 assert!(list_back(ptr::null_mut()).is_null());
1427 list_delete(ptr::null_mut()); // Should not crash
1428 list_clear(ptr::null_mut()); // Should not crash
1429 list_pop_front(ptr::null_mut()); // Should not crash
1430 list_pop_back(ptr::null_mut()); // Should not crash
1431 }
1432 }
1433}