| extern crate test; |
| |
| use itertools::Itertools; |
| |
| use super::query_context::test::{Def, UltraMinimal}; |
| use crate::{Answer, Assume, Condition, Reason, layout}; |
| |
| type Tree = layout::Tree<Def, !, !>; |
| type Dfa = layout::Dfa<!, !>; |
| |
| trait Representation { |
| fn is_transmutable(src: Self, dst: Self, assume: Assume) -> Answer<!, !>; |
| } |
| |
| impl Representation for Tree { |
| fn is_transmutable(src: Self, dst: Self, assume: Assume) -> Answer<!, !> { |
| crate::maybe_transmutable::MaybeTransmutableQuery::new( |
| src, |
| dst, |
| assume, |
| UltraMinimal::default(), |
| ) |
| .answer() |
| } |
| } |
| |
| impl Representation for Dfa { |
| fn is_transmutable(src: Self, dst: Self, assume: Assume) -> Answer<!, !> { |
| crate::maybe_transmutable::MaybeTransmutableQuery::new( |
| src, |
| dst, |
| assume, |
| UltraMinimal::default(), |
| ) |
| .answer() |
| } |
| } |
| |
| fn is_transmutable<R: Representation + Clone>( |
| src: &R, |
| dst: &R, |
| assume: Assume, |
| ) -> crate::Answer<!, !> { |
| let src = src.clone(); |
| let dst = dst.clone(); |
| // The only dimension of the transmutability analysis we want to test |
| // here is the safety analysis. To ensure this, we disable all other |
| // toggleable aspects of the transmutability analysis. |
| R::is_transmutable(src, dst, assume) |
| } |
| |
| mod safety { |
| use super::*; |
| use crate::Answer; |
| |
| const DST_HAS_SAFETY_INVARIANTS: Answer<!, !> = |
| Answer::No(crate::Reason::DstMayHaveSafetyInvariants); |
| |
| #[test] |
| fn src_safe_dst_safe() { |
| let src = Tree::Def(Def::NoSafetyInvariants).then(Tree::u8()); |
| let dst = Tree::Def(Def::NoSafetyInvariants).then(Tree::u8()); |
| assert_eq!(is_transmutable(&src, &dst, Assume::default()), Answer::Yes); |
| assert_eq!( |
| is_transmutable(&src, &dst, Assume { safety: true, ..Assume::default() }), |
| Answer::Yes |
| ); |
| } |
| |
| #[test] |
| fn src_safe_dst_unsafe() { |
| let src = Tree::Def(Def::NoSafetyInvariants).then(Tree::u8()); |
| let dst = Tree::Def(Def::HasSafetyInvariants).then(Tree::u8()); |
| assert_eq!(is_transmutable(&src, &dst, Assume::default()), DST_HAS_SAFETY_INVARIANTS); |
| assert_eq!( |
| is_transmutable(&src, &dst, Assume { safety: true, ..Assume::default() }), |
| Answer::Yes |
| ); |
| } |
| |
| #[test] |
| fn src_unsafe_dst_safe() { |
| let src = Tree::Def(Def::HasSafetyInvariants).then(Tree::u8()); |
| let dst = Tree::Def(Def::NoSafetyInvariants).then(Tree::u8()); |
| assert_eq!(is_transmutable(&src, &dst, Assume::default()), Answer::Yes); |
| assert_eq!( |
| is_transmutable(&src, &dst, Assume { safety: true, ..Assume::default() }), |
| Answer::Yes |
| ); |
| } |
| |
| #[test] |
| fn src_unsafe_dst_unsafe() { |
| let src = Tree::Def(Def::HasSafetyInvariants).then(Tree::u8()); |
| let dst = Tree::Def(Def::HasSafetyInvariants).then(Tree::u8()); |
| assert_eq!(is_transmutable(&src, &dst, Assume::default()), DST_HAS_SAFETY_INVARIANTS); |
| assert_eq!( |
| is_transmutable(&src, &dst, Assume { safety: true, ..Assume::default() }), |
| Answer::Yes |
| ); |
| } |
| } |
| |
| mod size { |
| use super::*; |
| |
| #[test] |
| fn size() { |
| let small = Tree::number(1); |
| let large = Tree::number(2); |
| |
| for alignment in [false, true] { |
| for lifetimes in [false, true] { |
| for safety in [false, true] { |
| for validity in [false, true] { |
| let assume = Assume { alignment, lifetimes, safety, validity }; |
| assert_eq!( |
| is_transmutable(&small, &large, assume), |
| Answer::No(Reason::DstIsTooBig), |
| "assume: {assume:?}" |
| ); |
| assert_eq!( |
| is_transmutable(&large, &small, assume), |
| Answer::Yes, |
| "assume: {assume:?}" |
| ); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| mod bool { |
| use super::*; |
| |
| #[test] |
| fn should_permit_identity_transmutation_tree() { |
| let src = Tree::bool(); |
| assert_eq!(is_transmutable(&src, &src, Assume::default()), Answer::Yes); |
| assert_eq!( |
| is_transmutable(&src, &src, Assume { validity: true, ..Assume::default() }), |
| Answer::Yes |
| ); |
| } |
| |
| #[test] |
| fn should_permit_identity_transmutation_dfa() { |
| let src = Dfa::bool(); |
| assert_eq!(is_transmutable(&src, &src, Assume::default()), Answer::Yes); |
| assert_eq!( |
| is_transmutable(&src, &src, Assume { validity: true, ..Assume::default() }), |
| Answer::Yes |
| ); |
| } |
| |
| #[test] |
| fn transmute_u8() { |
| let bool = &Tree::bool(); |
| let u8 = &Tree::u8(); |
| for (src, dst, assume_validity, answer) in [ |
| (bool, u8, false, Answer::Yes), |
| (bool, u8, true, Answer::Yes), |
| (u8, bool, false, Answer::No(Reason::DstIsBitIncompatible)), |
| (u8, bool, true, Answer::Yes), |
| ] { |
| assert_eq!( |
| is_transmutable( |
| src, |
| dst, |
| Assume { validity: assume_validity, ..Assume::default() } |
| ), |
| answer |
| ); |
| } |
| } |
| |
| #[test] |
| fn should_permit_validity_expansion_and_reject_contraction() { |
| let b0 = layout::Tree::<Def, !, !>::byte(0); |
| let b1 = layout::Tree::<Def, !, !>::byte(1); |
| let b2 = layout::Tree::<Def, !, !>::byte(2); |
| |
| let alts = [b0, b1, b2]; |
| |
| let into_layout = |alts: Vec<_>| { |
| alts.into_iter() |
| .fold(layout::Tree::<Def, !, !>::uninhabited(), layout::Tree::<Def, !, !>::or) |
| }; |
| |
| let into_set = |alts: Vec<_>| { |
| #[cfg(feature = "rustc")] |
| let mut set = rustc_data_structures::fx::FxIndexSet::default(); |
| #[cfg(not(feature = "rustc"))] |
| let mut set = std::collections::HashSet::new(); |
| set.extend(alts); |
| set |
| }; |
| |
| for src_alts in alts.clone().into_iter().powerset() { |
| let src_layout = into_layout(src_alts.clone()); |
| let src_set = into_set(src_alts.clone()); |
| |
| for dst_alts in alts.clone().into_iter().powerset().filter(|alts| !alts.is_empty()) { |
| let dst_layout = into_layout(dst_alts.clone()); |
| let dst_set = into_set(dst_alts.clone()); |
| |
| if src_set.is_subset(&dst_set) { |
| assert_eq!( |
| Answer::Yes, |
| is_transmutable(&src_layout, &dst_layout, Assume::default()), |
| "{:?} SHOULD be transmutable into {:?}", |
| src_layout, |
| dst_layout |
| ); |
| } else if !src_set.is_disjoint(&dst_set) { |
| assert_eq!( |
| Answer::Yes, |
| is_transmutable( |
| &src_layout, |
| &dst_layout, |
| Assume { validity: true, ..Assume::default() } |
| ), |
| "{:?} SHOULD be transmutable (assuming validity) into {:?}", |
| src_layout, |
| dst_layout |
| ); |
| } else { |
| assert_eq!( |
| Answer::No(Reason::DstIsBitIncompatible), |
| is_transmutable(&src_layout, &dst_layout, Assume::default()), |
| "{:?} should NOT be transmutable into {:?}", |
| src_layout, |
| dst_layout |
| ); |
| } |
| } |
| } |
| } |
| } |
| |
| mod uninit { |
| use super::*; |
| |
| #[test] |
| fn size() { |
| let mu = Tree::uninit(); |
| let u8 = Tree::u8(); |
| |
| for alignment in [false, true] { |
| for lifetimes in [false, true] { |
| for safety in [false, true] { |
| for validity in [false, true] { |
| let assume = Assume { alignment, lifetimes, safety, validity }; |
| |
| let want = if validity { |
| Answer::Yes |
| } else { |
| Answer::No(Reason::DstIsBitIncompatible) |
| }; |
| |
| assert_eq!(is_transmutable(&mu, &u8, assume), want, "assume: {assume:?}"); |
| assert_eq!( |
| is_transmutable(&u8, &mu, assume), |
| Answer::Yes, |
| "assume: {assume:?}" |
| ); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| mod alt { |
| use super::*; |
| use crate::Answer; |
| |
| #[test] |
| fn should_permit_identity_transmutation() { |
| type Tree = layout::Tree<Def, !, !>; |
| |
| let x = Tree::Seq(vec![Tree::byte(0), Tree::byte(0)]); |
| let y = Tree::Seq(vec![Tree::bool(), Tree::byte(1)]); |
| let layout = Tree::Alt(vec![x, y]); |
| |
| let answer = crate::maybe_transmutable::MaybeTransmutableQuery::new( |
| layout.clone(), |
| layout.clone(), |
| crate::Assume::default(), |
| UltraMinimal::default(), |
| ) |
| .answer(); |
| assert_eq!(answer, Answer::Yes, "layout:{:#?}", layout); |
| } |
| } |
| |
| mod union { |
| use super::*; |
| |
| #[test] |
| fn union() { |
| let [a, b, c, d] = [0, 1, 2, 3]; |
| let s = Dfa::from_edges(a, d, &[(a, 0, b), (b, 0, d), (a, 1, c), (c, 1, d)]); |
| |
| let t = Dfa::from_edges(a, c, &[(a, 1, b), (b, 0, c)]); |
| |
| let mut ctr = 0; |
| let new_state = || { |
| let state = crate::layout::dfa::State(ctr); |
| ctr += 1; |
| state |
| }; |
| |
| let u = s.clone().union(t.clone(), new_state); |
| |
| let expected_u = |
| Dfa::from_edges(b, a, &[(b, 0..=0, c), (b, 1..=1, d), (d, 0..=1, a), (c, 0..=0, a)]); |
| |
| assert_eq!(u, expected_u); |
| |
| assert_eq!(is_transmutable(&s, &u, Assume::default()), Answer::Yes); |
| assert_eq!(is_transmutable(&t, &u, Assume::default()), Answer::Yes); |
| } |
| } |
| |
| mod char { |
| use super::*; |
| use crate::layout::tree::Endian; |
| |
| #[test] |
| fn should_permit_valid_transmutation() { |
| for order in [Endian::Big, Endian::Little] { |
| use Answer::*; |
| let char_layout = layout::Tree::<Def, !, !>::char(order); |
| |
| // `char`s can be in the following ranges: |
| // - [0, 0xD7FF] |
| // - [0xE000, 10FFFF] |
| // |
| // This loop synthesizes a singleton-validity type for the extremes |
| // of each range, and for one past the end of the extremes of each |
| // range. |
| let no = No(Reason::DstIsBitIncompatible); |
| for (src, answer) in [ |
| (0u32, Yes), |
| (0xD7FF, Yes), |
| (0xD800, no.clone()), |
| (0xDFFF, no.clone()), |
| (0xE000, Yes), |
| (0x10FFFF, Yes), |
| (0x110000, no.clone()), |
| (0xFFFF0000, no.clone()), |
| (0xFFFFFFFF, no), |
| ] { |
| let src_layout = |
| layout::tree::Tree::<Def, !, !>::from_big_endian(order, src.to_be_bytes()); |
| |
| let a = is_transmutable(&src_layout, &char_layout, Assume::default()); |
| assert_eq!(a, answer, "endian:{order:?},\nsrc:{src:x}"); |
| } |
| } |
| } |
| } |
| |
| mod nonzero { |
| use super::*; |
| use crate::{Answer, Reason}; |
| |
| const NONZERO_BYTE_WIDTHS: [u64; 5] = [1, 2, 4, 8, 16]; |
| |
| #[test] |
| fn should_permit_identity_transmutation() { |
| for width in NONZERO_BYTE_WIDTHS { |
| let layout = layout::Tree::<Def, !, !>::nonzero(width); |
| assert_eq!(is_transmutable(&layout, &layout, Assume::default()), Answer::Yes); |
| } |
| } |
| |
| #[test] |
| fn should_permit_valid_transmutation() { |
| for width in NONZERO_BYTE_WIDTHS { |
| use Answer::*; |
| |
| let num = layout::Tree::<Def, !, !>::number(width); |
| let nz = layout::Tree::<Def, !, !>::nonzero(width); |
| |
| let a = is_transmutable(&num, &nz, Assume::default()); |
| assert_eq!(a, No(Reason::DstIsBitIncompatible), "width:{width}"); |
| |
| let a = is_transmutable(&nz, &num, Assume::default()); |
| assert_eq!(a, Yes, "width:{width}"); |
| } |
| } |
| } |
| |
| mod r#ref { |
| use super::*; |
| use crate::layout::Reference; |
| |
| #[test] |
| fn should_permit_identity_transmutation() { |
| type Tree = crate::layout::Tree<Def, usize, ()>; |
| |
| for validity in [false, true] { |
| let layout = Tree::Seq(vec![ |
| Tree::byte(0x00), |
| Tree::Ref(Reference { |
| region: 42, |
| is_mut: false, |
| referent: (), |
| referent_size: 0, |
| referent_align: 1, |
| }), |
| ]); |
| |
| let assume = Assume { validity, ..Assume::default() }; |
| |
| let answer = crate::maybe_transmutable::MaybeTransmutableQuery::new( |
| layout.clone(), |
| layout, |
| assume, |
| UltraMinimal::default(), |
| ) |
| .answer(); |
| assert_eq!( |
| answer, |
| Answer::If(Condition::IfAll(vec![ |
| Condition::Transmutable { src: (), dst: () }, |
| Condition::Outlives { long: 42, short: 42 }, |
| Condition::Immutable { ty: () }, |
| ])) |
| ); |
| } |
| } |
| } |
| |
| mod benches { |
| use std::hint::black_box; |
| |
| use test::Bencher; |
| |
| use super::*; |
| |
| #[bench] |
| fn bench_dfa_from_tree(b: &mut Bencher) { |
| let num = Tree::number(8).prune(&|_| false); |
| let num = black_box(num); |
| |
| b.iter(|| { |
| let _ = black_box(Dfa::from_tree(num.clone())); |
| }) |
| } |
| |
| #[bench] |
| fn bench_transmute(b: &mut Bencher) { |
| let num = Tree::number(8).prune(&|_| false); |
| let dfa = black_box(Dfa::from_tree(num).unwrap()); |
| |
| b.iter(|| { |
| let answer = crate::maybe_transmutable::MaybeTransmutableQuery::new( |
| dfa.clone(), |
| dfa.clone(), |
| Assume::default(), |
| UltraMinimal::default(), |
| ) |
| .answer(); |
| let answer = std::hint::black_box(answer); |
| assert_eq!(answer, Answer::Yes); |
| }) |
| } |
| } |