@@ -219,12 +219,21 @@ pub struct DstLayout {
219
219
/// `size_of::<T>()`. For DSTs, the size represents the size of the type
220
220
/// when the trailing slice field contains 0 elements.
221
221
/// - For all types, the alignment represents the alignment of the type.
222
+ // TODO: If we end up replacing this with separate size and alignment to
223
+ // make Kani happy, file an issue to eventually adopt the stdlib's
224
+ // `Alignment` type trick.
222
225
_base_layout : Layout ,
223
226
/// For sized types, `None`. For DSTs, the size of the element type of the
224
227
/// trailing slice.
225
228
_trailing_slice_elem_size : Option < usize > ,
226
229
}
227
230
231
+ #[ cfg_attr( test, derive( Copy , Clone , Debug ) ) ]
232
+ enum _CastType {
233
+ _Prefix,
234
+ _Suffix,
235
+ }
236
+
228
237
impl DstLayout {
229
238
/// Constructs a `DstLayout` which describes `T`.
230
239
///
@@ -251,6 +260,162 @@ impl DstLayout {
251
260
_trailing_slice_elem_size : Some ( mem:: size_of :: < T > ( ) ) ,
252
261
}
253
262
}
263
+
264
+ /// TODO
265
+ ///
266
+ /// The caller is responsible for ensuring that `addr + bytes_len` does not
267
+ /// overflow `usize`.
268
+ ///
269
+ /// # Panics
270
+ ///
271
+ /// If `addr + bytes_len` overflows `usize`, `validate_cast` may panic, or
272
+ /// it may return incorrect results. No guarantees are made about when
273
+ /// `validate_cast` will panic. The caller should not rely on
274
+ /// `validate_cast` panicking in any particular condition, even if
275
+ /// `debug_assertions` are enabled.
276
+ const fn _validate_cast (
277
+ & self ,
278
+ addr : usize ,
279
+ bytes_len : usize ,
280
+ cast_type : _CastType ,
281
+ ) -> Option < ( usize , usize ) > {
282
+ // `debug_assert!`, but with `#[allow(clippy::arithmetic_side_effects)]`.
283
+ macro_rules! __debug_assert {
284
+ ( $e: expr) => {
285
+ debug_assert!( {
286
+ #[ allow( clippy:: arithmetic_side_effects) ]
287
+ let e = $e;
288
+ e
289
+ } )
290
+ } ;
291
+ }
292
+
293
+ let base_size = self . _base_layout . size ( ) ;
294
+
295
+ // Precondition
296
+ __debug_assert ! ( addr. checked_add( bytes_len) . is_some( ) ) ;
297
+
298
+ // LEMMA 0: max_slice_bytes + base_size == bytes_len
299
+ //
300
+ // LEMMA 1: base_size <= bytes_len:
301
+ // - If `base_size > bytes_len`, `bytes_len.checked_sub(base_size)`
302
+ // returns `None`, and we return.
303
+ //
304
+ // TODO(#67): Once our MSRV is 1.65, use let-else:
305
+ // https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html#let-else-statements
306
+ let max_slice_bytes = if let Some ( max_byte_slice) = bytes_len. checked_sub ( base_size) {
307
+ max_byte_slice
308
+ } else {
309
+ return None ;
310
+ } ;
311
+
312
+ // Lemma 0
313
+ __debug_assert ! ( max_slice_bytes + base_size == bytes_len) ;
314
+
315
+ // Lemma 1
316
+ __debug_assert ! ( base_size <= bytes_len) ;
317
+
318
+ let ( elems, self_bytes) = if let Some ( elem_size) = self . _trailing_slice_elem_size {
319
+ // TODO(#67): Once our MSRV is 1.65, use let-else:
320
+ // https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html#let-else-statements
321
+ let elem_size = if let Some ( elem_size) = NonZeroUsize :: new ( elem_size) {
322
+ elem_size
323
+ } else {
324
+ panic ! ( "attempted to cast to slice type with zero-sized element" ) ;
325
+ } ;
326
+
327
+ // Guaranteed not to divide by 0 because `elem_size` is a
328
+ // `NonZeroUsize`.
329
+ #[ allow( clippy:: arithmetic_side_effects) ]
330
+ let elems = max_slice_bytes / elem_size. get ( ) ;
331
+
332
+ // NOTE: Another option for this step in the algorithm is to set
333
+ // `slice_bytes = elems * elem_size`. However, using multiplication
334
+ // causes Kani to choke. In practice, the compiler is likely to
335
+ // generate identical machine code in both cases. Note that this
336
+ // divide-then-mod approach is trivially optimizable into a single
337
+ // operation that computes both the quotient and the remainder.
338
+
339
+ // First line is guaranteed not to mod by 0 because `elem_size` is a
340
+ // `NonZeroUsize`. Second line is guaranteed not to underflow
341
+ // because `rem <= max_slice_bytes` thanks to the mod operation.
342
+ //
343
+ // LEMMA 2: slice_bytes <= max_slice_bytes
344
+ #[ allow( clippy:: arithmetic_side_effects) ]
345
+ let rem = max_slice_bytes % elem_size. get ( ) ;
346
+ #[ allow( clippy:: arithmetic_side_effects) ]
347
+ let slice_bytes = max_slice_bytes - rem;
348
+
349
+ // Lemma 2
350
+ __debug_assert ! ( slice_bytes <= max_slice_bytes) ;
351
+
352
+ // Guaranteed not to overflow:
353
+ // - max_slice_bytes + base_size == bytes_len (lemma 0)
354
+ // - slice_bytes <= max_slice_bytes (lemma 2)
355
+ // - slice_bytes + base_size <= bytes_len (substitution)
356
+ // - bytes_len <= usize::MAX (bytes_len: usize)
357
+ // - slice_bytes + base_size <= usize::MAX (substitution)
358
+ //
359
+ // LEMMA 3: self_bytes <= bytes_len: TODO
360
+ #[ allow( clippy:: arithmetic_side_effects) ]
361
+ let self_bytes = base_size + slice_bytes;
362
+
363
+ // Lemma 3
364
+ __debug_assert ! ( self_bytes <= bytes_len) ;
365
+
366
+ ( elems, self_bytes)
367
+ } else {
368
+ ( 0 , base_size)
369
+ } ;
370
+
371
+ // LEMMA 4: self_bytes <= bytes_len:
372
+ // - `if` branch returns `self_bytes`; lemma 3 guarantees `self_bytes <=
373
+ // bytes_len`
374
+ // - `else` branch returns `base_size`; lemma 1 guarantees `base_size <=
375
+ // bytes_len`
376
+
377
+ // Lemma 5
378
+ __debug_assert ! ( self_bytes <= bytes_len) ;
379
+
380
+ // `self_addr` indicates where in the given byte range the `Self` will
381
+ // start. If we're doing a prefix cast, it starts at the beginning. If
382
+ // we're doing a suffix cast, it starts after whatever bytes are
383
+ // remaining.
384
+ let ( self_addr, split_at) = match cast_type {
385
+ _CastType:: _Prefix => ( addr, self_bytes) ,
386
+ _CastType:: _Suffix => {
387
+ // Guaranteed not to underflow because `self_bytes <= bytes_len`
388
+ // (lemma 4).
389
+ //
390
+ // LEMMA 5: split_at == bytes_len - self_bytes
391
+ #[ allow( clippy:: arithmetic_side_effects) ]
392
+ let split_at = bytes_len - self_bytes;
393
+
394
+ // Lemma 5
395
+ __debug_assert ! ( split_at == bytes_len - self_bytes) ;
396
+
397
+ // Guaranteed not to overflow:
398
+ // - addr + bytes_len <= usize::MAX (method precondition)
399
+ // - split_at == bytes_len - self_bytes (lemma 5)
400
+ // - addr + split_at == addr + bytes_len - self_bytes (substitution)
401
+ // - addr + split_at <= addr + bytes_len
402
+ // - addr + split_at <= usize::MAX (substitution)
403
+ #[ allow( clippy:: arithmetic_side_effects) ]
404
+ let self_addr = addr + split_at;
405
+
406
+ ( self_addr, split_at)
407
+ }
408
+ } ;
409
+
410
+ // Guaranteed not to divide by 0 because `.align()` guarantees that it
411
+ // returns a non-zero value.
412
+ #[ allow( clippy:: arithmetic_side_effects) ]
413
+ if self_addr % self . _base_layout . align ( ) != 0 {
414
+ return None ;
415
+ }
416
+
417
+ Some ( ( elems, split_at) )
418
+ }
254
419
}
255
420
256
421
/// A trait which carries information about a type's layout that is used by the
@@ -2738,6 +2903,99 @@ mod tests {
2738
2903
}
2739
2904
}
2740
2905
2906
+ // This test takes a long time when running under Miri, so we skip it in
2907
+ // that case. This is acceptable because this is a logic test that doesn't
2908
+ // attempt to expose UB.
2909
+ #[ test]
2910
+ #[ cfg_attr( miri, ignore) ]
2911
+ fn test_validate_cast ( ) {
2912
+ let layout = |base_size, align, _trailing_slice_elem_size| DstLayout {
2913
+ _base_layout : Layout :: from_size_align ( base_size, align) . unwrap ( ) ,
2914
+ _trailing_slice_elem_size,
2915
+ } ;
2916
+
2917
+ macro_rules! test {
2918
+ (
2919
+ layout( $base_size: tt, $align: tt, $trailing_size: tt)
2920
+ . validate_cast( $addr: tt, $bytes_len: tt, $cast_type: tt) , $expect: pat $( , ) ?
2921
+ ) => {
2922
+ itertools:: iproduct!(
2923
+ test!( @generate_usize $base_size) ,
2924
+ test!( @generate_align $align) ,
2925
+ test!( @generate_opt_usize $trailing_size) ,
2926
+ test!( @generate_usize $addr) ,
2927
+ test!( @generate_usize $bytes_len) ,
2928
+ test!( @generate_cast_type $cast_type)
2929
+ ) . for_each( |( base_size, align, trailing_size, addr, bytes_len, cast_type) | {
2930
+ assert_matches:: assert_matches!(
2931
+ layout( base_size, align, trailing_size) . _validate_cast( addr, bytes_len, cast_type) , $expect,
2932
+ "layout({base_size}, {align}, {trailing_size:?}).validate_cast({addr}, {bytes_len}, {cast_type:?})" ,
2933
+ ) ;
2934
+ } ) ;
2935
+ } ;
2936
+ ( @generate_usize _) => { 0 ..8 } ;
2937
+ ( @generate_align _) => { [ 1 , 2 , 4 , 8 , 16 ] } ;
2938
+ ( @generate_opt_usize _) => { [ None ] . into_iter( ) . chain( ( 0 ..8 ) . map( Some ) . into_iter( ) ) } ;
2939
+ ( @generate_cast_type _) => { [ _CastType:: _Prefix, _CastType:: _Suffix] } ;
2940
+ ( @generate_cast_type $variant: ident) => { [ _CastType:: $variant] } ;
2941
+ // Some expressions need to be wrapped in parentheses in order to be
2942
+ // valid `tt`s (required by the top match pattern). See the comment
2943
+ // below for more details. This arm removes these parentheses to
2944
+ // avoid generating an `unused_parens` warning.
2945
+ ( @$_: ident ( $vals: expr) ) => { $vals } ;
2946
+ ( @$_: ident $vals: expr) => { $vals } ;
2947
+ }
2948
+
2949
+ // The format of all of these test cases is:
2950
+ //
2951
+ // layout(_, _, _).validate_cast(_, _, _), Some((_, _))
2952
+ // | | | | | | | |
2953
+ // base_size ----+ | | | | | | |
2954
+ // align -----------+ | | | | | |
2955
+ // trailing_size ------+ | | | | |
2956
+ // addr --------------------------------+ | | | |
2957
+ // bytes_len ------------------------------+ | | |
2958
+ // cast_type ---------------------------------+ | |
2959
+ // elems -----------------------------------------------+ |
2960
+ // split_at -----------------------------------------------+
2961
+ //
2962
+ // Each argument can either be an iterator or a wildcard. Each
2963
+ // wildcarded variable is implicitly replaced by an iterator over a
2964
+ // representative sample of values for that variable. Each `test!`
2965
+ // invocation iterates over every combination of values provided by each
2966
+ // variable's iterator (ie, the cartesian product) and validates that
2967
+ // the results are expected.
2968
+ //
2969
+ // The final argument uses the same syntax, but it has a different
2970
+ // meaning. The final argument is a pattern that will be supplied to
2971
+ // `assert_matches!` to validate the computed result for each
2972
+ // combination of input values.
2973
+ //
2974
+ // Note that the meta-variables that match these variables have the `tt`
2975
+ // type, and some valid expressions are not valid `tt`s (such as
2976
+ // `a..b`). In this case, wrap the expression in parentheses, and it
2977
+ // will become valid `tt`.
2978
+
2979
+ // base_size is too big for the memory region.
2980
+ test ! ( layout( ( 1 ..8 ) , _, _) . validate_cast( _, [ 0 ] , _) , None ) ;
2981
+ test ! ( layout( ( 2 ..8 ) , _, _) . validate_cast( _, [ 1 ] , _) , None ) ;
2982
+
2983
+ // addr is unaligned
2984
+ test ! ( layout( _, [ 2 ] , [ None ] ) . validate_cast( [ 1 , 3 , 5 , 7 , 9 ] , _, _Prefix) , None ) ;
2985
+ test ! ( layout( _, [ 2 ] , ( ( 1 ..8 ) . map( Some ) ) ) . validate_cast( [ 1 , 3 , 5 , 7 , 9 ] , _, _Prefix) , None ) ;
2986
+
2987
+ // TODO: Test Suffix cast failure cases, especially regarding alignment.
2988
+
2989
+ // TDOO: Success cases
2990
+ }
2991
+
2992
+ #[ test]
2993
+ fn test_validate_cast_panics ( ) {
2994
+ // TODO: Test for these cases:
2995
+ // - addr + bytes overflows usize
2996
+ // - zero-sized trailing element type
2997
+ }
2998
+
2741
2999
#[ test]
2742
3000
fn test_known_layout ( ) {
2743
3001
// Test that `$ty` and `ManuallyDrop<$ty>` have the expected layout.
0 commit comments