From fcedda49fc35e42c3459ed22043618a4f2c5e48e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 15 Sep 2026 19:00:47 +0200 Subject: [PATCH 1/2] gecko: Prevent ZSTs with ThinVec when in gecko-ffi mode. This prevents bugs like https://bugzilla.mozilla.org/show_bug.cgi?id=2067654 Fixes https://bugzilla.mozilla.org/show_bug.cgi?id=2072302 While it's possible to make this a compile-time panic rather than a runtime panic, doing so is a breaking change, and makes testing a PITA, since the only feature that I can think of that allows to test that something shouldn't compile are doctests, and doctests don't work with the gecko-ffi mode :/ --- src/lib.rs | 80 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bc001a4..a8daf29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -408,6 +408,10 @@ const fn padding() -> usize { let alloc_align = alloc_align::(); let header_size = mem::size_of::
(); if cfg!(feature = "gecko-ffi") { + assert!( + mem::size_of::() != 0, + "ThinVec cannot bridge to nsTArray when T is zero-sized" + ); assert!( header_size >= alloc_align, "nsTArray does not handle alignment above the header size correctly", @@ -576,12 +580,13 @@ impl ThinVec { /// assert_eq!(vec.len(), 11); /// assert!(vec.capacity() >= 11); /// + /// # #[cfg(not(feature = "gecko-ffi"))] { /// // A vector of a zero-sized type will always over-allocate, since no /// // space is needed to store the actual elements. + /// // Note this is only true **without** the gecko-ffi feature! /// let vec_units = ThinVec::<()>::with_capacity(10); - /// - /// // Only true **without** the gecko-ffi feature! - /// // assert_eq!(vec_units.capacity(), usize::MAX); + /// assert_eq!(vec_units.capacity(), usize::MAX); + /// # } /// ``` pub fn with_capacity(cap: usize) -> Self { // `padding` contains ~static assertions against types that are @@ -3115,7 +3120,10 @@ mod tests { } #[test] - #[cfg_attr(feature = "gecko-ffi", should_panic)] + #[cfg_attr( + feature = "gecko-ffi", + should_panic = "nsTArray does not handle alignment above the header size correctly" + )] fn test_overaligned_type_is_rejected_for_gecko_ffi_mode() { #[repr(align(16))] #[allow(unused)] @@ -3173,6 +3181,10 @@ mod tests { } #[test] + #[cfg_attr( + feature = "gecko-ffi", + should_panic = "ThinVec cannot bridge to nsTArray when T is zero-sized" + )] fn test_drain_items_zero_sized() { let mut vec = thin_vec![(), (), ()]; let mut vec2 = thin_vec![]; @@ -3203,13 +3215,24 @@ mod tests { let mut v: ThinVec<_> = (1..6).map(|x| x.to_string()).collect(); for _ in v.drain(1..4).rev() {} assert_eq!(v, &[1.to_string(), 5.to_string()]); + } + #[test] + #[cfg_attr( + feature = "gecko-ffi", + should_panic = "ThinVec cannot bridge to nsTArray when T is zero-sized" + )] + fn test_drain_range_zst() { let mut v: ThinVec<_> = thin_vec![(); 5]; for _ in v.drain(1..4).rev() {} assert_eq!(v, &[(), ()]); } #[test] + #[cfg_attr( + feature = "gecko-ffi", + should_panic = "ThinVec cannot bridge to nsTArray when T is zero-sized" + )] fn test_drain_max_vec_size() { let mut v = ThinVec::<()>::with_capacity(MAX_CAP); unsafe { @@ -3567,16 +3590,6 @@ mod std_tests { v.extend(w.clone()); // specializes to `append` assert!(v.iter().eq(w.iter().chain(w.iter()))); - // Zero sized types - #[derive(PartialEq, Debug)] - struct Foo; - - let mut a = ThinVec::new(); - let b = thin_vec![Foo, Foo]; - - a.extend(b); - assert_eq!(a, &[Foo, Foo]); - // Double drop let mut count_x = 0; { @@ -3590,6 +3603,22 @@ mod std_tests { assert_eq!(count_x, 1); } + #[test] + #[cfg_attr( + feature = "gecko-ffi", + should_panic = "ThinVec cannot bridge to nsTArray when T is zero-sized" + )] + fn test_extend_zst() { + #[derive(PartialEq, Debug)] + struct Foo; + + let mut a = ThinVec::new(); + let b = thin_vec![Foo, Foo]; + + a.extend(b); + assert_eq!(a, &[Foo, Foo]); + } + /* TODO: implement extend for Iter<&Copy> #[test] fn test_extend_ref() { @@ -3779,6 +3808,10 @@ mod std_tests { } #[test] + #[cfg_attr( + feature = "gecko-ffi", + should_panic = "ThinVec cannot bridge to nsTArray when T is zero-sized" + )] fn zero_sized_values() { let mut v = ThinVec::new(); assert_eq!(v.len(), 0); @@ -3956,6 +3989,10 @@ mod std_tests { } #[test] + #[cfg_attr( + feature = "gecko-ffi", + should_panic = "ThinVec cannot bridge to nsTArray when T is zero-sized" + )] fn test_move_items_zero_sized() { let vec = thin_vec![(), (), ()]; let mut vec2 = thin_vec![]; @@ -3988,6 +4025,10 @@ mod std_tests { } #[test] + #[cfg_attr( + feature = "gecko-ffi", + should_panic = "ThinVec cannot bridge to nsTArray when T is zero-sized" + )] fn test_drain_items_zero_sized() { let mut vec = thin_vec![(), (), ()]; let mut vec2 = thin_vec![]; @@ -4018,7 +4059,14 @@ mod std_tests { let mut v: ThinVec<_> = (1..6).map(|x| x.to_string()).collect(); for _ in v.drain(1..4).rev() {} assert_eq!(v, &[1.to_string(), 5.to_string()]); + } + #[test] + #[cfg_attr( + feature = "gecko-ffi", + should_panic = "ThinVec cannot bridge to nsTArray when T is zero-sized" + )] + fn test_drain_range_zst() { let mut v: ThinVec<_> = thin_vec![(); 5]; for _ in v.drain(1..4).rev() {} assert_eq!(v, &[(), ()]); @@ -4111,6 +4159,10 @@ mod std_tests { } #[test] + #[cfg_attr( + feature = "gecko-ffi", + should_panic = "ThinVec cannot bridge to nsTArray when T is zero-sized" + )] fn test_splice_items_zero_sized() { let mut vec = thin_vec![(), (), ()]; let vec2 = thin_vec![]; From c2776d8149a21153f0da1d7ff7171e071660b7fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 15 Sep 2026 19:43:30 +0200 Subject: [PATCH 2/2] chore: Fix a few CI warnings. --- Cargo.toml | 2 -- src/lib.rs | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6434f01..61b47d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,6 @@ authors = ["Aria Beingessner "] edition = "2024" rust-version = "1.85" description = "A Vec that takes up less space on the stack." -readme = "README.md" -homepage = "https://github.com/mozilla/thin-vec" repository = "https://github.com/mozilla/thin-vec" license = "MIT OR Apache-2.0" diff --git a/src/lib.rs b/src/lib.rs index a8daf29..15f230c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -229,7 +229,7 @@ mod impl_details { pub type SizeType = u32; - pub const MAX_CAP: usize = i32::max_value() as usize; + pub const MAX_CAP: usize = i32::MAX as usize; // See kAutoTArrayHeaderOffset pub const AUTO_ARRAY_HEADER_OFFSET: usize = 8; @@ -365,7 +365,7 @@ impl Header { static EMPTY_HEADER: Header = Header { _len: 0, _cap: 0 }; #[cfg(all(feature = "gecko-ffi", not(test), not(miri)))] -extern "C" { +unsafe extern "C" { #[link_name = "sEmptyTArrayHeader"] static EMPTY_HEADER: Header; }