1+ use core:: fmt:: Debug ;
12use core:: mem:: size_of;
23use std:: boxed:: ThinBox ;
34
@@ -24,3 +25,231 @@ fn want_thin() {
2425 assert ! ( is_thin:: <[ i32 ] >( ) ) ;
2526 assert ! ( is_thin:: <i32 >( ) ) ;
2627}
28+
29+ #[ track_caller]
30+ fn verify_aligned < T > ( ptr : * const T ) {
31+ // Use `black_box` to attempt to obscure the fact that we're calling this
32+ // function on pointers that come from box/references, which the compiler
33+ // would otherwise realize is impossible (because it would mean we've
34+ // already executed UB).
35+ //
36+ // That is, we'd *like* it to be possible for the asserts in this function
37+ // to detect brokenness in the ThinBox impl.
38+ //
39+ // It would probably be better if we instead had these as debug_asserts
40+ // inside `ThinBox`, prior to the point where we do the UB. Anyway, in
41+ // practice these checks are mostly just smoke-detectors for an extremely
42+ // broken `ThinBox` impl, since it's an extremely subtle piece of code.
43+ let ptr = core:: hint:: black_box ( ptr) ;
44+ let align = core:: mem:: align_of :: < T > ( ) ;
45+ assert ! (
46+ ( ptr. addr( ) & ( align - 1 ) ) == 0 && !ptr. is_null( ) ,
47+ "misaligned ThinBox data; valid pointers to `{}` should be aligned to {align}: {ptr:p}" ,
48+ core:: any:: type_name:: <T >( ) ,
49+ ) ;
50+ }
51+
52+ #[ track_caller]
53+ fn check_thin_sized < T : Debug + PartialEq + Clone > ( make : impl FnOnce ( ) -> T ) {
54+ let value = make ( ) ;
55+ let boxed = ThinBox :: new ( value. clone ( ) ) ;
56+ let val = & * boxed;
57+ verify_aligned ( val as * const T ) ;
58+ assert_eq ! ( val, & value) ;
59+ }
60+
61+ #[ track_caller]
62+ fn check_thin_dyn < T : Debug + PartialEq + Clone > ( make : impl FnOnce ( ) -> T ) {
63+ let value = make ( ) ;
64+ let wanted_debug = format ! ( "{value:?}" ) ;
65+ let boxed: ThinBox < dyn Debug > = ThinBox :: new_unsize ( value. clone ( ) ) ;
66+ let val = & * boxed;
67+ // wide reference -> wide pointer -> thin pointer
68+ verify_aligned ( val as * const dyn Debug as * const T ) ;
69+ let got_debug = format ! ( "{val:?}" ) ;
70+ assert_eq ! ( wanted_debug, got_debug) ;
71+ }
72+
73+ macro_rules! define_test {
74+ (
75+ @test_name: $testname: ident;
76+
77+ $( #[ $m: meta] ) *
78+ struct $Type: ident( $inner: ty) ;
79+
80+ $( $test_stmts: tt) *
81+ ) => {
82+ #[ test]
83+ fn $testname( ) {
84+ use core:: sync:: atomic:: { AtomicIsize , Ordering } ;
85+ // Define the type, and implement new/clone/drop in such a way that
86+ // the number of live instances will be counted.
87+ $( #[ $m] ) *
88+ #[ derive( Debug , PartialEq ) ]
89+ struct $Type {
90+ _priv: $inner,
91+ }
92+
93+ impl Clone for $Type {
94+ fn clone( & self ) -> Self {
95+ verify_aligned( self ) ;
96+ Self :: new( self . _priv. clone( ) )
97+ }
98+ }
99+
100+ impl Drop for $Type {
101+ fn drop( & mut self ) {
102+ verify_aligned( self ) ;
103+ Self :: modify_live( -1 ) ;
104+ }
105+ }
106+
107+ impl $Type {
108+ fn new( i: $inner) -> Self {
109+ Self :: modify_live( 1 ) ;
110+ Self { _priv: i }
111+ }
112+
113+ fn modify_live( n: isize ) -> isize {
114+ static COUNTER : AtomicIsize = AtomicIsize :: new( 0 ) ;
115+ COUNTER . fetch_add( n, Ordering :: Relaxed ) + n
116+ }
117+
118+ fn live_objects( ) -> isize {
119+ Self :: modify_live( 0 )
120+ }
121+ }
122+ // Run the test statements
123+ let _: ( ) = { $( $test_stmts) * } ;
124+ // Check that we didn't leak anything, or call drop too many times.
125+ assert_eq!(
126+ $Type:: live_objects( ) , 0 ,
127+ "Wrong number of drops of {}, `initializations - drops` should be 0." ,
128+ stringify!( $Type) ,
129+ ) ;
130+ }
131+ } ;
132+ }
133+
134+ define_test ! {
135+ @test_name: align1zst;
136+ struct Align1Zst ( ( ) ) ;
137+
138+ check_thin_sized( || Align1Zst :: new( ( ) ) ) ;
139+ check_thin_dyn( || Align1Zst :: new( ( ) ) ) ;
140+ }
141+
142+ define_test ! {
143+ @test_name: align1small;
144+ struct Align1Small ( u8 ) ;
145+
146+ check_thin_sized( || Align1Small :: new( 50 ) ) ;
147+ check_thin_dyn( || Align1Small :: new( 50 ) ) ;
148+ }
149+
150+ define_test ! {
151+ @test_name: align1_size_not_pow2;
152+ struct Align64NotPow2Size ( [ u8 ; 79 ] ) ;
153+
154+ check_thin_sized( || Align64NotPow2Size :: new( [ 100 ; 79 ] ) ) ;
155+ check_thin_dyn( || Align64NotPow2Size :: new( [ 100 ; 79 ] ) ) ;
156+ }
157+
158+ define_test ! {
159+ @test_name: align1big;
160+ struct Align1Big ( [ u8 ; 256 ] ) ;
161+
162+ check_thin_sized( || Align1Big :: new( [ 5u8 ; 256 ] ) ) ;
163+ check_thin_dyn( || Align1Big :: new( [ 5u8 ; 256 ] ) ) ;
164+ }
165+
166+ // Note: `#[repr(align(2))]` is worth testing because
167+ // - can have pointers which are misaligned, unlike align(1)
168+ // - is still expected to have an alignment less than the alignment of a vtable.
169+ define_test ! {
170+ @test_name: align2zst;
171+ #[ repr( align( 2 ) ) ]
172+ struct Align2Zst ( ( ) ) ;
173+
174+ check_thin_sized( || Align2Zst :: new( ( ) ) ) ;
175+ check_thin_dyn( || Align2Zst :: new( ( ) ) ) ;
176+ }
177+
178+ define_test ! {
179+ @test_name: align2small;
180+ #[ repr( align( 2 ) ) ]
181+ struct Align2Small ( u8 ) ;
182+
183+ check_thin_sized( || Align2Small :: new( 60 ) ) ;
184+ check_thin_dyn( || Align2Small :: new( 60 ) ) ;
185+ }
186+
187+ define_test ! {
188+ @test_name: align2full;
189+ #[ repr( align( 2 ) ) ]
190+ struct Align2Full ( [ u8 ; 2 ] ) ;
191+ check_thin_sized( || Align2Full :: new( [ 3u8 ; 2 ] ) ) ;
192+ check_thin_dyn( || Align2Full :: new( [ 3u8 ; 2 ] ) ) ;
193+ }
194+
195+ define_test ! {
196+ @test_name: align2_size_not_pow2;
197+ #[ repr( align( 2 ) ) ]
198+ struct Align2NotPower2Size ( [ u8 ; 6 ] ) ;
199+
200+ check_thin_sized( || Align2NotPower2Size :: new( [ 3 ; 6 ] ) ) ;
201+ check_thin_dyn( || Align2NotPower2Size :: new( [ 3 ; 6 ] ) ) ;
202+ }
203+
204+ define_test ! {
205+ @test_name: align2big;
206+ #[ repr( align( 2 ) ) ]
207+ struct Align2Big ( [ u8 ; 256 ] ) ;
208+
209+ check_thin_sized( || Align2Big :: new( [ 5u8 ; 256 ] ) ) ;
210+ check_thin_dyn( || Align2Big :: new( [ 5u8 ; 256 ] ) ) ;
211+ }
212+
213+ define_test ! {
214+ @test_name: align64zst;
215+ #[ repr( align( 64 ) ) ]
216+ struct Align64Zst ( ( ) ) ;
217+
218+ check_thin_sized( || Align64Zst :: new( ( ) ) ) ;
219+ check_thin_dyn( || Align64Zst :: new( ( ) ) ) ;
220+ }
221+
222+ define_test ! {
223+ @test_name: align64small;
224+ #[ repr( align( 64 ) ) ]
225+ struct Align64Small ( u8 ) ;
226+
227+ check_thin_sized( || Align64Small :: new( 50 ) ) ;
228+ check_thin_dyn( || Align64Small :: new( 50 ) ) ;
229+ }
230+
231+ define_test ! {
232+ @test_name: align64med;
233+ #[ repr( align( 64 ) ) ]
234+ struct Align64Med ( [ u8 ; 64 ] ) ;
235+ check_thin_sized( || Align64Med :: new( [ 10 ; 64 ] ) ) ;
236+ check_thin_dyn( || Align64Med :: new( [ 10 ; 64 ] ) ) ;
237+ }
238+
239+ define_test ! {
240+ @test_name: align64_size_not_pow2;
241+ #[ repr( align( 64 ) ) ]
242+ struct Align64NotPow2Size ( [ u8 ; 192 ] ) ;
243+
244+ check_thin_sized( || Align64NotPow2Size :: new( [ 10 ; 192 ] ) ) ;
245+ check_thin_dyn( || Align64NotPow2Size :: new( [ 10 ; 192 ] ) ) ;
246+ }
247+
248+ define_test ! {
249+ @test_name: align64big;
250+ #[ repr( align( 64 ) ) ]
251+ struct Align64Big ( [ u8 ; 256 ] ) ;
252+
253+ check_thin_sized( || Align64Big :: new( [ 10 ; 256 ] ) ) ;
254+ check_thin_dyn( || Align64Big :: new( [ 10 ; 256 ] ) ) ;
255+ }
0 commit comments