Skip to content

Commit

Permalink
Fix panics in try_cast_slice_box etc (#254)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
zachs18 authored Jul 21, 2024
1 parent 02ffd53 commit 005ee32
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 11 deletions.
40 changes: 29 additions & 11 deletions src/allocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use alloc::{
vec::Vec,
};
use core::{
mem::ManuallyDrop,
mem::{size_of_val, ManuallyDrop},
ops::{Deref, DerefMut},
};

Expand Down Expand Up @@ -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))
Expand All @@ -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) };
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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))
Expand All @@ -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,
Expand Down Expand Up @@ -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))
Expand All @@ -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,
Expand Down
133 changes: 133 additions & 0 deletions tests/cast_slice_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

0 comments on commit 005ee32

Please sign in to comment.