1use std::sync::atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering};
4
5use crate::BackendError;
6
7pub fn checked_add_u64_value<E>(lhs: u64, rhs: u64, error: E) -> Result<u64, E> {
9 lhs.checked_add(rhs).ok_or(error)
10}
11
12pub fn checked_add_u64_lazy<E>(lhs: u64, rhs: u64, error: impl FnOnce() -> E) -> Result<u64, E> {
18 lhs.checked_add(rhs).ok_or_else(error)
19}
20
21pub fn checked_mul_u64_value<E>(lhs: u64, rhs: u64, error: E) -> Result<u64, E> {
23 lhs.checked_mul(rhs).ok_or(error)
24}
25
26pub fn checked_mul_u64_lazy<E>(lhs: u64, rhs: u64, error: impl FnOnce() -> E) -> Result<u64, E> {
32 lhs.checked_mul(rhs).ok_or_else(error)
33}
34
35pub fn checked_sub_u64_value<E>(lhs: u64, rhs: u64, error: E) -> Result<u64, E> {
37 lhs.checked_sub(rhs).ok_or(error)
38}
39
40pub fn checked_sub_u64_lazy<E>(lhs: u64, rhs: u64, error: impl FnOnce() -> E) -> Result<u64, E> {
46 lhs.checked_sub(rhs).ok_or_else(error)
47}
48
49pub fn checked_sub_usize_lazy<E>(
55 lhs: usize,
56 rhs: usize,
57 error: impl FnOnce() -> E,
58) -> Result<usize, E> {
59 lhs.checked_sub(rhs).ok_or_else(error)
60}
61
62pub fn checked_add_usize_value<E>(lhs: usize, rhs: usize, error: E) -> Result<usize, E> {
64 lhs.checked_add(rhs).ok_or(error)
65}
66
67pub fn checked_add_usize_lazy<E>(
73 lhs: usize,
74 rhs: usize,
75 error: impl FnOnce() -> E,
76) -> Result<usize, E> {
77 lhs.checked_add(rhs).ok_or_else(error)
78}
79
80pub fn checked_mul_usize_lazy<E>(
86 lhs: usize,
87 rhs: usize,
88 error: impl FnOnce() -> E,
89) -> Result<usize, E> {
90 lhs.checked_mul(rhs).ok_or_else(error)
91}
92
93pub fn checked_usize_to_u64_lazy<E>(value: usize, error: impl FnOnce() -> E) -> Result<u64, E> {
99 u64::try_from(value).map_err(|_| error())
100}
101
102pub fn checked_usize_byte_range_end_lazy<E>(
109 start: usize,
110 len: usize,
111 limit: usize,
112 overflow_error: impl FnOnce() -> E,
113 out_of_bounds_error: impl FnOnce(usize) -> E,
114) -> Result<usize, E> {
115 let end = start.checked_add(len).ok_or_else(overflow_error)?;
116 if end > limit {
117 return Err(out_of_bounds_error(end));
118 }
119 Ok(end)
120}
121
122pub fn checked_add_u64_usize_offset_lazy<E>(
129 base: u64,
130 offset: usize,
131 conversion_error: impl FnOnce() -> E,
132 overflow_error: impl FnOnce() -> E,
133) -> Result<u64, E> {
134 let offset = u64::try_from(offset).map_err(|_| conversion_error())?;
135 base.checked_add(offset).ok_or_else(overflow_error)
136}
137
138#[cfg(test)]
139mod byte_range_accounting_tests {
140 use std::cell::Cell;
141
142 use super::{
143 checked_add_u64_usize_offset_lazy, checked_mul_u32_value, checked_mul_u64_lazy,
144 checked_sub_u64_lazy, checked_sub_usize_lazy, checked_usize_byte_range_end_lazy,
145 checked_usize_to_u64_lazy,
146 };
147
148 #[test]
149 fn checked_mul_u64_lazy_is_lazy_on_success() {
150 let overflow_called = Cell::new(false);
151
152 let value = checked_mul_u64_lazy(8, 4, || {
153 overflow_called.set(true);
154 "overflow"
155 });
156
157 assert_eq!(value, Ok(32));
158 assert!(!overflow_called.get());
159 }
160
161 #[test]
162 fn checked_mul_u64_lazy_reports_overflow() {
163 let value = checked_mul_u64_lazy(u64::MAX, 2, || "overflow");
164
165 assert_eq!(value, Err("overflow"));
166 }
167
168 #[test]
169 fn checked_mul_u32_value_multiplies_without_wraparound() {
170 let value = checked_mul_u32_value(128, 8, "overflow");
171
172 assert_eq!(value, Ok(1024));
173 }
174
175 #[test]
176 fn checked_mul_u32_value_reports_overflow() {
177 let value = checked_mul_u32_value(u32::MAX, 2, "overflow");
178
179 assert_eq!(value, Err("overflow"));
180 }
181
182 #[test]
183 fn checked_sub_u64_lazy_reports_underflow() {
184 let value = checked_sub_u64_lazy(1, 2, || "underflow");
185
186 assert_eq!(value, Err("underflow"));
187 }
188
189 #[test]
190 fn checked_sub_usize_lazy_reports_underflow() {
191 let value = checked_sub_usize_lazy(4, 8, || "underflow");
192
193 assert_eq!(value, Err("underflow"));
194 }
195
196 #[test]
197 fn checked_usize_to_u64_lazy_converts_host_width() {
198 let value = checked_usize_to_u64_lazy(64, || "overflow");
199
200 assert_eq!(value, Ok(64));
201 }
202
203 #[test]
204 fn checked_usize_byte_range_end_lazy_is_lazy_on_success() {
205 let overflow_called = Cell::new(false);
206 let bounds_called = Cell::new(false);
207
208 let end = checked_usize_byte_range_end_lazy(
209 8,
210 4,
211 16,
212 || {
213 overflow_called.set(true);
214 "overflow"
215 },
216 |_| {
217 bounds_called.set(true);
218 "bounds"
219 },
220 );
221
222 assert_eq!(end, Ok(12));
223 assert!(!overflow_called.get());
224 assert!(!bounds_called.get());
225 }
226
227 #[test]
228 fn checked_usize_byte_range_end_lazy_passes_computed_end_to_bounds_error() {
229 let end = checked_usize_byte_range_end_lazy(8, 5, 12, || usize::MAX, |end| end);
230
231 assert_eq!(end, Err(13));
232 }
233
234 #[test]
235 fn checked_add_u64_usize_offset_lazy_is_lazy_on_success() {
236 let conversion_called = Cell::new(false);
237 let overflow_called = Cell::new(false);
238
239 let value = checked_add_u64_usize_offset_lazy(
240 64,
241 8,
242 || {
243 conversion_called.set(true);
244 "conversion"
245 },
246 || {
247 overflow_called.set(true);
248 "overflow"
249 },
250 );
251
252 assert_eq!(value, Ok(72));
253 assert!(!conversion_called.get());
254 assert!(!overflow_called.get());
255 }
256
257 #[test]
258 fn checked_add_u64_usize_offset_lazy_reports_pointer_overflow() {
259 let value = checked_add_u64_usize_offset_lazy(u64::MAX, 1, || "conversion", || "overflow");
260
261 assert_eq!(value, Err("overflow"));
262 }
263}
264
265pub fn checked_add_u32_value<E>(lhs: u32, rhs: u32, error: E) -> Result<u32, E> {
267 lhs.checked_add(rhs).ok_or(error)
268}
269
270pub fn checked_mul_u32_value<E>(lhs: u32, rhs: u32, error: E) -> Result<u32, E> {
272 lhs.checked_mul(rhs).ok_or(error)
273}
274
275pub trait ArithmeticOverflow: Sized {
277 fn arithmetic_overflow(field: &'static str) -> Self;
279}
280
281pub fn checked_add_u64_count<E>(lhs: u64, rhs: u64, field: &'static str) -> Result<u64, E>
287where
288 E: ArithmeticOverflow,
289{
290 checked_add_u64_value(lhs, rhs, E::arithmetic_overflow(field))
291}
292
293pub fn checked_mul_u64_count<E>(lhs: u64, rhs: u64, field: &'static str) -> Result<u64, E>
299where
300 E: ArithmeticOverflow,
301{
302 checked_mul_u64_value(lhs, rhs, E::arithmetic_overflow(field))
303}
304
305pub fn checked_sub_u64_count<E>(lhs: u64, rhs: u64, field: &'static str) -> Result<u64, E>
311where
312 E: ArithmeticOverflow,
313{
314 checked_sub_u64_value(lhs, rhs, E::arithmetic_overflow(field))
315}
316
317pub fn checked_add_usize_count<E>(lhs: usize, rhs: usize, field: &'static str) -> Result<usize, E>
323where
324 E: ArithmeticOverflow,
325{
326 checked_add_usize_value(lhs, rhs, E::arithmetic_overflow(field))
327}
328
329pub fn checked_add_u32_count<E>(lhs: u32, rhs: u32, field: &'static str) -> Result<u32, E>
335where
336 E: ArithmeticOverflow,
337{
338 checked_add_u32_value(lhs, rhs, E::arithmetic_overflow(field))
339}
340
341pub fn checked_atomic_add_u64(
347 counter: &AtomicU64,
348 value: u64,
349 overflow: impl Fn(u64, u64) -> BackendError,
350) -> Result<(), BackendError> {
351 checked_atomic_add_u64_with_order(
352 counter,
353 value,
354 Ordering::Relaxed,
355 Ordering::Relaxed,
356 Ordering::Relaxed,
357 overflow,
358 )
359}
360
361pub fn checked_atomic_add_u64_with_order<E>(
367 counter: &AtomicU64,
368 value: u64,
369 load_order: Ordering,
370 success_order: Ordering,
371 failure_order: Ordering,
372 overflow: impl Fn(u64, u64) -> E,
373) -> Result<(), E> {
374 checked_atomic_add_u64_guarded_with_order(
375 counter,
376 value,
377 load_order,
378 success_order,
379 failure_order,
380 overflow,
381 |_| Ok(()),
382 )
383}
384
385pub fn checked_atomic_add_u64_guarded_with_order<E>(
392 counter: &AtomicU64,
393 value: u64,
394 load_order: Ordering,
395 success_order: Ordering,
396 failure_order: Ordering,
397 overflow: impl Fn(u64, u64) -> E,
398 mut validate_next: impl FnMut(u64) -> Result<(), E>,
399) -> Result<(), E> {
400 let mut observed = counter.load(load_order);
401 loop {
402 let next = observed
403 .checked_add(value)
404 .ok_or_else(|| overflow(observed, value))?;
405 validate_next(next)?;
406 match counter.compare_exchange_weak(observed, next, success_order, failure_order) {
407 Ok(_) => return Ok(()),
408 Err(actual) => observed = actual,
409 }
410 }
411}
412
413pub fn checked_atomic_add_usize(
419 counter: &AtomicUsize,
420 value: usize,
421 overflow: impl Fn(usize, usize) -> BackendError,
422) -> Result<(), BackendError> {
423 checked_atomic_add_usize_with_order(
424 counter,
425 value,
426 Ordering::Acquire,
427 Ordering::AcqRel,
428 Ordering::Acquire,
429 overflow,
430 )
431}
432
433pub fn checked_atomic_add_usize_with_order<E>(
439 counter: &AtomicUsize,
440 value: usize,
441 load_order: Ordering,
442 success_order: Ordering,
443 failure_order: Ordering,
444 overflow: impl Fn(usize, usize) -> E,
445) -> Result<(), E> {
446 checked_atomic_add_usize_guarded_with_order(
447 counter,
448 value,
449 load_order,
450 success_order,
451 failure_order,
452 overflow,
453 |_| Ok(()),
454 )
455}
456
457pub fn checked_atomic_add_usize_guarded_with_order<E>(
465 counter: &AtomicUsize,
466 value: usize,
467 load_order: Ordering,
468 success_order: Ordering,
469 failure_order: Ordering,
470 overflow: impl Fn(usize, usize) -> E,
471 mut validate_next: impl FnMut(usize) -> Result<(), E>,
472) -> Result<(), E> {
473 let mut observed = counter.load(load_order);
474 loop {
475 let next = observed
476 .checked_add(value)
477 .ok_or_else(|| overflow(observed, value))?;
478 validate_next(next)?;
479 match counter.compare_exchange_weak(observed, next, success_order, failure_order) {
480 Ok(_) => return Ok(()),
481 Err(actual) => observed = actual,
482 }
483 }
484}
485
486pub fn checked_atomic_sub_u64(
492 counter: &AtomicU64,
493 value: u64,
494 underflow: impl Fn(u64, u64) -> BackendError,
495) -> Result<(), BackendError> {
496 checked_atomic_sub_u64_with_order(
497 counter,
498 value,
499 Ordering::Acquire,
500 Ordering::AcqRel,
501 Ordering::Acquire,
502 underflow,
503 )
504}
505
506pub fn checked_atomic_sub_u64_with_order<E>(
512 counter: &AtomicU64,
513 value: u64,
514 load_order: Ordering,
515 success_order: Ordering,
516 failure_order: Ordering,
517 underflow: impl Fn(u64, u64) -> E,
518) -> Result<(), E> {
519 if value == 0 {
520 return Ok(());
521 }
522 let mut observed = counter.load(load_order);
523 loop {
524 let next = observed
525 .checked_sub(value)
526 .ok_or_else(|| underflow(observed, value))?;
527 match counter.compare_exchange_weak(observed, next, success_order, failure_order) {
528 Ok(_) => return Ok(()),
529 Err(actual) => observed = actual,
530 }
531 }
532}
533
534pub fn checked_atomic_sub_usize(
540 counter: &AtomicUsize,
541 value: usize,
542 underflow: impl Fn(usize, usize) -> BackendError,
543) -> Result<(), BackendError> {
544 checked_atomic_sub_usize_with_order(
545 counter,
546 value,
547 Ordering::Acquire,
548 Ordering::AcqRel,
549 Ordering::Acquire,
550 underflow,
551 )
552}
553
554pub fn checked_atomic_sub_usize_with_order<E>(
560 counter: &AtomicUsize,
561 value: usize,
562 load_order: Ordering,
563 success_order: Ordering,
564 failure_order: Ordering,
565 underflow: impl Fn(usize, usize) -> E,
566) -> Result<(), E> {
567 if value == 0 {
568 return Ok(());
569 }
570 let mut observed = counter.load(load_order);
571 loop {
572 let next = observed
573 .checked_sub(value)
574 .ok_or_else(|| underflow(observed, value))?;
575 match counter.compare_exchange_weak(observed, next, success_order, failure_order) {
576 Ok(_) => return Ok(()),
577 Err(actual) => observed = actual,
578 }
579 }
580}
581
582pub fn checked_atomic_update_u64_with_order<E>(
589 counter: &AtomicU64,
590 load_order: Ordering,
591 success_order: Ordering,
592 failure_order: Ordering,
593 mut update: impl FnMut(u64) -> Result<u64, E>,
594 mut on_retry: impl FnMut(u64, u64) -> Result<(), E>,
595) -> Result<u64, E> {
596 let mut observed = counter.load(load_order);
597 loop {
598 let next = update(observed)?;
599 match counter.compare_exchange_weak(observed, next, success_order, failure_order) {
600 Ok(previous) => return Ok(previous),
601 Err(actual) => {
602 on_retry(observed, actual)?;
603 observed = actual;
604 }
605 }
606 }
607}
608
609pub fn checked_atomic_update_u32_with_order<E>(
615 counter: &AtomicU32,
616 load_order: Ordering,
617 success_order: Ordering,
618 failure_order: Ordering,
619 mut update: impl FnMut(u32) -> Result<u32, E>,
620 mut on_retry: impl FnMut(u32, u32) -> Result<(), E>,
621) -> Result<u32, E> {
622 let mut observed = counter.load(load_order);
623 loop {
624 let next = update(observed)?;
625 match counter.compare_exchange_weak(observed, next, success_order, failure_order) {
626 Ok(previous) => return Ok(previous),
627 Err(actual) => {
628 on_retry(observed, actual)?;
629 observed = actual;
630 }
631 }
632 }
633}
634
635#[cfg(test)]
636mod checked_atomic_update_with_order_tests {
637 use super::*;
638
639 #[test]
640 fn checked_atomic_update_u64_publishes_checked_next_and_returns_observed() {
641 let counter = AtomicU64::new(41);
642
643 let previous = checked_atomic_update_u64_with_order(
644 &counter,
645 Ordering::Acquire,
646 Ordering::AcqRel,
647 Ordering::Acquire,
648 |observed| observed.checked_add(1).ok_or("overflow"),
649 |_, _| Ok(()),
650 )
651 .expect("Fix: reject accounting updates that overflow the tracked counter range - update should fit");
652
653 assert_eq!(previous, 41);
654 assert_eq!(counter.load(Ordering::Acquire), 42);
655 }
656
657 #[test]
658 fn checked_atomic_update_u32_rejects_without_publishing() {
659 let counter = AtomicU32::new(u32::MAX);
660
661 let error = checked_atomic_update_u32_with_order(
662 &counter,
663 Ordering::Acquire,
664 Ordering::AcqRel,
665 Ordering::Acquire,
666 |observed| observed.checked_add(1).ok_or("overflow"),
667 |_, _| Ok(()),
668 )
669 .expect_err("overflow should be surfaced");
670
671 assert_eq!(error, "overflow");
672 assert_eq!(counter.load(Ordering::Acquire), u32::MAX);
673 }
674}
675
676pub fn repair_atomic_sub_usize_with_order(
682 counter: &AtomicUsize,
683 value: usize,
684 load_order: Ordering,
685 success_order: Ordering,
686 failure_order: Ordering,
687 on_repair: impl FnMut(usize, usize),
688) {
689 let _ = repair_atomic_sub_usize_fetch_with_order(
690 counter,
691 value,
692 load_order,
693 success_order,
694 failure_order,
695 on_repair,
696 );
697}
698
699pub fn repair_atomic_sub_usize_fetch_with_order(
705 counter: &AtomicUsize,
706 value: usize,
707 load_order: Ordering,
708 success_order: Ordering,
709 failure_order: Ordering,
710 mut on_repair: impl FnMut(usize, usize),
711) -> usize {
712 if value == 0 {
713 return counter.load(load_order);
714 }
715 let mut observed = counter.load(load_order);
716 loop {
717 let Some(next) = observed.checked_sub(value) else {
718 match counter.compare_exchange_weak(observed, 0, success_order, failure_order) {
719 Ok(_) => {
720 on_repair(observed, value);
721 return observed;
722 }
723 Err(actual) => {
724 observed = actual;
725 continue;
726 }
727 }
728 };
729 match counter.compare_exchange_weak(observed, next, success_order, failure_order) {
730 Ok(_) => return observed,
731 Err(actual) => observed = actual,
732 }
733 }
734}
735
736pub fn pinning_atomic_add_usize_with_order(
742 counter: &AtomicUsize,
743 value: usize,
744 success_order: Ordering,
745 failure_order: Ordering,
746 on_pinned: impl FnOnce(usize, usize),
747) -> usize {
748 if value == 0 {
749 return counter.load(failure_order);
750 }
751 let mut current = counter.load(failure_order);
752 loop {
753 let next = current.checked_add(value).unwrap_or(usize::MAX);
754 match counter.compare_exchange_weak(current, next, success_order, failure_order) {
755 Ok(previous) => {
756 if next == usize::MAX && previous != usize::MAX {
757 on_pinned(previous, value);
758 }
759 return previous;
760 }
761 Err(observed) => current = observed,
762 }
763 }
764}
765
766#[cfg(test)]
767mod pinning_atomic_add_usize_with_order_tests {
768 use super::*;
769
770 #[test]
771 fn pinning_atomic_add_usize_pins_without_wrapping_and_returns_previous() {
772 let counter = AtomicUsize::new(usize::MAX - 1);
773 let mut pinned = None;
774
775 let previous = pinning_atomic_add_usize_with_order(
776 &counter,
777 2,
778 Ordering::AcqRel,
779 Ordering::Acquire,
780 |observed, value| pinned = Some((observed, value)),
781 );
782
783 assert_eq!(previous, usize::MAX - 1);
784 assert_eq!(counter.load(Ordering::Acquire), usize::MAX);
785 assert_eq!(pinned, Some((usize::MAX - 1, 2)));
786
787 let mut called_again = false;
788 let previous = pinning_atomic_add_usize_with_order(
789 &counter,
790 1,
791 Ordering::AcqRel,
792 Ordering::Acquire,
793 |_, _| called_again = true,
794 );
795
796 assert_eq!(previous, usize::MAX);
797 assert_eq!(counter.load(Ordering::Acquire), usize::MAX);
798 assert!(!called_again);
799 }
800
801 #[test]
802 fn repair_atomic_sub_usize_fetch_repairs_and_returns_observed() {
803 let counter = AtomicUsize::new(3);
804 let mut repair = None;
805
806 let previous = repair_atomic_sub_usize_fetch_with_order(
807 &counter,
808 5,
809 Ordering::Acquire,
810 Ordering::AcqRel,
811 Ordering::Acquire,
812 |observed, value| repair = Some((observed, value)),
813 );
814
815 assert_eq!(previous, 3);
816 assert_eq!(counter.load(Ordering::Acquire), 0);
817 assert_eq!(repair, Some((3, 5)));
818 }
819}
820
821pub fn pinning_atomic_increment_u64(
826 counter: &AtomicU64,
827 success_order: Ordering,
828 failure_order: Ordering,
829 on_pinned: impl FnOnce(),
830) -> bool {
831 let mut current = counter.load(failure_order);
832 loop {
833 let Some(next) = current.checked_add(1) else {
834 on_pinned();
835 return false;
836 };
837 match counter.compare_exchange_weak(current, next, success_order, failure_order) {
838 Ok(_) => return true,
839 Err(observed) => current = observed,
840 }
841 }
842}
843
844pub fn pinning_atomic_increment_u32(
849 counter: &AtomicU32,
850 success_order: Ordering,
851 failure_order: Ordering,
852 on_pinned: impl FnOnce(),
853) -> bool {
854 let mut current = counter.load(failure_order);
855 loop {
856 let Some(next) = current.checked_add(1) else {
857 on_pinned();
858 return false;
859 };
860 match counter.compare_exchange_weak(current, next, success_order, failure_order) {
861 Ok(_) => return true,
862 Err(observed) => current = observed,
863 }
864 }
865}
866
867pub fn rebasing_atomic_next_u64(
873 counter: &AtomicU64,
874 rebase_to: u64,
875 load_order: Ordering,
876 success_order: Ordering,
877 failure_order: Ordering,
878 mut on_rebase: impl FnMut(u64, u64),
879) -> u64 {
880 let mut observed = counter.load(load_order);
881 loop {
882 let next = match observed.checked_add(1) {
883 Some(next) => next,
884 None => rebase_to,
885 };
886 match counter.compare_exchange_weak(observed, next, success_order, failure_order) {
887 Ok(_) => {
888 if next == rebase_to && observed == u64::MAX {
889 on_rebase(observed, rebase_to);
890 }
891 return observed;
892 }
893 Err(actual) => observed = actual,
894 }
895 }
896}
897
898pub fn checked_atomic_next_u64_with_order<E>(
905 counter: &AtomicU64,
906 load_order: Ordering,
907 success_order: Ordering,
908 failure_order: Ordering,
909 overflow: impl Fn(u64) -> E,
910) -> Result<u64, E> {
911 let mut observed = counter.load(load_order);
912 loop {
913 let next = observed.checked_add(1).ok_or_else(|| overflow(observed))?;
914 match counter.compare_exchange_weak(observed, next, success_order, failure_order) {
915 Ok(_) => return Ok(observed),
916 Err(actual) => observed = actual,
917 }
918 }
919}
920
921pub fn atomic_max_u64(counter: &AtomicU64, value: u64, order: Ordering) -> u64 {
926 counter.fetch_max(value, order)
927}
928
929pub fn pinning_increment_u64(counter: &mut u64, on_pinned: impl FnOnce()) -> bool {
934 match counter.checked_add(1) {
935 Some(next) => {
936 *counter = next;
937 true
938 }
939 None => {
940 on_pinned();
941 *counter = u64::MAX;
942 false
943 }
944 }
945}
946
947#[cfg(test)]
948mod tests {
949 use std::sync::atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering};
950
951 use super::{
952 atomic_max_u64, checked_add_u32_count, checked_add_u32_value, checked_add_u64_count,
953 checked_add_u64_lazy, checked_add_u64_value, checked_add_usize_count,
954 checked_add_usize_lazy, checked_add_usize_value, checked_atomic_add_u64,
955 checked_atomic_add_u64_guarded_with_order, checked_atomic_add_u64_with_order,
956 checked_atomic_add_usize, checked_atomic_add_usize_guarded_with_order,
957 checked_atomic_add_usize_with_order, checked_atomic_next_u64_with_order,
958 checked_atomic_sub_u64, checked_atomic_sub_u64_with_order, checked_atomic_sub_usize,
959 checked_atomic_sub_usize_with_order, checked_mul_u64_count, checked_mul_u64_value,
960 checked_mul_usize_lazy, checked_sub_u64_count, checked_sub_u64_value,
961 pinning_atomic_increment_u32, pinning_atomic_increment_u64, pinning_increment_u64,
962 rebasing_atomic_next_u64, repair_atomic_sub_usize_with_order, ArithmeticOverflow,
963 };
964
965 #[derive(Debug, Eq, PartialEq)]
966 enum ArithmeticError {
967 Overflow(&'static str),
968 }
969
970 impl ArithmeticOverflow for ArithmeticError {
971 fn arithmetic_overflow(field: &'static str) -> Self {
972 Self::Overflow(field)
973 }
974 }
975
976 #[test]
977 fn checked_value_helpers_preserve_domain_errors() {
978 assert_eq!(checked_add_u64_value(2, 3, "overflow"), Ok(5));
979 assert_eq!(checked_mul_u64_value(2, 3, "overflow"), Ok(6));
980 assert_eq!(checked_sub_u64_value(5, 3, "underflow"), Ok(2));
981 assert_eq!(checked_add_usize_value(2, 3, "overflow"), Ok(5));
982 assert_eq!(checked_add_u32_value(2, 3, "overflow"), Ok(5));
983
984 assert_eq!(
985 checked_add_u64_value(u64::MAX, 1, "overflow"),
986 Err("overflow")
987 );
988 assert_eq!(
989 checked_mul_u64_value(u64::MAX, 2, "overflow"),
990 Err("overflow")
991 );
992 assert_eq!(checked_sub_u64_value(0, 1, "underflow"), Err("underflow"));
993 assert_eq!(
994 checked_add_usize_value(usize::MAX, 1, "overflow"),
995 Err("overflow")
996 );
997 assert_eq!(
998 checked_add_u32_value(u32::MAX, 1, "overflow"),
999 Err("overflow")
1000 );
1001 }
1002
1003 #[test]
1004 fn checked_add_usize_lazy_does_not_build_success_error() {
1005 let mut constructed = false;
1006
1007 assert_eq!(
1008 checked_add_usize_lazy(2, 3, || {
1009 constructed = true;
1010 "overflow"
1011 }),
1012 Ok(5)
1013 );
1014 assert!(
1015 !constructed,
1016 "Fix: hot-path checked usize accounting must not construct error strings on success."
1017 );
1018 assert_eq!(
1019 checked_add_usize_lazy(usize::MAX, 1, || "overflow"),
1020 Err("overflow")
1021 );
1022 }
1023
1024 #[test]
1025 fn checked_add_u64_lazy_does_not_build_success_error() {
1026 let mut constructed = false;
1027
1028 assert_eq!(
1029 checked_add_u64_lazy(2, 3, || {
1030 constructed = true;
1031 "overflow"
1032 }),
1033 Ok(5)
1034 );
1035 assert!(
1036 !constructed,
1037 "Fix: hot-path checked u64 accounting must not construct error strings on success."
1038 );
1039 assert_eq!(
1040 checked_add_u64_lazy(u64::MAX, 1, || "overflow"),
1041 Err("overflow")
1042 );
1043 }
1044
1045 #[test]
1046 fn checked_mul_usize_lazy_does_not_build_success_error() {
1047 let mut constructed = false;
1048
1049 assert_eq!(
1050 checked_mul_usize_lazy(2, 3, || {
1051 constructed = true;
1052 "overflow"
1053 }),
1054 Ok(6)
1055 );
1056 assert!(
1057 !constructed,
1058 "Fix: hot-path checked usize multiplication must not construct error strings on success."
1059 );
1060 assert_eq!(
1061 checked_mul_usize_lazy(usize::MAX, 2, || "overflow"),
1062 Err("overflow")
1063 );
1064 }
1065
1066 #[test]
1067 fn typed_checked_arithmetic_helpers_preserve_domain_error_fields() {
1068 assert_eq!(
1069 checked_add_u64_count::<ArithmeticError>(u64::MAX, 1, "u64 add"),
1070 Err(ArithmeticError::Overflow("u64 add"))
1071 );
1072 assert_eq!(
1073 checked_mul_u64_count::<ArithmeticError>(u64::MAX, 2, "u64 mul"),
1074 Err(ArithmeticError::Overflow("u64 mul"))
1075 );
1076 assert_eq!(
1077 checked_sub_u64_count::<ArithmeticError>(0, 1, "u64 sub"),
1078 Err(ArithmeticError::Overflow("u64 sub"))
1079 );
1080 assert_eq!(
1081 checked_add_usize_count::<ArithmeticError>(usize::MAX, 1, "usize add"),
1082 Err(ArithmeticError::Overflow("usize add"))
1083 );
1084 assert_eq!(
1085 checked_add_u32_count::<ArithmeticError>(u32::MAX, 1, "u32 add"),
1086 Err(ArithmeticError::Overflow("u32 add"))
1087 );
1088 }
1089
1090 #[test]
1091 fn generated_checked_arithmetic_matrix_matches_primitive_semantics() {
1092 const VALUES: [u64; 12] = [
1093 0,
1094 1,
1095 2,
1096 3,
1097 7,
1098 31,
1099 255,
1100 1024,
1101 u32::MAX as u64,
1102 u64::MAX / 2,
1103 u64::MAX - 1,
1104 u64::MAX,
1105 ];
1106
1107 for lhs in VALUES {
1108 for rhs in VALUES {
1109 assert_eq!(
1110 checked_add_u64_value(lhs, rhs, "overflow").ok(),
1111 lhs.checked_add(rhs)
1112 );
1113 assert_eq!(
1114 checked_mul_u64_value(lhs, rhs, "overflow").ok(),
1115 lhs.checked_mul(rhs)
1116 );
1117 assert_eq!(
1118 checked_sub_u64_value(lhs, rhs, "underflow").ok(),
1119 lhs.checked_sub(rhs)
1120 );
1121 }
1122 }
1123 }
1124
1125 #[test]
1126 fn checked_atomic_accounting_reports_overflow_and_underflow_without_saturation() {
1127 let add_counter = AtomicU64::new(u64::MAX - 1);
1128 checked_atomic_add_u64(&add_counter, 1, |_, _| unreachable!("one fits"))
1129 .expect("Fix: atomic add should accept exact non-overflow");
1130 assert_eq!(add_counter.load(Ordering::Relaxed), u64::MAX);
1131 let add_error = checked_atomic_add_u64(&add_counter, 1, |current, attempted| {
1132 crate::BackendError::InvalidProgram {
1133 fix: format!("Fix: overflow {current} {attempted}"),
1134 }
1135 })
1136 .expect_err("overflowing atomic add should fail");
1137 assert!(add_error.to_string().contains("overflow"));
1138 assert_eq!(add_counter.load(Ordering::Relaxed), u64::MAX);
1139
1140 let sub_counter = AtomicU64::new(1);
1141 checked_atomic_sub_u64(&sub_counter, 1, |_, _| unreachable!("one fits"))
1142 .expect("Fix: atomic sub should accept exact subtraction");
1143 assert_eq!(sub_counter.load(Ordering::Acquire), 0);
1144 let sub_error = checked_atomic_sub_u64(&sub_counter, 1, |current, attempted| {
1145 crate::BackendError::InvalidProgram {
1146 fix: format!("Fix: underflow {current} {attempted}"),
1147 }
1148 })
1149 .expect_err("underflowing atomic sub should fail");
1150 assert!(sub_error.to_string().contains("underflow"));
1151 assert_eq!(sub_counter.load(Ordering::Acquire), 0);
1152
1153 let usize_add_counter = AtomicUsize::new(usize::MAX - 1);
1154 checked_atomic_add_usize(&usize_add_counter, 1, |_, _| unreachable!("one fits"))
1155 .expect("Fix: usize atomic add should accept exact non-overflow");
1156 assert_eq!(usize_add_counter.load(Ordering::Acquire), usize::MAX);
1157 let usize_add_error =
1158 checked_atomic_add_usize(&usize_add_counter, 1, |current, attempted| {
1159 crate::BackendError::InvalidProgram {
1160 fix: format!("Fix: usize overflow {current} {attempted}"),
1161 }
1162 })
1163 .expect_err("overflowing usize atomic add should fail");
1164 assert!(usize_add_error.to_string().contains("usize overflow"));
1165 assert_eq!(usize_add_counter.load(Ordering::Acquire), usize::MAX);
1166
1167 let usize_counter = AtomicUsize::new(0);
1168 let usize_error = checked_atomic_sub_usize(&usize_counter, 1, |current, attempted| {
1169 crate::BackendError::InvalidProgram {
1170 fix: format!("Fix: usize underflow {current} {attempted}"),
1171 }
1172 })
1173 .expect_err("underflowing usize atomic sub should fail");
1174 assert!(usize_error.to_string().contains("usize underflow"));
1175 assert_eq!(usize_counter.load(Ordering::Acquire), 0);
1176 }
1177
1178 #[test]
1179 fn ordered_atomic_helpers_preserve_domain_errors() {
1180 let add_counter = AtomicU64::new(40);
1181 checked_atomic_add_u64_with_order(
1182 &add_counter,
1183 2,
1184 Ordering::Acquire,
1185 Ordering::AcqRel,
1186 Ordering::Acquire,
1187 |_, _| "overflow",
1188 )
1189 .expect("Fix: reject adds that would overflow; use checked accounting API on hostile sizes - ordered atomic add should accept non-overflow");
1190 assert_eq!(add_counter.load(Ordering::Acquire), 42);
1191
1192 let sub_counter = AtomicU64::new(42);
1193 checked_atomic_sub_u64_with_order(
1194 &sub_counter,
1195 2,
1196 Ordering::Acquire,
1197 Ordering::AcqRel,
1198 Ordering::Acquire,
1199 |_, _| "underflow",
1200 )
1201 .expect("Fix: reject subs that would underflow; use checked accounting API on hostile sizes - ordered atomic sub should accept non-underflow");
1202 assert_eq!(sub_counter.load(Ordering::Acquire), 40);
1203
1204 let usize_counter = AtomicUsize::new(10);
1205 checked_atomic_add_usize_with_order(
1206 &usize_counter,
1207 5,
1208 Ordering::Acquire,
1209 Ordering::AcqRel,
1210 Ordering::Acquire,
1211 |_, _| "usize overflow",
1212 )
1213 .expect("Fix: reject usize atomics that overflow/underflow; return Err from guarded helpers - ordered usize atomic add should accept non-overflow");
1214 assert_eq!(usize_counter.load(Ordering::Acquire), 15);
1215 checked_atomic_sub_usize_with_order(
1216 &usize_counter,
1217 3,
1218 Ordering::Acquire,
1219 Ordering::AcqRel,
1220 Ordering::Acquire,
1221 |_, _| "usize underflow",
1222 )
1223 .expect("Fix: reject usize atomics that overflow/underflow; return Err from guarded helpers - ordered usize atomic sub should accept non-underflow");
1224 assert_eq!(usize_counter.load(Ordering::Acquire), 12);
1225 }
1226
1227 #[test]
1228 fn guarded_atomic_add_helpers_validate_next_value_before_publish() {
1229 let u64_counter = AtomicU64::new(8);
1230 let u64_error = checked_atomic_add_u64_guarded_with_order(
1231 &u64_counter,
1232 5,
1233 Ordering::Acquire,
1234 Ordering::AcqRel,
1235 Ordering::Acquire,
1236 |_, _| "overflow",
1237 |next| {
1238 if next <= 12 {
1239 Ok(())
1240 } else {
1241 Err("budget")
1242 }
1243 },
1244 )
1245 .expect_err("guarded u64 add should reject over-budget next value");
1246 assert_eq!(u64_error, "budget");
1247 assert_eq!(u64_counter.load(Ordering::Acquire), 8);
1248
1249 checked_atomic_add_u64_guarded_with_order(
1250 &u64_counter,
1251 4,
1252 Ordering::Acquire,
1253 Ordering::AcqRel,
1254 Ordering::Acquire,
1255 |_, _| "overflow",
1256 |next| if next <= 12 { Ok(()) } else { Err("budget") },
1257 )
1258 .expect("Fix: reject guarded adds that overflow; surface Err to caller instead of panicking - guarded u64 add should publish accepted next value");
1259 assert_eq!(u64_counter.load(Ordering::Acquire), 12);
1260
1261 let usize_counter = AtomicUsize::new(3);
1262 let usize_error = checked_atomic_add_usize_guarded_with_order(
1263 &usize_counter,
1264 2,
1265 Ordering::Acquire,
1266 Ordering::AcqRel,
1267 Ordering::Acquire,
1268 |_, _| "overflow",
1269 |next| {
1270 if next < 5 {
1271 Ok(())
1272 } else {
1273 Err("usize budget")
1274 }
1275 },
1276 )
1277 .expect_err("guarded usize add should reject over-budget next value");
1278 assert_eq!(usize_error, "usize budget");
1279 assert_eq!(usize_counter.load(Ordering::Acquire), 3);
1280 }
1281
1282 #[test]
1283 fn pinning_atomic_increment_helpers_never_wrap() {
1284 let u64_counter = AtomicU64::new(u64::MAX - 1);
1285 assert!(pinning_atomic_increment_u64(
1286 &u64_counter,
1287 Ordering::Relaxed,
1288 Ordering::Relaxed,
1289 || unreachable!("first increment should fit")
1290 ));
1291 assert_eq!(u64_counter.load(Ordering::Relaxed), u64::MAX);
1292 let mut u64_pinned = false;
1293 assert!(!pinning_atomic_increment_u64(
1294 &u64_counter,
1295 Ordering::Relaxed,
1296 Ordering::Relaxed,
1297 || u64_pinned = true
1298 ));
1299 assert!(u64_pinned);
1300 assert_eq!(u64_counter.load(Ordering::Relaxed), u64::MAX);
1301
1302 let u32_counter = AtomicU32::new(u32::MAX - 1);
1303 assert!(pinning_atomic_increment_u32(
1304 &u32_counter,
1305 Ordering::Relaxed,
1306 Ordering::Relaxed,
1307 || unreachable!("first increment should fit")
1308 ));
1309 assert_eq!(u32_counter.load(Ordering::Relaxed), u32::MAX);
1310 let mut u32_pinned = false;
1311 assert!(!pinning_atomic_increment_u32(
1312 &u32_counter,
1313 Ordering::Relaxed,
1314 Ordering::Relaxed,
1315 || u32_pinned = true
1316 ));
1317 assert!(u32_pinned);
1318 assert_eq!(u32_counter.load(Ordering::Relaxed), u32::MAX);
1319
1320 let mut scalar_counter = u64::MAX - 1;
1321 assert!(pinning_increment_u64(&mut scalar_counter, || {
1322 unreachable!("first scalar increment should fit")
1323 }));
1324 assert_eq!(scalar_counter, u64::MAX);
1325 let mut scalar_pinned = false;
1326 assert!(!pinning_increment_u64(&mut scalar_counter, || {
1327 scalar_pinned = true;
1328 }));
1329 assert!(scalar_pinned);
1330 assert_eq!(scalar_counter, u64::MAX);
1331 }
1332
1333 #[test]
1334 fn atomic_max_helper_raises_without_lowering() {
1335 let counter = AtomicU64::new(10);
1336 assert_eq!(atomic_max_u64(&counter, 42, Ordering::Relaxed), 10);
1337 assert_eq!(counter.load(Ordering::Relaxed), 42);
1338 assert_eq!(atomic_max_u64(&counter, 7, Ordering::Relaxed), 42);
1339 assert_eq!(counter.load(Ordering::Relaxed), 42);
1340 }
1341
1342 #[test]
1343 fn rebasing_atomic_next_returns_observed_and_rebases_on_overflow() {
1344 let counter = AtomicU64::new(7);
1345 let mut rebase_count = 0;
1346 assert_eq!(
1347 rebasing_atomic_next_u64(
1348 &counter,
1349 1,
1350 Ordering::Acquire,
1351 Ordering::AcqRel,
1352 Ordering::Acquire,
1353 |_, _| rebase_count += 1,
1354 ),
1355 7
1356 );
1357 assert_eq!(counter.load(Ordering::Acquire), 8);
1358 assert_eq!(rebase_count, 0);
1359
1360 counter.store(u64::MAX, Ordering::Release);
1361 assert_eq!(
1362 rebasing_atomic_next_u64(
1363 &counter,
1364 1,
1365 Ordering::Acquire,
1366 Ordering::AcqRel,
1367 Ordering::Acquire,
1368 |observed, rebase_to| {
1369 assert_eq!(observed, u64::MAX);
1370 assert_eq!(rebase_to, 1);
1371 rebase_count += 1;
1372 },
1373 ),
1374 u64::MAX
1375 );
1376 assert_eq!(counter.load(Ordering::Acquire), 1);
1377 assert_eq!(rebase_count, 1);
1378 }
1379
1380 #[test]
1381 fn checked_atomic_next_returns_observed_and_rejects_wraparound() {
1382 let counter = AtomicU64::new(41);
1383 assert_eq!(
1384 checked_atomic_next_u64_with_order(
1385 &counter,
1386 Ordering::Acquire,
1387 Ordering::AcqRel,
1388 Ordering::Acquire,
1389 |_| "overflow",
1390 )
1391 .expect("Fix: allocation of next atomic value must not overflow; return None/Err on hostile input - checked atomic next should allocate non-overflowing value"),
1392 41
1393 );
1394 assert_eq!(counter.load(Ordering::Acquire), 42);
1395
1396 counter.store(u64::MAX, Ordering::Release);
1397 let error = checked_atomic_next_u64_with_order(
1398 &counter,
1399 Ordering::Acquire,
1400 Ordering::AcqRel,
1401 Ordering::Acquire,
1402 |observed| {
1403 assert_eq!(observed, u64::MAX);
1404 "overflow"
1405 },
1406 )
1407 .expect_err("checked atomic next should reject u64 wraparound");
1408 assert_eq!(error, "overflow");
1409 assert_eq!(counter.load(Ordering::Acquire), u64::MAX);
1410 }
1411
1412 #[test]
1413 fn repair_atomic_sub_usize_repairs_underflow_to_zero_once() {
1414 let counter = AtomicUsize::new(10);
1415 let mut repairs = 0;
1416 repair_atomic_sub_usize_with_order(
1417 &counter,
1418 4,
1419 Ordering::Acquire,
1420 Ordering::AcqRel,
1421 Ordering::Acquire,
1422 |_, _| repairs += 1,
1423 );
1424 assert_eq!(counter.load(Ordering::Acquire), 6);
1425 assert_eq!(repairs, 0);
1426
1427 repair_atomic_sub_usize_with_order(
1428 &counter,
1429 99,
1430 Ordering::Acquire,
1431 Ordering::AcqRel,
1432 Ordering::Acquire,
1433 |observed, attempted| {
1434 assert_eq!(observed, 6);
1435 assert_eq!(attempted, 99);
1436 repairs += 1;
1437 },
1438 );
1439 assert_eq!(counter.load(Ordering::Acquire), 0);
1440 assert_eq!(repairs, 1);
1441 }
1442}