#![no_std]
pub(crate) mod prims;
pub(crate) mod static_assert;
pub(crate) mod traits;
pub use isrepr_macros::IsRepr;
pub use traits::{HasRepr, RawTryInto, Repr, ReprError};
#[cfg(test)]
extern crate self as isrepr;
#[cfg(test)]
mod test {
extern crate alloc;
extern crate std;
use alloc::format;
use crate::{
traits::{HasRepr, Repr, ReprError},
IsRepr,
};
use std::{
marker::PhantomData,
mem::{align_of, size_of, transmute_copy},
};
fn to_repr<T: HasRepr>(t: &T) -> Repr<T> {
unsafe { transmute_copy(t) }
}
unsafe fn raw_write<T, M>(t: &mut T, m: M, off: usize) {
let tp = t as *mut _ as *mut u8;
let tm = &m as *const _ as *const u8;
let tp = tp.offset(off as isize);
tp.copy_from(tm, size_of::<M>());
}
mod layout {
extern crate alloc;
extern crate std;
use super::{raw_write, to_repr};
use alloc::vec::Vec;
use crate::{
traits::{HasRepr, Repr, ReprError},
IsRepr,
};
use core::panic::RefUnwindSafe;
use std::sync::Mutex;
use std::{
collections::HashSet,
mem::{align_of, size_of},
};
trait Num: Sized + Copy + Eq + std::fmt::Debug + Default {}
impl<T: Sized + Copy + Eq + std::fmt::Debug + Default> Num for T {}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(C)]
struct L<T: Num, const OFF: usize, const ID: u8>(T);
impl<T: Num, const OFF: usize, const ID: u8> L<T, OFF, ID> {
fn unique_byte_pattern() -> Vec<u8> {
alloc::vec![0xffu8 - ID; size_of::<Self>()]
}
fn new() -> Self {
let mut me = Self(Default::default());
let mep = &mut me as *mut _ as *mut u8;
let unique_byte_pattern = Self::unique_byte_pattern();
unsafe {
mep.copy_from(unique_byte_pattern.as_ptr(), size_of::<Self>());
}
me
}
}
unsafe impl<T: Num, const OFF: usize, const ID: u8> HasRepr for L<T, OFF, ID> {
type Raw = Self;
fn raw_is_valid(value: &Self::Raw) -> Result<(), ReprError> {
let unique_byte_pattern = Self::unique_byte_pattern();
let current_byte_pattern = unsafe {
std::slice::from_raw_parts(value as *const _ as *const u8, size_of::<Self>())
};
assert_eq!(&unique_byte_pattern, current_byte_pattern);
add_verified_offset(ID);
Ok(())
}
}
struct GlobalLayoutInfo {
verified_offsets: Vec<u8>,
}
unsafe impl Send for GlobalLayoutInfo {}
static OFFSET_CHECK_LOCK: Mutex<()> = Mutex::new(());
static LAYOUT_INFO: Mutex<GlobalLayoutInfo> = Mutex::new(GlobalLayoutInfo {
verified_offsets: Vec::new(),
});
fn add_verified_offset(off: u8) {
LAYOUT_INFO.lock().unwrap().verified_offsets.push(off);
}
fn clear_verified_offsets() {
LAYOUT_INFO.lock().unwrap().verified_offsets.clear();
}
fn compare_verified_offsets(to: &[u8]) {
let offs = LAYOUT_INFO.lock().unwrap().verified_offsets.clone();
let mut expected_set: HashSet<u8> = HashSet::new();
expected_set.extend(to);
let mut actual_set = HashSet::new();
actual_set.extend(offs);
assert_eq!(expected_set, actual_set);
}
fn test_layout<T: HasRepr>(t: &T, expected_offsets: &[u8])
where
T::Raw: RefUnwindSafe,
{
assert_eq!(size_of::<T>(), size_of::<Repr<T>>());
assert_eq!(align_of::<T>(), align_of::<Repr<T>>());
let t_repr = to_repr(t);
let t_repr_ref = &t_repr;
let _lock = OFFSET_CHECK_LOCK.lock().unwrap();
let p = std::panic::catch_unwind(|| {
clear_verified_offsets();
let _new_t = t_repr_ref.ref_try_into().unwrap();
compare_verified_offsets(expected_offsets);
});
drop(_lock);
match p {
Ok(r) => r,
Err(e) => std::panic::panic_any(e),
}
}
unsafe fn test_enum_invalid_tag<E: HasRepr + std::fmt::Debug, M>(
e: &mut E,
m: M,
off: usize,
) {
let mut e = to_repr(e);
raw_write(&mut e, m, off);
e.repr_try_into().unwrap_err();
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
enum ValuelessEnum {
FOO = 5,
BAR = 10,
}
#[test]
fn test_valueless_enum() {
test_layout(&ValuelessEnum::FOO, &[]);
test_layout(&ValuelessEnum::BAR, &[]);
unsafe {
test_enum_invalid_tag(&mut ValuelessEnum::FOO, 15u8, 0);
}
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
enum EnumWithValues {
Unit,
Tuple(L<u64, 8, 0>, L<u64, 16, 1>),
Struct {
x: L<u32, 4, 2>,
y: L<u32, 8, 3>,
z: L<u32, 12, 4>,
},
}
#[test]
fn test_enum_with_values() {
test_layout(&EnumWithValues::Unit, &[]);
test_layout(&EnumWithValues::Tuple(L::new(), L::new()), &[0, 1]);
test_layout(
&EnumWithValues::Struct {
x: L::new(),
y: L::new(),
z: L::new(),
},
&[2, 3, 4],
);
unsafe {
test_enum_invalid_tag(&mut EnumWithValues::Unit, 3u8, 0);
}
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C, u8)]
enum CEnumWithValues {
Unit,
Tuple(L<u64, 8, 0>, L<u64, 16, 1>),
Struct {
x: L<u32, 8, 2>,
y: L<u32, 12, 3>,
z: L<u32, 16, 4>,
},
}
#[test]
fn test_c_enum_with_values() {
test_layout(&CEnumWithValues::Unit, &[]);
test_layout(&CEnumWithValues::Tuple(L::new(), L::new()), &[0, 1]);
test_layout(
&CEnumWithValues::Struct {
x: L::new(),
y: L::new(),
z: L::new(),
},
&[2, 3, 4],
);
unsafe {
test_enum_invalid_tag(&mut CEnumWithValues::Unit, 3u8, 0);
}
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C)]
struct Struct {
x: L<u8, 0, 0>,
y: L<u32, 4, 1>,
z: L<u64, 8, 2>,
t: L<u64, 16, 3>,
}
#[test]
fn test_struct() {
test_layout(
&Struct {
x: L::new(),
y: L::new(),
z: L::new(),
t: L::new(),
},
&[0, 1, 2, 3],
);
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C)]
struct StructWithArray {
x: L<[u8; 3], 0, 0>,
y: L<[u32; 3], 4, 1>,
z: L<[u8; 5], 16, 2>,
t: L<u64, 24, 3>,
}
#[test]
fn test_struct_with_array() {
test_layout(
&StructWithArray {
x: L::new(),
y: L::new(),
z: L::new(),
t: L::new(),
},
&[0, 1, 2, 3],
);
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C)]
struct NestedReprOuter {
x: L<u8, 0, 0>,
inner: NestedReprInner,
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
enum NestedReprInner {
Unit(L<u8, 2, 1>),
}
#[test]
fn test_nested_repr() {
test_layout(
&NestedReprOuter {
x: L::new(),
inner: NestedReprInner::Unit(L::new()),
},
&[0, 1],
);
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C, align(16))]
struct Aligned16Outer {
foo: L<u32, 0, 0>,
bar: Aligned16Inner,
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C, align(16))]
struct Aligned16Inner {
x: L<u32, 16, 1>,
y: L<u32, 20, 2>,
z: L<u32, 24, 3>,
}
#[test]
fn test_aligned_struct() {
test_layout(
&Aligned16Outer {
foo: L::new(),
bar: Aligned16Inner {
x: L::new(),
y: L::new(),
z: L::new(),
},
},
&[0, 1, 2, 3],
);
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C)]
struct StructWithAligned4Enum {
foo: L<u8, 0, 0>,
bar: Aligned4Enum,
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8, align(4))]
#[allow(dead_code)]
enum Aligned4Enum {
Bar,
Foo(L<u8, 5, 1>),
}
#[test]
fn test_aligned_enum() {
test_layout(
&StructWithAligned4Enum {
foo: L::new(),
bar: Aligned4Enum::Foo(L::new()),
},
&[0, 1],
);
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C)]
struct StructWithCAligned4Enum {
foo: L<u8, 0, 0>,
bar: CAligned4Enum,
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C, u8, align(4))]
#[allow(dead_code)]
enum CAligned4Enum {
Bar(L<u16, 6, 1>),
Foo(L<u8, 6, 2>),
}
#[test]
fn test_c_aligned_enum() {
test_layout(
&StructWithCAligned4Enum {
foo: L::new(),
bar: CAligned4Enum::Foo(L::new()),
},
&[0, 2],
);
test_layout(
&StructWithCAligned4Enum {
foo: L::new(),
bar: CAligned4Enum::Bar(L::new()),
},
&[0, 1],
);
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C, packed)]
struct Packed {
foo: L<u8, 0, 0>,
bar: L<u32, 1, 1>,
baz: L<u64, 5, 2>,
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C, packed(2))]
struct Packed2 {
foo: L<u8, 0, 0>,
bar: L<u32, 2, 1>,
baz: L<u64, 6, 2>,
bax: L<u8, 14, 3>,
bav: L<u8, 15, 4>,
}
#[test]
fn test_c_packed_enum() {
test_layout(
&Packed {
foo: L::new(),
bar: L::new(),
baz: L::new(),
},
&[0, 1, 2],
);
test_layout(
&Packed2 {
foo: L::new(),
bar: L::new(),
baz: L::new(),
bax: L::new(),
bav: L::new(),
},
&[0, 1, 2, 3, 4],
);
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(transparent)]
enum TransparentEnum {
FOO((), L<u32, 0, 0>, ()),
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(transparent)]
enum EmptyTransparentEnum {
FOO,
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(transparent)]
struct TransparentStruct((), L<u32, 0, 0>, ());
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(transparent)]
struct EmptyTransparentStruct;
#[test]
fn test_transparent() {
test_layout(&TransparentEnum::FOO((), L::new(), ()), &[0]);
test_layout(&EmptyTransparentEnum::FOO, &[]);
test_layout(&TransparentStruct((), L::new(), ()), &[0]);
test_layout(&EmptyTransparentStruct, &[]);
}
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
enum ValuelessEnum {
FOO = 5,
BAR = 10,
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C, u8)]
pub enum CEnumWithValues {
Unit,
Tuple(u64, u64),
Struct { x: u32, y: u32, z: u32 },
}
#[test]
fn test_valueless_enum_from_raw() {
assert_eq!(ValuelessEnum::try_from_raw(8), Err(ReprError));
assert_eq!(
ValuelessEnum::try_from_repr(<Repr<ValuelessEnum>>::from_raw(5)),
Ok(ValuelessEnum::FOO)
);
}
#[test]
fn test_valueless_enum_ref() {
let mut base_val = <Repr<ValuelessEnum>>::from_raw(5);
let foo = ValuelessEnum::try_from_ref(&base_val).unwrap();
assert_eq!(*foo, ValuelessEnum::FOO);
let foo = ValuelessEnum::try_from_mut(&mut base_val).unwrap();
assert_eq!(*foo, ValuelessEnum::FOO);
*foo = ValuelessEnum::BAR;
assert_eq!(base_val.repr_try_into().unwrap(), ValuelessEnum::BAR);
}
#[test]
fn test_value_enum_with_values_debug() {
let value = CEnumWithValues::Unit;
let value_repr: Repr<CEnumWithValues> = to_repr(&value);
assert_eq!(
format!("{:?}", value_repr),
"Repr(CEnumWithValuesRepr { _repr_tag: 0, f_Unit: Unit })"
);
let value = CEnumWithValues::Tuple(5, 10);
let value_repr: Repr<CEnumWithValues> = to_repr(&value);
assert_eq!(
format!("{:?}", value_repr),
"Repr(CEnumWithValuesRepr { _repr_tag: 1, f_Tuple: Tuple(5, 10) })"
);
let value = CEnumWithValues::Struct { x: 1, y: 2, z: 3 };
let mut value_repr: Repr<CEnumWithValues> = to_repr(&value);
assert_eq!(
format!("{:?}", value_repr),
"Repr(CEnumWithValuesRepr { _repr_tag: 2, f_Struct: Struct { x: 1, y: 2, z: 3 } })"
);
let repr_ptr = &mut value_repr as *mut Repr<CEnumWithValues> as *mut u8;
unsafe { core::ptr::write(repr_ptr, 3) };
assert_eq!(
format!("{:?}", value_repr),
"Repr(CEnumWithValuesRepr { _repr_tag: 3 })"
);
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C, u8)]
enum EnumWithBoolAndArray {
Bool(bool),
Array([bool; 7]),
}
#[test]
fn test_bool_enum_with_values() {
for value in &[
EnumWithBoolAndArray::Bool(true),
EnumWithBoolAndArray::Array([true, false, false, false, true, true, true]),
] {
let r = to_repr(value);
r.repr_try_into().unwrap();
}
let mut value = EnumWithBoolAndArray::Bool(true);
unsafe { raw_write(&mut value, 2u8, 1) };
let r = to_repr(&value);
r.repr_try_into().unwrap_err();
let mut value = EnumWithBoolAndArray::Array([true; 7]);
unsafe { raw_write(&mut value, 2u8, 5) };
let r = to_repr(&value);
r.repr_try_into().unwrap_err();
}
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C)]
struct StructWithLifetime<'a> {
x: u8,
p: PhantomData<&'a u8>,
}
#[test]
fn test_repr_with_lifetime_layout_matches() {
assert_eq!(
size_of::<StructWithLifetime<'static>>(),
size_of::<Repr<StructWithLifetime<'static>>>()
);
assert_eq!(
align_of::<StructWithLifetime<'static>>(),
align_of::<Repr<StructWithLifetime<'static>>>()
);
}
#[derive(IsRepr)]
#[repr(C)]
struct NestedLifetimeInner<'a>(PhantomData<&'a u8>);
#[derive(IsRepr, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C)]
struct NestedLifetimeOuter<'a>(*const Repr<NestedLifetimeInner<'a>>);
#[test]
fn test_nested_lifetime_struct() {
let _ = NestedLifetimeOuter(std::ptr::null());
}
#[derive(IsRepr)]
#[repr(C)]
struct UnsizedPhantomData(PhantomData<[u8]>);
#[derive(IsRepr)]
#[repr(C)]
struct UnsizedPtr(*mut [u8]);
}