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 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}