From 9b45b967e2e8470111bb0d9bdf703e5dd30a6cdb Mon Sep 17 00:00:00 2001 From: soruh Date: Fri, 31 Jul 2026 03:35:20 +0200 Subject: [PATCH] Check capacity arithmetic --- src/lib.rs | 6 +++++- src/tests.rs | 32 ++++++++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1f0746d..9a283b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -129,7 +129,11 @@ impl TypeErasedVec { /// logical capacity. #[must_use] pub const fn capacity_bytes(&self) -> usize { - self.parts.cap * self.layout.size() + let capacity_bytes = self.parts.cap.checked_mul(self.layout.size()); + + // SAFETY: A valid Vec allocation cannot exceed usize::MAX bytes. + // ZST vectors multiply their logical capacity by zero. + unsafe { capacity_bytes.unwrap_unchecked() } } /// Access the erased capacity with a temporarily fixed type. diff --git a/src/tests.rs b/src/tests.rs index 537210b..175778b 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -16,11 +16,29 @@ impl DropTracker { } impl Drop for DropTracker { + #[expect( + clippy::panic, + reason = "overflow means the test's drop-count assertion can no longer be trusted" + )] fn drop(&mut self) { - self.counter.set(self.counter.get() + 1); + let Some(next_count) = self.counter.get().checked_add(1) else { + panic!("drop counter overflow"); + }; + self.counter.set(next_count); } } +#[expect( + clippy::panic, + reason = "overflow means the test's capacity expectation is invalid" +)] +fn checked_product(left: usize, right: usize) -> usize { + let Some(product) = left.checked_mul(right) else { + panic!("test multiplication overflow"); + }; + product +} + #[track_caller] fn assert_erased_state(erased: &TypeErasedVec, expected_len: usize, expected_layout: Layout) { assert_eq!(erased.length(), expected_len, "Length mismatch"); @@ -28,7 +46,7 @@ fn assert_erased_state(erased: &TypeErasedVec, expected_len: usize, expected_lay assert_eq!(erased.layout(), expected_layout, "Layout mismatch"); assert_eq!( erased.capacity_bytes(), - erased.capacity() * expected_layout.size(), + checked_product(erased.capacity(), expected_layout.size()), "Capacity bytes mismatch" ); } @@ -92,7 +110,10 @@ macro_rules! assert_thread_safe_erased_vec_api { assert_eq!(erased.length(), 1); assert!(!erased.is_empty()); assert!(erased.capacity() >= 1); - assert_eq!(erased.capacity_bytes(), erased.capacity() * 4); + assert_eq!( + erased.capacity_bytes(), + checked_product(erased.capacity(), 4) + ); erased.reserve(8); assert!(erased.capacity() >= 9); @@ -512,7 +533,10 @@ fn test_erased_layout_and_capacity_bytes() { let erased = TypeErasedVec::new(vec); assert_eq!(erased.layout(), Layout::new::()); - assert_eq!(erased.capacity_bytes(), erased.capacity() * 8); + assert_eq!( + erased.capacity_bytes(), + checked_product(erased.capacity(), 8) + ); } #[test]