mirror of
				https://github.com/rust-lang/rust.git
				synced 2025-10-30 20:44:34 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			263 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			263 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use core::fmt::Debug;
 | |
| use core::mem::size_of;
 | |
| use std::boxed::ThinBox;
 | |
| 
 | |
| #[test]
 | |
| fn want_niche_optimization() {
 | |
|     fn uses_niche<T: ?Sized>() -> bool {
 | |
|         size_of::<*const ()>() == size_of::<Option<ThinBox<T>>>()
 | |
|     }
 | |
| 
 | |
|     trait Tr {}
 | |
|     assert!(uses_niche::<dyn Tr>());
 | |
|     assert!(uses_niche::<[i32]>());
 | |
|     assert!(uses_niche::<i32>());
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn want_thin() {
 | |
|     fn is_thin<T: ?Sized>() -> bool {
 | |
|         size_of::<*const ()>() == size_of::<ThinBox<T>>()
 | |
|     }
 | |
| 
 | |
|     trait Tr {}
 | |
|     assert!(is_thin::<dyn Tr>());
 | |
|     assert!(is_thin::<[i32]>());
 | |
|     assert!(is_thin::<i32>());
 | |
| }
 | |
| 
 | |
| #[allow(dead_code)]
 | |
| fn assert_covariance() {
 | |
|     fn thin_box<'new>(b: ThinBox<[&'static str]>) -> ThinBox<[&'new str]> {
 | |
|         b
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[track_caller]
 | |
| fn verify_aligned<T>(ptr: *const T) {
 | |
|     // Use `black_box` to attempt to obscure the fact that we're calling this
 | |
|     // function on pointers that come from box/references, which the compiler
 | |
|     // would otherwise realize is impossible (because it would mean we've
 | |
|     // already executed UB).
 | |
|     //
 | |
|     // That is, we'd *like* it to be possible for the asserts in this function
 | |
|     // to detect brokenness in the ThinBox impl.
 | |
|     //
 | |
|     // It would probably be better if we instead had these as debug_asserts
 | |
|     // inside `ThinBox`, prior to the point where we do the UB. Anyway, in
 | |
|     // practice these checks are mostly just smoke-detectors for an extremely
 | |
|     // broken `ThinBox` impl, since it's an extremely subtle piece of code.
 | |
|     let ptr = core::hint::black_box(ptr);
 | |
|     assert!(
 | |
|         ptr.is_aligned() && !ptr.is_null(),
 | |
|         "misaligned ThinBox data; valid pointers to `{ty}` should be aligned to {align}: {ptr:p}",
 | |
|         ty = core::any::type_name::<T>(),
 | |
|         align = core::mem::align_of::<T>(),
 | |
|     );
 | |
| }
 | |
| 
 | |
| #[track_caller]
 | |
| fn check_thin_sized<T: Debug + PartialEq + Clone>(make: impl FnOnce() -> T) {
 | |
|     let value = make();
 | |
|     let boxed = ThinBox::new(value.clone());
 | |
|     let val = &*boxed;
 | |
|     verify_aligned(val as *const T);
 | |
|     assert_eq!(val, &value);
 | |
| }
 | |
| 
 | |
| #[track_caller]
 | |
| fn check_thin_dyn<T: Debug + PartialEq + Clone>(make: impl FnOnce() -> T) {
 | |
|     let value = make();
 | |
|     let wanted_debug = format!("{value:?}");
 | |
|     let boxed: ThinBox<dyn Debug> = ThinBox::new_unsize(value.clone());
 | |
|     let val = &*boxed;
 | |
|     // wide reference -> wide pointer -> thin pointer
 | |
|     verify_aligned(val as *const dyn Debug as *const T);
 | |
|     let got_debug = format!("{val:?}");
 | |
|     assert_eq!(wanted_debug, got_debug);
 | |
| }
 | |
| 
 | |
| macro_rules! define_test {
 | |
|     (
 | |
|         @test_name: $testname:ident;
 | |
| 
 | |
|         $(#[$m:meta])*
 | |
|         struct $Type:ident($inner:ty);
 | |
| 
 | |
|         $($test_stmts:tt)*
 | |
|     ) => {
 | |
|         #[test]
 | |
|         fn $testname() {
 | |
|             use core::sync::atomic::{AtomicIsize, Ordering};
 | |
|             // Define the type, and implement new/clone/drop in such a way that
 | |
|             // the number of live instances will be counted.
 | |
|             $(#[$m])*
 | |
|             #[derive(Debug, PartialEq)]
 | |
|             struct $Type {
 | |
|                 _priv: $inner,
 | |
|             }
 | |
| 
 | |
|             impl Clone for $Type {
 | |
|                 fn clone(&self) -> Self {
 | |
|                     verify_aligned(self);
 | |
|                     Self::new(self._priv.clone())
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             impl Drop for $Type {
 | |
|                 fn drop(&mut self) {
 | |
|                     verify_aligned(self);
 | |
|                     Self::modify_live(-1);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             impl $Type {
 | |
|                 fn new(i: $inner) -> Self {
 | |
|                     Self::modify_live(1);
 | |
|                     Self { _priv: i }
 | |
|                 }
 | |
| 
 | |
|                 fn modify_live(n: isize) -> isize {
 | |
|                     static COUNTER: AtomicIsize = AtomicIsize::new(0);
 | |
|                     COUNTER.fetch_add(n, Ordering::Relaxed) + n
 | |
|                 }
 | |
| 
 | |
|                 fn live_objects() -> isize {
 | |
|                     Self::modify_live(0)
 | |
|                 }
 | |
|             }
 | |
|             // Run the test statements
 | |
|             let _: () = { $($test_stmts)* };
 | |
|             // Check that we didn't leak anything, or call drop too many times.
 | |
|             assert_eq!(
 | |
|                 $Type::live_objects(), 0,
 | |
|                 "Wrong number of drops of {}, `initializations - drops` should be 0.",
 | |
|                 stringify!($Type),
 | |
|             );
 | |
|         }
 | |
|     };
 | |
| }
 | |
| 
 | |
| define_test! {
 | |
|     @test_name: align1zst;
 | |
|     struct Align1Zst(());
 | |
| 
 | |
|     check_thin_sized(|| Align1Zst::new(()));
 | |
|     check_thin_dyn(|| Align1Zst::new(()));
 | |
| }
 | |
| 
 | |
| define_test! {
 | |
|     @test_name: align1small;
 | |
|     struct Align1Small(u8);
 | |
| 
 | |
|     check_thin_sized(|| Align1Small::new(50));
 | |
|     check_thin_dyn(|| Align1Small::new(50));
 | |
| }
 | |
| 
 | |
| define_test! {
 | |
|     @test_name: align1_size_not_pow2;
 | |
|     struct Align64NotPow2Size([u8; 79]);
 | |
| 
 | |
|     check_thin_sized(|| Align64NotPow2Size::new([100; 79]));
 | |
|     check_thin_dyn(|| Align64NotPow2Size::new([100; 79]));
 | |
| }
 | |
| 
 | |
| define_test! {
 | |
|     @test_name: align1big;
 | |
|     struct Align1Big([u8; 256]);
 | |
| 
 | |
|     check_thin_sized(|| Align1Big::new([5u8; 256]));
 | |
|     check_thin_dyn(|| Align1Big::new([5u8; 256]));
 | |
| }
 | |
| 
 | |
| // Note: `#[repr(align(2))]` is worth testing because
 | |
| // - can have pointers which are misaligned, unlike align(1)
 | |
| // - is still expected to have an alignment less than the alignment of a vtable.
 | |
| define_test! {
 | |
|     @test_name: align2zst;
 | |
|     #[repr(align(2))]
 | |
|     struct Align2Zst(());
 | |
| 
 | |
|     check_thin_sized(|| Align2Zst::new(()));
 | |
|     check_thin_dyn(|| Align2Zst::new(()));
 | |
| }
 | |
| 
 | |
| define_test! {
 | |
|     @test_name: align2small;
 | |
|     #[repr(align(2))]
 | |
|     struct Align2Small(u8);
 | |
| 
 | |
|     check_thin_sized(|| Align2Small::new(60));
 | |
|     check_thin_dyn(|| Align2Small::new(60));
 | |
| }
 | |
| 
 | |
| define_test! {
 | |
|     @test_name: align2full;
 | |
|     #[repr(align(2))]
 | |
|     struct Align2Full([u8; 2]);
 | |
|     check_thin_sized(|| Align2Full::new([3u8; 2]));
 | |
|     check_thin_dyn(|| Align2Full::new([3u8; 2]));
 | |
| }
 | |
| 
 | |
| define_test! {
 | |
|     @test_name: align2_size_not_pow2;
 | |
|     #[repr(align(2))]
 | |
|     struct Align2NotPower2Size([u8; 6]);
 | |
| 
 | |
|     check_thin_sized(|| Align2NotPower2Size::new([3; 6]));
 | |
|     check_thin_dyn(|| Align2NotPower2Size::new([3; 6]));
 | |
| }
 | |
| 
 | |
| define_test! {
 | |
|     @test_name: align2big;
 | |
|     #[repr(align(2))]
 | |
|     struct Align2Big([u8; 256]);
 | |
| 
 | |
|     check_thin_sized(|| Align2Big::new([5u8; 256]));
 | |
|     check_thin_dyn(|| Align2Big::new([5u8; 256]));
 | |
| }
 | |
| 
 | |
| define_test! {
 | |
|     @test_name: align64zst;
 | |
|     #[repr(align(64))]
 | |
|     struct Align64Zst(());
 | |
| 
 | |
|     check_thin_sized(|| Align64Zst::new(()));
 | |
|     check_thin_dyn(|| Align64Zst::new(()));
 | |
| }
 | |
| 
 | |
| define_test! {
 | |
|     @test_name: align64small;
 | |
|     #[repr(align(64))]
 | |
|     struct Align64Small(u8);
 | |
| 
 | |
|     check_thin_sized(|| Align64Small::new(50));
 | |
|     check_thin_dyn(|| Align64Small::new(50));
 | |
| }
 | |
| 
 | |
| define_test! {
 | |
|     @test_name: align64med;
 | |
|     #[repr(align(64))]
 | |
|     struct Align64Med([u8; 64]);
 | |
|     check_thin_sized(|| Align64Med::new([10; 64]));
 | |
|     check_thin_dyn(|| Align64Med::new([10; 64]));
 | |
| }
 | |
| 
 | |
| define_test! {
 | |
|     @test_name: align64_size_not_pow2;
 | |
|     #[repr(align(64))]
 | |
|     struct Align64NotPow2Size([u8; 192]);
 | |
| 
 | |
|     check_thin_sized(|| Align64NotPow2Size::new([10; 192]));
 | |
|     check_thin_dyn(|| Align64NotPow2Size::new([10; 192]));
 | |
| }
 | |
| 
 | |
| define_test! {
 | |
|     @test_name: align64big;
 | |
|     #[repr(align(64))]
 | |
|     struct Align64Big([u8; 256]);
 | |
| 
 | |
|     check_thin_sized(|| Align64Big::new([10; 256]));
 | |
|     check_thin_dyn(|| Align64Big::new([10; 256]));
 | |
| }
 | 
