Skip to main content

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 a list with a data copier (upstream list.c `xmlListCopy`): each
312/// node's data is copied through `copier` (returns a fresh pointer or
313/// NULL on failure). The result replaces the target list `l`'s content.
314/// Returns 0 on success, -1 on error.
315///
316/// # SAFETY
317///
318/// - `l` must be a valid list pointer or NULL.
319/// - `copier` must be a valid copier function.
320pub unsafe fn list_copy(
321    l: *mut List,
322    copier: Option<unsafe extern "C" fn(*mut c_void) -> *mut c_void>,
323) -> c_int {
324    if l.is_null() {
325        return -1;
326    }
327    let copier = match copier {
328        Some(c) => c,
329        None => return -1,
330    };
331    let list = unsafe { &*l };
332    let new_list = Box::new(List {
333        front: ptr::null_mut(),
334        back: ptr::null_mut(),
335        count: 0,
336        deallocator: list.deallocator,
337        comparator: list.comparator,
338    });
339    let new_ptr = Box::into_raw(new_list);
340    let mut cur = list.front;
341    while !cur.is_null() {
342        // SAFETY: cur is valid; copier must return a fresh copy or NULL.
343        let copied = unsafe { copier((*cur).data) };
344        if copied.is_null() {
345            unsafe { list_delete(new_ptr) };
346            return -1;
347        }
348        if unsafe { list_push_back(new_ptr, copied) } != 0 {
349            unsafe { list_delete(new_ptr) };
350            return -1;
351        }
352        cur = unsafe { (*cur).next };
353    }
354    // Replace `l`'s content with the copy (upstream copies INTO `l`).
355    unsafe {
356        list_clear(l);
357        let dst = &mut *l;
358        let src = &mut *new_ptr;
359        core::mem::swap(dst, src);
360        list_delete(new_ptr);
361    }
362    0
363}
364
365/// Return the data stored in a link (upstream list.c `xmlLinkGetData`).
366///
367/// # SAFETY
368///
369/// - `link` must be a valid link pointer or NULL.
370pub unsafe fn link_get_data(link: *mut c_void) -> *mut c_void {
371    if link.is_null() {
372        return ptr::null_mut();
373    }
374    unsafe { (*(link as *mut ListNode)).data }
375}
376
377/// Walk the list, calling the walker function for each element.
378///
379/// # UPSTREAM-PARITY
380///
381/// ```c
382/// void xmlListWalk(xmlListPtr l, xmlListWalker walker, void *data);
383/// ```
384///
385/// # SAFETY
386///
387/// - `l` must be a valid pointer to a List, or NULL.
388/// - `walker` must be a valid function pointer or NULL.
389pub unsafe fn list_walk(l: *mut List, walker: Option<xmlListWalker>, data: *mut c_void) {
390    if l.is_null() || walker.is_none() {
391        return;
392    }
393    let walker = walker.unwrap();
394
395    let list = unsafe { &*l };
396    let mut cur = list.front;
397    while !cur.is_null() {
398        let node = unsafe { &*cur };
399        // UPSTREAM-PARITY (list.c xmlListWalk): the walker returns 0 to stop.
400        if unsafe { walker(node.data, data) == 0 } {
401            break;
402        }
403        cur = node.next;
404    }
405}
406
407/// Push data to the back of the list.
408///
409/// # UPSTREAM-PARITY
410///
411/// ```c
412/// int xmlListPushBack(xmlListPtr l, void *data);
413/// ```
414///
415/// Returns 0 on success, -1 on failure.
416///
417/// # SAFETY
418///
419/// - `l` must be a valid pointer to a List, or NULL.
420pub unsafe fn list_push_back(l: *mut List, data: *mut c_void) -> c_int {
421    if l.is_null() {
422        return -1;
423    }
424
425    let list = unsafe { &mut *l };
426
427    let node = allocator::xmlMallocZero(size_of::<ListNode>() as usize) as *mut ListNode;
428    if node.is_null() {
429        return -1;
430    }
431
432    unsafe {
433        (*node).data = data;
434        (*node).prev = list.back;
435        (*node).next = ptr::null_mut();
436    }
437
438    if list.back.is_null() {
439        list.front = node;
440        list.back = node;
441    } else {
442        unsafe { (*list.back).next = node };
443        list.back = node;
444    }
445
446    list.count += 1;
447    0
448}
449
450/// Push data to the front of the list.
451///
452/// # UPSTREAM-PARITY
453///
454/// ```c
455/// int xmlListPushFront(xmlListPtr l, void *data);
456/// ```
457///
458/// Returns 0 on success, -1 on failure.
459///
460/// # SAFETY
461///
462/// - `l` must be a valid pointer to a List, or NULL.
463pub unsafe fn list_push_front(l: *mut List, data: *mut c_void) -> c_int {
464    if l.is_null() {
465        return -1;
466    }
467
468    let list = unsafe { &mut *l };
469
470    let node = allocator::xmlMallocZero(size_of::<ListNode>() as usize) as *mut ListNode;
471    if node.is_null() {
472        return -1;
473    }
474
475    unsafe {
476        (*node).data = data;
477        (*node).prev = ptr::null_mut();
478        (*node).next = list.front;
479    }
480
481    if list.front.is_null() {
482        list.front = node;
483        list.back = node;
484    } else {
485        unsafe { (*list.front).prev = node };
486        list.front = node;
487    }
488
489    list.count += 1;
490    0
491}
492
493/// Pop data from the back of the list.
494///
495/// # UPSTREAM-PARITY
496///
497/// ```c
498/// void xmlListPopBack(xmlListPtr l);
499/// ```
500///
501/// # SAFETY
502///
503/// - `l` must be a valid pointer to a List, or NULL.
504pub unsafe fn list_pop_back(l: *mut List) {
505    if l.is_null() {
506        return;
507    }
508
509    let list = unsafe { &mut *l };
510    if list.back.is_null() {
511        return;
512    }
513
514    let node = list.back;
515    let prev = unsafe { (*node).prev };
516
517    if let Some(dealloc) = list.deallocator {
518        unsafe { dealloc((*node).data) };
519    }
520    unsafe { allocator::xmlFreeImpl(node as *mut c_void) };
521
522    list.back = prev;
523    if prev.is_null() {
524        list.front = ptr::null_mut();
525    } else {
526        unsafe { (*prev).next = ptr::null_mut() };
527    }
528
529    list.count = list.count.saturating_sub(1);
530}
531
532/// Pop data from the front of the list.
533///
534/// # UPSTREAM-PARITY
535///
536/// ```c
537/// void xmlListPopFront(xmlListPtr l);
538/// ```
539///
540/// # SAFETY
541///
542/// - `l` must be a valid pointer to a List, or NULL.
543pub unsafe fn list_pop_front(l: *mut List) {
544    if l.is_null() {
545        return;
546    }
547
548    let list = unsafe { &mut *l };
549    if list.front.is_null() {
550        return;
551    }
552
553    let node = list.front;
554    let next = unsafe { (*node).next };
555
556    if let Some(dealloc) = list.deallocator {
557        unsafe { dealloc((*node).data) };
558    }
559    unsafe { allocator::xmlFreeImpl(node as *mut c_void) };
560
561    list.front = next;
562    if next.is_null() {
563        list.back = ptr::null_mut();
564    } else {
565        unsafe { (*next).prev = ptr::null_mut() };
566    }
567
568    list.count = list.count.saturating_sub(1);
569}
570
571/// Insert data into the sorted position.
572///
573/// # UPSTREAM-PARITY
574///
575/// ```c
576/// int xmlListInsert(xmlListPtr l, void *data);
577/// ```
578///
579/// Inserts data in sorted order using the list's comparator.
580/// Returns 0 on success, -1 on failure.
581///
582/// # SAFETY
583///
584/// - `l` must be a valid pointer to a List, or NULL.
585pub unsafe fn list_insert(l: *mut List, data: *mut c_void) -> c_int {
586    if l.is_null() {
587        return -1;
588    }
589
590    let list = unsafe { &mut *l };
591
592    // If no comparator, just push back
593    let comparator = match list.comparator {
594        Some(c) => c,
595        None => return list_push_back(l, data),
596    };
597
598    // Find insertion point
599    let mut cur = list.front;
600    while !cur.is_null() {
601        let node = unsafe { &*cur };
602        if unsafe { comparator(data as *const c_void, node.data as *const c_void) <= 0 } {
603            // Insert before cur
604            let new_node =
605                allocator::xmlMallocZero(size_of::<ListNode>() as usize) as *mut ListNode;
606            if new_node.is_null() {
607                return -1;
608            }
609            unsafe {
610                (*new_node).data = data;
611                (*new_node).prev = node.prev;
612                (*new_node).next = cur;
613                if !node.prev.is_null() {
614                    (*node.prev).next = new_node;
615                } else {
616                    list.front = new_node;
617                }
618                (*cur).prev = new_node;
619            }
620            list.count += 1;
621            return 0;
622        }
623        cur = node.next;
624    }
625
626    // Append at end
627    list_push_back(l, data)
628}
629
630/// Append data to the end of the list (alias for push_back).
631///
632/// # UPSTREAM-PARITY
633///
634/// ```c
635/// int xmlListAppend(xmlListPtr l, void *data);
636/// ```
637///
638/// # SAFETY
639///
640/// - `l`, `data` must be valid pointers (or NULL
641///   where the upstream C contract allows), obtained from the
642///   matching constructor/owner and not yet freed; the callee may
643///   take or keep ownership exactly as the C API specifies.
644///
645/// The caller must not race this call with concurrent mutation of the
646/// same objects from other threads (per-object state is not internally
647/// synchronized). Violating any of the above is undefined behavior.
648///
649/// Exercised by the C-API differential courts
650/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
651/// courts; those pass byte-for-byte against the upstream oracle.
652pub unsafe fn list_append(l: *mut List, data: *mut c_void) -> c_int {
653    // UPSTREAM-PARITY (list.c xmlListAppend): with a comparator the new
654    // element is inserted in sorted order (before the first node whose data
655    // compares greater); without one it is pushed to the back.
656    if l.is_null() {
657        return -1;
658    }
659    let comparator = unsafe { (*l).comparator };
660    match comparator {
661        None => unsafe { list_push_back(l, data) },
662        Some(cmp) => {
663            unsafe {
664                let mut cur = (*l).front;
665                while !cur.is_null() {
666                    if cmp((*cur).data as *const c_void, data as *const c_void) > 0 {
667                        break;
668                    }
669                    cur = (*cur).next;
670                }
671                // Insert before `cur`.
672                let node =
673                    allocator::xmlMallocZero(size_of::<ListNode>() as usize) as *mut ListNode;
674                if node.is_null() {
675                    return -1;
676                }
677                (*node).data = data;
678                (*node).next = cur;
679                if cur.is_null() {
680                    (*node).prev = (*l).back;
681                    if !(*l).back.is_null() {
682                        (*(*l).back).next = node;
683                    }
684                    (*l).back = node;
685                    if (*l).front.is_null() {
686                        (*l).front = node;
687                    }
688                } else {
689                    (*node).prev = (*cur).prev;
690                    if !(*cur).prev.is_null() {
691                        (*(*cur).prev).next = node;
692                    } else {
693                        (*l).front = node;
694                    }
695                    (*cur).prev = node;
696                }
697            }
698            0
699        }
700    }
701}
702
703/// Remove the first matching element.
704///
705/// # UPSTREAM-PARITY
706///
707/// ```c
708/// int xmlListRemoveFirst(xmlListPtr l, void *data);
709/// ```
710///
711/// Returns 0 on success, -1 if not found.
712///
713/// # SAFETY
714///
715/// - `l` must be a valid pointer to a List, or NULL.
716pub unsafe fn list_remove_first(l: *mut List, data: *const c_void) -> c_int {
717    if l.is_null() {
718        return -1;
719    }
720
721    let list = unsafe { &mut *l };
722    let comparator = match list.comparator {
723        Some(c) => c,
724        None => return -1,
725    };
726
727    let mut cur = list.front;
728    while !cur.is_null() {
729        let node = unsafe { &*cur };
730        let next = node.next;
731        if unsafe { comparator(node.data as *const c_void, data) == 0 } {
732            // Remove this node
733            if !node.prev.is_null() {
734                unsafe { (*node.prev).next = node.next };
735            } else {
736                list.front = node.next;
737            }
738            if !node.next.is_null() {
739                unsafe { (*node.next).prev = node.prev };
740            } else {
741                list.back = node.prev;
742            }
743
744            if let Some(dealloc) = list.deallocator {
745                unsafe { dealloc(node.data) };
746            }
747            unsafe { allocator::xmlFreeImpl(cur as *mut c_void) };
748            list.count = list.count.saturating_sub(1);
749            return 0;
750        }
751        cur = next;
752    }
753
754    -1
755}
756
757/// Remove the last matching element.
758///
759/// # UPSTREAM-PARITY
760///
761/// ```c
762/// int xmlListRemoveLast(xmlListPtr l, void *data);
763/// ```
764///
765/// Returns 0 on success, -1 if not found.
766///
767/// # SAFETY
768///
769/// - `l` must be a valid pointer to a List, or NULL.
770pub unsafe fn list_remove_last(l: *mut List, data: *const c_void) -> c_int {
771    if l.is_null() {
772        return -1;
773    }
774
775    let list = unsafe { &mut *l };
776    let comparator = match list.comparator {
777        Some(c) => c,
778        None => return -1,
779    };
780
781    let mut cur = list.back;
782    while !cur.is_null() {
783        let node = unsafe { &*cur };
784        let prev = node.prev;
785        if unsafe { comparator(node.data as *const c_void, data) == 0 } {
786            // Remove this node
787            if !node.prev.is_null() {
788                unsafe { (*node.prev).next = node.next };
789            } else {
790                list.front = node.next;
791            }
792            if !node.next.is_null() {
793                unsafe { (*node.next).prev = node.prev };
794            } else {
795                list.back = node.prev;
796            }
797
798            if let Some(dealloc) = list.deallocator {
799                unsafe { dealloc(node.data) };
800            }
801            unsafe { allocator::xmlFreeImpl(cur as *mut c_void) };
802            list.count = list.count.saturating_sub(1);
803            return 0;
804        }
805        cur = prev;
806    }
807
808    -1
809}
810
811/// Remove all matching elements.
812///
813/// # UPSTREAM-PARITY
814///
815/// ```c
816/// int xmlListRemoveAll(xmlListPtr l, void *data);
817/// ```
818///
819/// Returns the number of elements removed.
820///
821/// # SAFETY
822///
823/// - `l` must be a valid pointer to a List, or NULL.
824pub unsafe fn list_remove_all(l: *mut List, data: *const c_void) -> c_int {
825    if l.is_null() {
826        return 0;
827    }
828
829    let list = unsafe { &mut *l };
830    let comparator = match list.comparator {
831        Some(c) => c,
832        None => return 0,
833    };
834
835    let mut removed = 0;
836    let mut cur = list.front;
837
838    while !cur.is_null() {
839        let node = unsafe { &*cur };
840        let next = node.next;
841
842        if unsafe { comparator(node.data as *const c_void, data) == 0 } {
843            // Remove this node
844            if !node.prev.is_null() {
845                unsafe { (*node.prev).next = node.next };
846            } else {
847                list.front = node.next;
848            }
849            if !node.next.is_null() {
850                unsafe { (*node.next).prev = node.prev };
851            } else {
852                list.back = node.prev;
853            }
854
855            if let Some(dealloc) = list.deallocator {
856                unsafe { dealloc(node.data) };
857            }
858            unsafe { allocator::xmlFreeImpl(cur as *mut c_void) };
859            list.count = list.count.saturating_sub(1);
860            removed += 1;
861        }
862
863        cur = next;
864    }
865
866    removed
867}
868
869/// Clear the list (remove all elements).
870///
871/// # UPSTREAM-PARITY
872///
873/// ```c
874/// void xmlListClear(xmlListPtr l);
875/// ```
876///
877/// # SAFETY
878///
879/// - `l` must be a valid pointer to a List, or NULL.
880pub unsafe fn list_clear(l: *mut List) {
881    if l.is_null() {
882        return;
883    }
884
885    let list = unsafe { &mut *l };
886    let mut cur = list.front;
887
888    while !cur.is_null() {
889        let next = unsafe { (*cur).next };
890        if let Some(dealloc) = list.deallocator {
891            unsafe { dealloc((*cur).data) };
892        }
893        unsafe { allocator::xmlFreeImpl(cur as *mut c_void) };
894        cur = next;
895    }
896
897    list.front = ptr::null_mut();
898    list.back = ptr::null_mut();
899    list.count = 0;
900}
901
902/// Check if the list is empty.
903///
904/// # UPSTREAM-PARITY
905///
906/// ```c
907/// int xmlListEmpty(xmlListPtr l);
908/// ```
909///
910/// Returns 1 if empty, 0 if not empty.
911pub fn list_empty(l: *mut List) -> c_int {
912    if l.is_null() {
913        return 1;
914    }
915    let list = unsafe { &*l };
916    if list.front.is_null() {
917        1
918    } else {
919        0
920    }
921}
922
923/// Get the data at the front of the list.
924///
925/// # UPSTREAM-PARITY
926///
927/// ```c
928/// void *xmlListFront(xmlListPtr l);
929/// ```
930///
931/// Returns the data at the front, or NULL if the list is empty.
932pub fn list_front(l: *mut List) -> *mut c_void {
933    if l.is_null() {
934        return ptr::null_mut();
935    }
936    let list = unsafe { &*l };
937    if list.front.is_null() {
938        ptr::null_mut()
939    } else {
940        unsafe { (*list.front).data }
941    }
942}
943
944/// Get the data at the back of the list.
945///
946/// # UPSTREAM-PARITY
947///
948/// ```c
949/// void *xmlListBack(xmlListPtr l);
950/// ```
951///
952/// Returns the data at the back, or NULL if the list is empty.
953pub fn list_back(l: *mut List) -> *mut c_void {
954    if l.is_null() {
955        return ptr::null_mut();
956    }
957    let list = unsafe { &*l };
958    if list.back.is_null() {
959        ptr::null_mut()
960    } else {
961        unsafe { (*list.back).data }
962    }
963}
964
965/// Get the number of elements in the list.
966///
967/// # UPSTREAM-PARITY
968///
969/// ```c
970/// int xmlListSize(xmlListPtr l);
971/// ```
972///
973/// Returns the number of elements, or -1 if the list is NULL.
974pub fn list_size(l: *mut List) -> c_int {
975    if l.is_null() {
976        return -1;
977    }
978    let list = unsafe { &*l };
979    list.count as c_int
980}
981
982/// Sort the list in-place using the comparator.
983///
984/// # UPSTREAM-PARITY
985///
986/// ```c
987/// void xmlListSort(xmlListPtr l);
988/// ```
989///
990/// # SAFETY
991///
992/// - `l` must be a valid pointer to a List, or NULL.
993pub unsafe fn list_sort(l: *mut List) {
994    if l.is_null() {
995        return;
996    }
997
998    let list = unsafe { &mut *l };
999    if list.count <= 1 {
1000        return;
1001    }
1002
1003    let comparator = match list.comparator {
1004        Some(c) => c,
1005        None => return,
1006    };
1007
1008    // Convert to Vec, sort, rebuild
1009    let mut nodes: Vec<*mut ListNode> = Vec::with_capacity(list.count);
1010    let mut cur = list.front;
1011    while !cur.is_null() {
1012        nodes.push(cur);
1013        cur = unsafe { (*cur).next };
1014    }
1015
1016    // Bubble sort (simple, matches upstream's simple approach)
1017    for i in 0..nodes.len() {
1018        for j in 0..nodes.len() - 1 - i {
1019            let a = unsafe { &*nodes[j] };
1020            let b = unsafe { &*nodes[j + 1] };
1021            if unsafe { comparator(a.data as *const c_void, b.data as *const c_void) > 0 } {
1022                nodes.swap(j, j + 1);
1023            }
1024        }
1025    }
1026
1027    // Rebuild links
1028    list.front = nodes[0];
1029    list.back = nodes[nodes.len() - 1];
1030
1031    for i in 0..nodes.len() {
1032        unsafe {
1033            (*nodes[i]).prev = if i > 0 { nodes[i - 1] } else { ptr::null_mut() };
1034            (*nodes[i]).next = if i + 1 < nodes.len() {
1035                nodes[i + 1]
1036            } else {
1037                ptr::null_mut()
1038            };
1039        }
1040    }
1041}
1042
1043/// Reverse the list in-place.
1044///
1045/// # UPSTREAM-PARITY
1046///
1047/// ```c
1048/// void xmlListReverse(xmlListPtr l);
1049/// ```
1050///
1051/// # SAFETY
1052///
1053/// - `l` must be a valid pointer to a List, or NULL.
1054pub unsafe fn list_reverse(l: *mut List) {
1055    if l.is_null() {
1056        return;
1057    }
1058
1059    let list = unsafe { &mut *l };
1060
1061    // `cur` keeps the old front (the new back): the walk below traverses the
1062    // old-next chain to the old back, swapping each node's links.
1063    let mut cur = list.front;
1064    std::mem::swap(&mut list.front, &mut list.back);
1065
1066    while !cur.is_null() {
1067        let next = unsafe { (*cur).next };
1068        unsafe {
1069            (*cur).next = (*cur).prev;
1070            (*cur).prev = next;
1071        }
1072        cur = next;
1073    }
1074}
1075
1076/// Reverse splice: move all elements from `l2` to the front of `l1` in reverse order.
1077///
1078/// # UPSTREAM-PARITY
1079///
1080/// ```c
1081/// void xmlListReverseSplice(xmlListPtr l1, xmlListPtr l2);
1082/// ```
1083///
1084/// # SAFETY
1085///
1086/// - `l1`, `l2` must be valid pointers to Lists, or NULL.
1087pub unsafe fn list_reverse_splice(l1: *mut List, l2: *mut List) {
1088    if l1.is_null() || l2.is_null() {
1089        return;
1090    }
1091
1092    let list1 = unsafe { &mut *l1 };
1093    let list2 = unsafe { &mut *l2 };
1094
1095    if list2.front.is_null() {
1096        return;
1097    }
1098
1099    // Reverse l2 first
1100    list_reverse(l2);
1101
1102    // Move all nodes from l2 to front of l1
1103    unsafe {
1104        (*list2.back).next = list1.front;
1105        if !list1.front.is_null() {
1106            (*list1.front).prev = list2.back;
1107        } else {
1108            list1.back = list2.back;
1109        }
1110        list1.front = list2.front;
1111    }
1112
1113    list1.count += list2.count;
1114
1115    // Clear l2
1116    list2.front = ptr::null_mut();
1117    list2.back = ptr::null_mut();
1118    list2.count = 0;
1119}
1120
1121/// Merge two sorted lists into one.
1122///
1123/// # UPSTREAM-PARITY
1124///
1125/// ```c
1126/// void xmlListMerge(xmlListPtr l1, xmlListPtr l2);
1127/// ```
1128///
1129/// Merges `l2` into `l1` in sorted order. `l2` becomes empty.
1130///
1131/// # SAFETY
1132///
1133/// - `l1`, `l2` must be valid pointers to Lists, or NULL.
1134pub unsafe fn list_merge(l1: *mut List, l2: *mut List) {
1135    if l1.is_null() || l2.is_null() {
1136        return;
1137    }
1138
1139    let list1 = unsafe { &mut *l1 };
1140    let list2 = unsafe { &mut *l2 };
1141
1142    if list2.front.is_null() {
1143        return;
1144    }
1145
1146    let comparator = match list1.comparator {
1147        Some(c) => c,
1148        None => {
1149            // No comparator — just append all of l2 to l1
1150            if !list1.back.is_null() {
1151                unsafe { (*list1.back).next = list2.front };
1152                unsafe { (*list2.front).prev = list1.back };
1153            } else {
1154                list1.front = list2.front;
1155            }
1156            list1.back = list2.back;
1157            list1.count += list2.count;
1158            list2.front = ptr::null_mut();
1159            list2.back = ptr::null_mut();
1160            list2.count = 0;
1161            return;
1162        }
1163    };
1164
1165    // Merge sorted lists
1166    let mut cur2 = list2.front;
1167    let mut insert_before = list1.front;
1168
1169    while !cur2.is_null() {
1170        let next2 = unsafe { (*cur2).next };
1171
1172        // Find insertion point
1173        while !insert_before.is_null() {
1174            if unsafe {
1175                comparator(
1176                    (*cur2).data as *const c_void,
1177                    (*insert_before).data as *const c_void,
1178                ) <= 0
1179            } {
1180                break;
1181            }
1182            insert_before = unsafe { (*insert_before).next };
1183        }
1184
1185        // Insert cur2 before insert_before
1186        if insert_before.is_null() {
1187            // Append at end
1188            if list1.back.is_null() {
1189                list1.front = cur2;
1190                list1.back = cur2;
1191                unsafe {
1192                    (*cur2).prev = ptr::null_mut();
1193                    (*cur2).next = ptr::null_mut();
1194                }
1195            } else {
1196                unsafe {
1197                    (*cur2).prev = list1.back;
1198                    (*cur2).next = ptr::null_mut();
1199                    (*list1.back).next = cur2;
1200                }
1201                list1.back = cur2;
1202            }
1203        } else {
1204            unsafe {
1205                (*cur2).prev = (*insert_before).prev;
1206                (*cur2).next = insert_before;
1207                if !(*insert_before).prev.is_null() {
1208                    (*(*insert_before).prev).next = cur2;
1209                } else {
1210                    list1.front = cur2;
1211                }
1212                (*insert_before).prev = cur2;
1213            }
1214        }
1215
1216        list1.count += 1;
1217        cur2 = next2;
1218    }
1219
1220    // Clear l2
1221    list2.front = ptr::null_mut();
1222    list2.back = ptr::null_mut();
1223    list2.count = 0;
1224}
1225
1226// ═══════════════════════════════════════════════════════════════════════════════
1227// Tests
1228// ═══════════════════════════════════════════════════════════════════════════════
1229
1230#[cfg(test)]
1231mod tests {
1232    use super::*;
1233
1234    unsafe extern "C" fn int_compare(a: *const c_void, b: *const c_void) -> c_int {
1235        let ai = *(a as *const i32);
1236        let bi = *(b as *const i32);
1237        ai.cmp(&bi) as c_int
1238    }
1239
1240    #[test]
1241    fn test_list_create_delete() {
1242        unsafe {
1243            let list = list_create(None, None);
1244            assert!(!list.is_null());
1245            list_delete(list);
1246        }
1247    }
1248
1249    #[test]
1250    fn test_list_push_pop() {
1251        unsafe {
1252            let list = list_create(None, None);
1253            let v1 = &mut 1 as *mut c_int as *mut c_void;
1254            let v2 = &mut 2 as *mut c_int as *mut c_void;
1255
1256            list_push_back(list, v1);
1257            list_push_back(list, v2);
1258            assert_eq!(list_size(list), 2);
1259
1260            assert_eq!(*(list_front(list) as *const i32), 1);
1261            assert_eq!(*(list_back(list) as *const i32), 2);
1262
1263            list_pop_back(list);
1264            assert_eq!(list_size(list), 1);
1265            assert_eq!(*(list_back(list) as *const i32), 1);
1266
1267            list_pop_front(list);
1268            assert_eq!(list_size(list), 0);
1269            assert_eq!(list_empty(list), 1);
1270
1271            list_delete(list);
1272        }
1273    }
1274
1275    #[test]
1276    fn test_list_push_front() {
1277        unsafe {
1278            let list = list_create(None, None);
1279            let v1 = &mut 1 as *mut c_int as *mut c_void;
1280            let v2 = &mut 2 as *mut c_int as *mut c_void;
1281
1282            list_push_front(list, v1);
1283            list_push_front(list, v2);
1284            assert_eq!(*(list_front(list) as *const i32), 2);
1285            assert_eq!(*(list_back(list) as *const i32), 1);
1286
1287            list_delete(list);
1288        }
1289    }
1290
1291    #[test]
1292    fn test_list_insert_sorted() {
1293        unsafe {
1294            let list = list_create(None, Some(int_compare));
1295            let v2 = &mut 2 as *mut c_int as *mut c_void;
1296            let v1 = &mut 1 as *mut c_int as *mut c_void;
1297            let v3 = &mut 3 as *mut c_int as *mut c_void;
1298
1299            list_insert(list, v2);
1300            list_insert(list, v1);
1301            list_insert(list, v3);
1302
1303            // Should be 1, 2, 3
1304            assert_eq!(*(list_front(list) as *const i32), 1);
1305            assert_eq!(*(list_back(list) as *const i32), 3);
1306            assert_eq!(list_size(list), 3);
1307
1308            list_delete(list);
1309        }
1310    }
1311
1312    #[test]
1313    fn test_list_remove_first() {
1314        unsafe {
1315            let list = list_create(None, Some(int_compare));
1316            let v1 = &mut 1 as *mut c_int as *mut c_void;
1317            let v2 = &mut 2 as *mut c_int as *mut c_void;
1318
1319            list_push_back(list, v1);
1320            list_push_back(list, v2);
1321
1322            let one: i32 = 1;
1323            let result = list_remove_first(list, &one as *const i32 as *const c_void);
1324            assert_eq!(result, 0);
1325            assert_eq!(list_size(list), 1);
1326            assert_eq!(*(list_front(list) as *const i32), 2);
1327
1328            list_delete(list);
1329        }
1330    }
1331
1332    #[test]
1333    fn test_list_clear() {
1334        unsafe {
1335            let list = list_create(None, None);
1336            list_push_back(list, &mut 1 as *mut c_int as *mut c_void);
1337            list_push_back(list, &mut 2 as *mut c_int as *mut c_void);
1338            assert_eq!(list_size(list), 2);
1339
1340            list_clear(list);
1341            assert_eq!(list_empty(list), 1);
1342            assert_eq!(list_size(list), 0);
1343
1344            list_delete(list);
1345        }
1346    }
1347
1348    #[test]
1349    fn test_list_reverse() {
1350        unsafe {
1351            let list = list_create(None, None);
1352            let v1 = &mut 1 as *mut c_int as *mut c_void;
1353            let v2 = &mut 2 as *mut c_int as *mut c_void;
1354            let v3 = &mut 3 as *mut c_int as *mut c_void;
1355
1356            list_push_back(list, v1);
1357            list_push_back(list, v2);
1358            list_push_back(list, v3);
1359
1360            list_reverse(list);
1361
1362            assert_eq!(*(list_front(list) as *const i32), 3);
1363            assert_eq!(*(list_back(list) as *const i32), 1);
1364
1365            list_delete(list);
1366        }
1367    }
1368
1369    #[test]
1370    fn test_list_null_handling() {
1371        unsafe {
1372            assert_eq!(list_empty(ptr::null_mut()), 1);
1373            assert_eq!(list_size(ptr::null_mut()), -1);
1374            assert!(list_front(ptr::null_mut()).is_null());
1375            assert!(list_back(ptr::null_mut()).is_null());
1376            list_delete(ptr::null_mut()); // Should not crash
1377            list_clear(ptr::null_mut()); // Should not crash
1378            list_pop_front(ptr::null_mut()); // Should not crash
1379            list_pop_back(ptr::null_mut()); // Should not crash
1380        }
1381    }
1382}