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