mirror of
https://github.com/Lokathor/bytemuck.git
synced 2024-11-24 07:42:24 +00:00
Fix panics in try_cast_slice_box
etc (#254)
* Fix panics in try_cast_slice_{box,rc,arc},cast_vec. Casting from non-empty non-ZST slices to ZST slices now returns Err(SizeMismatch) (instead of panicking). Casting from empty non-ZST slices to ZST slices is allowed and returns an empty slice (instead of panicking). Casting from ZST slices to non-ZST slices is allowed and returns an empty slice (status quo). * Add tests for cast_slice_box,rc,arc.
This commit is contained in:
parent
02ffd53a4e
commit
005ee3254f
@ -21,7 +21,7 @@ use alloc::{
|
||||
vec::Vec,
|
||||
};
|
||||
use core::{
|
||||
mem::ManuallyDrop,
|
||||
mem::{size_of_val, ManuallyDrop},
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
@ -172,7 +172,10 @@ pub fn try_cast_slice_box<A: NoUninit, B: AnyBitPattern>(
|
||||
if align_of::<A>() != align_of::<B>() {
|
||||
Err((PodCastError::AlignmentMismatch, input))
|
||||
} else if size_of::<A>() != size_of::<B>() {
|
||||
if size_of::<A>() * input.len() % size_of::<B>() != 0 {
|
||||
let input_bytes = size_of_val::<[A]>(&*input);
|
||||
if (size_of::<B>() == 0 && input_bytes != 0)
|
||||
|| (size_of::<B>() != 0 && input_bytes % size_of::<B>() != 0)
|
||||
{
|
||||
// If the size in bytes of the underlying buffer does not match an exact
|
||||
// multiple of the size of B, we cannot cast between them.
|
||||
Err((PodCastError::SizeMismatch, input))
|
||||
@ -185,7 +188,8 @@ pub fn try_cast_slice_box<A: NoUninit, B: AnyBitPattern>(
|
||||
// Luckily, Layout only stores two things, the alignment, and the size in
|
||||
// bytes. So as long as both of those stay the same, the Layout will
|
||||
// remain a valid input to dealloc.
|
||||
let length = size_of::<A>() * input.len() / size_of::<B>();
|
||||
let length =
|
||||
if size_of::<B>() != 0 { input_bytes / size_of::<B>() } else { 0 };
|
||||
let box_ptr: *mut A = Box::into_raw(input) as *mut A;
|
||||
let ptr: *mut [B] =
|
||||
unsafe { core::slice::from_raw_parts_mut(box_ptr as *mut B, length) };
|
||||
@ -222,8 +226,12 @@ pub fn try_cast_vec<A: NoUninit, B: AnyBitPattern>(
|
||||
if align_of::<A>() != align_of::<B>() {
|
||||
Err((PodCastError::AlignmentMismatch, input))
|
||||
} else if size_of::<A>() != size_of::<B>() {
|
||||
if size_of::<A>() * input.len() % size_of::<B>() != 0
|
||||
|| size_of::<A>() * input.capacity() % size_of::<B>() != 0
|
||||
let input_size = size_of_val::<[A]>(&*input);
|
||||
let input_capacity = input.capacity() * size_of::<A>();
|
||||
if (size_of::<B>() == 0 && input_capacity != 0)
|
||||
|| (size_of::<B>() != 0
|
||||
&& (input_size % size_of::<B>() != 0
|
||||
|| input_capacity % size_of::<B>() != 0))
|
||||
{
|
||||
// If the size in bytes of the underlying buffer does not match an exact
|
||||
// multiple of the size of B, we cannot cast between them.
|
||||
@ -244,8 +252,10 @@ pub fn try_cast_vec<A: NoUninit, B: AnyBitPattern>(
|
||||
|
||||
// Note(Lokathor): First we record the length and capacity, which don't
|
||||
// have any secret provenance metadata.
|
||||
let length: usize = size_of::<A>() * input.len() / size_of::<B>();
|
||||
let capacity: usize = size_of::<A>() * input.capacity() / size_of::<B>();
|
||||
let length: usize =
|
||||
if size_of::<B>() != 0 { input_size / size_of::<B>() } else { 0 };
|
||||
let capacity: usize =
|
||||
if size_of::<B>() != 0 { input_capacity / size_of::<B>() } else { 0 };
|
||||
// Note(Lokathor): Next we "pre-forget" the old Vec by wrapping with
|
||||
// ManuallyDrop, because if we used `core::mem::forget` after taking the
|
||||
// pointer then that would invalidate our pointer. In nightly there's a
|
||||
@ -415,7 +425,10 @@ pub fn try_cast_slice_rc<
|
||||
if align_of::<A>() != align_of::<B>() {
|
||||
Err((PodCastError::AlignmentMismatch, input))
|
||||
} else if size_of::<A>() != size_of::<B>() {
|
||||
if size_of::<A>() * input.len() % size_of::<B>() != 0 {
|
||||
let input_bytes = size_of_val::<[A]>(&*input);
|
||||
if (size_of::<B>() == 0 && input_bytes != 0)
|
||||
|| (size_of::<B>() != 0 && input_bytes % size_of::<B>() != 0)
|
||||
{
|
||||
// If the size in bytes of the underlying buffer does not match an exact
|
||||
// multiple of the size of B, we cannot cast between them.
|
||||
Err((PodCastError::SizeMismatch, input))
|
||||
@ -427,7 +440,8 @@ pub fn try_cast_slice_rc<
|
||||
// acquired from Rc::into_raw() must have the same size alignment and
|
||||
// size of the type T in the new Rc<T>. So as long as both the size
|
||||
// and alignment stay the same, the Rc will remain a valid Rc.
|
||||
let length = size_of::<A>() * input.len() / size_of::<B>();
|
||||
let length =
|
||||
if size_of::<B>() != 0 { input_bytes / size_of::<B>() } else { 0 };
|
||||
let rc_ptr: *const A = Rc::into_raw(input) as *const A;
|
||||
// Must use ptr::slice_from_raw_parts, because we cannot make an
|
||||
// intermediate const reference, because it has mutable provenance,
|
||||
@ -479,7 +493,10 @@ pub fn try_cast_slice_arc<
|
||||
if align_of::<A>() != align_of::<B>() {
|
||||
Err((PodCastError::AlignmentMismatch, input))
|
||||
} else if size_of::<A>() != size_of::<B>() {
|
||||
if size_of::<A>() * input.len() % size_of::<B>() != 0 {
|
||||
let input_bytes = size_of_val::<[A]>(&*input);
|
||||
if (size_of::<B>() == 0 && input_bytes != 0)
|
||||
|| (size_of::<B>() != 0 && input_bytes % size_of::<B>() != 0)
|
||||
{
|
||||
// If the size in bytes of the underlying buffer does not match an exact
|
||||
// multiple of the size of B, we cannot cast between them.
|
||||
Err((PodCastError::SizeMismatch, input))
|
||||
@ -491,7 +508,8 @@ pub fn try_cast_slice_arc<
|
||||
// acquired from Arc::into_raw() must have the same size alignment and
|
||||
// size of the type T in the new Arc<T>. So as long as both the size
|
||||
// and alignment stay the same, the Arc will remain a valid Arc.
|
||||
let length = size_of::<A>() * input.len() / size_of::<B>();
|
||||
let length =
|
||||
if size_of::<B>() != 0 { input_bytes / size_of::<B>() } else { 0 };
|
||||
let arc_ptr: *const A = Arc::into_raw(input) as *const A;
|
||||
// Must use ptr::slice_from_raw_parts, because we cannot make an
|
||||
// intermediate const reference, because it has mutable provenance,
|
||||
|
@ -195,3 +195,136 @@ fn test_panics() {
|
||||
let aligned_bytes = bytemuck::cast_slice::<u32, u8>(&[0, 0]);
|
||||
should_panic!(from_bytes::<u32>(&aligned_bytes[1..5]));
|
||||
}
|
||||
|
||||
#[cfg(feature = "extern_crate_alloc")]
|
||||
#[test]
|
||||
fn test_boxed_slices() {
|
||||
let boxed_u8_slice: Box<[u8]> = Box::new([0, 1, u8::MAX, i8::MAX as u8]);
|
||||
let boxed_i8_slice: Box<[i8]> = cast_slice_box::<u8, i8>(boxed_u8_slice);
|
||||
assert_eq!(&*boxed_i8_slice, [0, 1, -1, i8::MAX]);
|
||||
|
||||
let result: Result<Box<[u16]>, (PodCastError, Box<[i8]>)> =
|
||||
try_cast_slice_box(boxed_i8_slice);
|
||||
let (error, boxed_i8_slice) =
|
||||
result.expect_err("u16 and i8 have different alignment");
|
||||
assert_eq!(error, PodCastError::AlignmentMismatch);
|
||||
|
||||
// FIXME(#253): Should these next two casts' errors be consistent?
|
||||
let result: Result<&[[i8; 3]], PodCastError> =
|
||||
try_cast_slice(&*boxed_i8_slice);
|
||||
let error =
|
||||
result.expect_err("slice of [i8; 3] cannot be made from slice of 4 i8s");
|
||||
assert_eq!(error, PodCastError::OutputSliceWouldHaveSlop);
|
||||
|
||||
let result: Result<Box<[[i8; 3]]>, (PodCastError, Box<[i8]>)> =
|
||||
try_cast_slice_box(boxed_i8_slice);
|
||||
let (error, boxed_i8_slice) =
|
||||
result.expect_err("slice of [i8; 3] cannot be made from slice of 4 i8s");
|
||||
assert_eq!(error, PodCastError::SizeMismatch);
|
||||
|
||||
let empty: Box<[()]> = cast_slice_box::<u8, ()>(Box::new([]));
|
||||
assert!(empty.is_empty());
|
||||
|
||||
let result: Result<Box<[()]>, (PodCastError, Box<[i8]>)> =
|
||||
try_cast_slice_box(boxed_i8_slice);
|
||||
let (error, boxed_i8_slice) =
|
||||
result.expect_err("slice of ZST cannot be made from slice of 4 u8s");
|
||||
assert_eq!(error, PodCastError::SizeMismatch);
|
||||
|
||||
drop(boxed_i8_slice);
|
||||
|
||||
let empty: Box<[i8]> = cast_slice_box::<(), i8>(Box::new([]));
|
||||
assert!(empty.is_empty());
|
||||
|
||||
let empty: Box<[i8]> = cast_slice_box::<(), i8>(Box::new([(); 42]));
|
||||
assert!(empty.is_empty());
|
||||
}
|
||||
|
||||
#[cfg(feature = "extern_crate_alloc")]
|
||||
#[test]
|
||||
fn test_rc_slices() {
|
||||
use std::rc::Rc;
|
||||
let rc_u8_slice: Rc<[u8]> = Rc::new([0, 1, u8::MAX, i8::MAX as u8]);
|
||||
let rc_i8_slice: Rc<[i8]> = cast_slice_rc::<u8, i8>(rc_u8_slice);
|
||||
assert_eq!(&*rc_i8_slice, [0, 1, -1, i8::MAX]);
|
||||
|
||||
let result: Result<Rc<[u16]>, (PodCastError, Rc<[i8]>)> =
|
||||
try_cast_slice_rc(rc_i8_slice);
|
||||
let (error, rc_i8_slice) =
|
||||
result.expect_err("u16 and i8 have different alignment");
|
||||
assert_eq!(error, PodCastError::AlignmentMismatch);
|
||||
|
||||
// FIXME(#253): Should these next two casts' errors be consistent?
|
||||
let result: Result<&[[i8; 3]], PodCastError> = try_cast_slice(&*rc_i8_slice);
|
||||
let error =
|
||||
result.expect_err("slice of [i8; 3] cannot be made from slice of 4 i8s");
|
||||
assert_eq!(error, PodCastError::OutputSliceWouldHaveSlop);
|
||||
|
||||
let result: Result<Rc<[[i8; 3]]>, (PodCastError, Rc<[i8]>)> =
|
||||
try_cast_slice_rc(rc_i8_slice);
|
||||
let (error, rc_i8_slice) =
|
||||
result.expect_err("slice of [i8; 3] cannot be made from slice of 4 i8s");
|
||||
assert_eq!(error, PodCastError::SizeMismatch);
|
||||
|
||||
let empty: Rc<[()]> = cast_slice_rc::<u8, ()>(Rc::new([]));
|
||||
assert!(empty.is_empty());
|
||||
|
||||
let result: Result<Rc<[()]>, (PodCastError, Rc<[i8]>)> =
|
||||
try_cast_slice_rc(rc_i8_slice);
|
||||
let (error, rc_i8_slice) =
|
||||
result.expect_err("slice of ZST cannot be made from slice of 4 u8s");
|
||||
assert_eq!(error, PodCastError::SizeMismatch);
|
||||
|
||||
drop(rc_i8_slice);
|
||||
|
||||
let empty: Rc<[i8]> = cast_slice_rc::<(), i8>(Rc::new([]));
|
||||
assert!(empty.is_empty());
|
||||
|
||||
let empty: Rc<[i8]> = cast_slice_rc::<(), i8>(Rc::new([(); 42]));
|
||||
assert!(empty.is_empty());
|
||||
}
|
||||
|
||||
#[cfg(feature = "extern_crate_alloc")]
|
||||
#[cfg(target_has_atomic = "ptr")]
|
||||
#[test]
|
||||
fn test_arc_slices() {
|
||||
use std::sync::Arc;
|
||||
let arc_u8_slice: Arc<[u8]> = Arc::new([0, 1, u8::MAX, i8::MAX as u8]);
|
||||
let arc_i8_slice: Arc<[i8]> = cast_slice_arc::<u8, i8>(arc_u8_slice);
|
||||
assert_eq!(&*arc_i8_slice, [0, 1, -1, i8::MAX]);
|
||||
|
||||
let result: Result<Arc<[u16]>, (PodCastError, Arc<[i8]>)> =
|
||||
try_cast_slice_arc(arc_i8_slice);
|
||||
let (error, arc_i8_slice) =
|
||||
result.expect_err("u16 and i8 have different alignment");
|
||||
assert_eq!(error, PodCastError::AlignmentMismatch);
|
||||
|
||||
// FIXME(#253): Should these next two casts' errors be consistent?
|
||||
let result: Result<&[[i8; 3]], PodCastError> = try_cast_slice(&*arc_i8_slice);
|
||||
let error =
|
||||
result.expect_err("slice of [i8; 3] cannot be made from slice of 4 i8s");
|
||||
assert_eq!(error, PodCastError::OutputSliceWouldHaveSlop);
|
||||
|
||||
let result: Result<Arc<[[i8; 3]]>, (PodCastError, Arc<[i8]>)> =
|
||||
try_cast_slice_arc(arc_i8_slice);
|
||||
let (error, arc_i8_slice) =
|
||||
result.expect_err("slice of [i8; 3] cannot be made from slice of 4 i8s");
|
||||
assert_eq!(error, PodCastError::SizeMismatch);
|
||||
|
||||
let empty: Arc<[()]> = cast_slice_arc::<u8, ()>(Arc::new([]));
|
||||
assert!(empty.is_empty());
|
||||
|
||||
let result: Result<Arc<[()]>, (PodCastError, Arc<[i8]>)> =
|
||||
try_cast_slice_arc(arc_i8_slice);
|
||||
let (error, arc_i8_slice) =
|
||||
result.expect_err("slice of ZST cannot be made from slice of 4 u8s");
|
||||
assert_eq!(error, PodCastError::SizeMismatch);
|
||||
|
||||
drop(arc_i8_slice);
|
||||
|
||||
let empty: Arc<[i8]> = cast_slice_arc::<(), i8>(Arc::new([]));
|
||||
assert!(empty.is_empty());
|
||||
|
||||
let empty: Arc<[i8]> = cast_slice_arc::<(), i8>(Arc::new([(); 42]));
|
||||
assert!(empty.is_empty());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user