diff --git a/compiler/rustc_transmute/Cargo.toml b/compiler/rustc_transmute/Cargo.toml index f0c783b30020e..9b7cfa8a3217d 100644 --- a/compiler/rustc_transmute/Cargo.toml +++ b/compiler/rustc_transmute/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] # tidy-alphabetical-start +itertools = "0.12" rustc_abi = { path = "../rustc_abi", optional = true } rustc_data_structures = { path = "../rustc_data_structures" } rustc_hir = { path = "../rustc_hir", optional = true } @@ -20,8 +21,3 @@ rustc = [ "dep:rustc_middle", "dep:rustc_span", ] - -[dev-dependencies] -# tidy-alphabetical-start -itertools = "0.12" -# tidy-alphabetical-end diff --git a/compiler/rustc_transmute/src/layout/automaton.rs b/compiler/rustc_transmute/src/layout/automaton.rs new file mode 100644 index 0000000000000..926deef9ed3a3 --- /dev/null +++ b/compiler/rustc_transmute/src/layout/automaton.rs @@ -0,0 +1,54 @@ +use std::fmt; +use std::sync::atomic::{AtomicU32, Ordering}; + +use super::{Byte, Ref}; +use crate::{Map, Set}; + +#[derive(PartialEq, Debug, Clone)] +pub(crate) struct Automaton +where + R: Ref, +{ + pub(crate) transitions: Map, Set>>, + pub(crate) start: State, + pub(crate) accept: State, +} + +/// The states in a `Nfa` represent byte offsets. +#[derive(Hash, Eq, PartialEq, PartialOrd, Ord, Copy, Clone)] +pub(crate) struct State(u32); + +/// The transitions between states in a `Nfa` reflect bit validity. +#[derive(Hash, Eq, PartialEq, Clone, Copy)] +pub(crate) enum Transition +where + R: Ref, +{ + Byte(Byte), + Ref(R), +} + +impl fmt::Debug for State { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "S_{}", self.0) + } +} + +impl fmt::Debug for Transition +where + R: Ref, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self { + Self::Byte(b) => b.fmt(f), + Self::Ref(r) => r.fmt(f), + } + } +} + +impl State { + pub(crate) fn new() -> Self { + static COUNTER: AtomicU32 = AtomicU32::new(0); + Self(COUNTER.fetch_add(1, Ordering::SeqCst)) + } +} diff --git a/compiler/rustc_transmute/src/layout/dfa.rs b/compiler/rustc_transmute/src/layout/dfa.rs index af568171f911c..950b42d04067c 100644 --- a/compiler/rustc_transmute/src/layout/dfa.rs +++ b/compiler/rustc_transmute/src/layout/dfa.rs @@ -1,88 +1,17 @@ -use std::fmt; -use std::sync::atomic::{AtomicU32, Ordering}; - +use itertools::Itertools; use tracing::instrument; -use super::{Byte, Nfa, Ref, nfa}; -use crate::Map; +use super::automaton::{Automaton, State, Transition}; +use super::{Byte, Nfa, Ref}; +use crate::{Map, Set}; #[derive(PartialEq, Clone, Debug)] -pub(crate) struct Dfa -where - R: Ref, -{ - pub(crate) transitions: Map>, - pub(crate) start: State, - pub(crate) accepting: State, -} - -#[derive(PartialEq, Clone, Debug)] -pub(crate) struct Transitions -where - R: Ref, -{ - byte_transitions: Map, - ref_transitions: Map, -} - -impl Default for Transitions -where - R: Ref, -{ - fn default() -> Self { - Self { byte_transitions: Map::default(), ref_transitions: Map::default() } - } -} - -impl Transitions -where - R: Ref, -{ - #[cfg(test)] - fn insert(&mut self, transition: Transition, state: State) { - match transition { - Transition::Byte(b) => { - self.byte_transitions.insert(b, state); - } - Transition::Ref(r) => { - self.ref_transitions.insert(r, state); - } - } - } -} - -/// The states in a `Nfa` represent byte offsets. -#[derive(Hash, Eq, PartialEq, PartialOrd, Ord, Copy, Clone)] -pub(crate) struct State(u32); - -#[cfg(test)] -#[derive(Hash, Eq, PartialEq, Clone, Copy)] -pub(crate) enum Transition -where - R: Ref, -{ - Byte(Byte), - Ref(R), -} - -impl fmt::Debug for State { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "S_{}", self.0) - } -} - -#[cfg(test)] -impl fmt::Debug for Transition -where - R: Ref, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self { - Self::Byte(b) => b.fmt(f), - Self::Ref(r) => r.fmt(f), - } - } -} +pub(crate) struct Dfa( + // INVARIANT: `Automaton` is a DFA, which means that, for any `state`, each + // transition in `self.0.transitions[state]` contains exactly one + // destination state. + pub(crate) Automaton, +); impl Dfa where @@ -90,49 +19,64 @@ where { #[cfg(test)] pub(crate) fn bool() -> Self { - let mut transitions: Map> = Map::default(); + let mut transitions: Map, Set>> = Map::default(); let start = State::new(); - let accepting = State::new(); + let accept = State::new(); - transitions.entry(start).or_default().insert(Transition::Byte(Byte::Init(0x00)), accepting); + transitions + .entry(start) + .or_default() + .insert(Transition::Byte(Byte::Init(0x00)), [accept].into_iter().collect()); - transitions.entry(start).or_default().insert(Transition::Byte(Byte::Init(0x01)), accepting); + transitions + .entry(start) + .or_default() + .insert(Transition::Byte(Byte::Init(0x01)), [accept].into_iter().collect()); - Self { transitions, start, accepting } + Dfa(Automaton { transitions, start, accept }) } #[instrument(level = "debug")] pub(crate) fn from_nfa(nfa: Nfa) -> Self { - let Nfa { transitions: nfa_transitions, start: nfa_start, accepting: nfa_accepting } = nfa; + // It might already be the case that `nfa` is a DFA. If that's the case, + // we can avoid reconstructing the DFA. + let is_dfa = nfa + .0 + .transitions + .iter() + .flat_map(|(_, transitions)| transitions.iter()) + .all(|(_, dsts)| dsts.len() <= 1); + if is_dfa { + return Dfa(nfa.0); + } + + let Nfa(Automaton { transitions: nfa_transitions, start: nfa_start, accept: nfa_accept }) = + nfa; - let mut dfa_transitions: Map> = Map::default(); - let mut nfa_to_dfa: Map = Map::default(); + let mut dfa_transitions: Map, Set>> = Map::default(); + let mut nfa_to_dfa: Map = Map::default(); let dfa_start = State::new(); nfa_to_dfa.insert(nfa_start, dfa_start); let mut queue = vec![(nfa_start, dfa_start)]; while let Some((nfa_state, dfa_state)) = queue.pop() { - if nfa_state == nfa_accepting { + if nfa_state == nfa_accept { continue; } for (nfa_transition, next_nfa_states) in nfa_transitions[&nfa_state].iter() { + use itertools::Itertools as _; + let dfa_transitions = dfa_transitions.entry(dfa_state).or_insert_with(Default::default); let mapped_state = next_nfa_states.iter().find_map(|x| nfa_to_dfa.get(x).copied()); - let next_dfa_state = match nfa_transition { - &nfa::Transition::Byte(b) => *dfa_transitions - .byte_transitions - .entry(b) - .or_insert_with(|| mapped_state.unwrap_or_else(State::new)), - &nfa::Transition::Ref(r) => *dfa_transitions - .ref_transitions - .entry(r) - .or_insert_with(|| mapped_state.unwrap_or_else(State::new)), - }; + let next_dfa_state = dfa_transitions.entry(*nfa_transition).or_insert_with(|| { + [mapped_state.unwrap_or_else(State::new)].into_iter().collect() + }); + let next_dfa_state = *next_dfa_state.iter().exactly_one().unwrap(); for &next_nfa_state in next_nfa_states { nfa_to_dfa.entry(next_nfa_state).or_insert_with(|| { @@ -143,40 +87,38 @@ where } } - let dfa_accepting = nfa_to_dfa[&nfa_accepting]; - - Self { transitions: dfa_transitions, start: dfa_start, accepting: dfa_accepting } - } - - pub(crate) fn bytes_from(&self, start: State) -> Option<&Map> { - Some(&self.transitions.get(&start)?.byte_transitions) + let dfa_accept = nfa_to_dfa[&nfa_accept]; + Dfa(Automaton { transitions: dfa_transitions, start: dfa_start, accept: dfa_accept }) } pub(crate) fn byte_from(&self, start: State, byte: Byte) -> Option { - self.transitions.get(&start)?.byte_transitions.get(&byte).copied() - } - - pub(crate) fn refs_from(&self, start: State) -> Option<&Map> { - Some(&self.transitions.get(&start)?.ref_transitions) + Some( + self.0 + .transitions + .get(&start)? + .get(&Transition::Byte(byte))? + .iter() + .copied() + .exactly_one() + .unwrap(), + ) } -} -impl State { - pub(crate) fn new() -> Self { - static COUNTER: AtomicU32 = AtomicU32::new(0); - Self(COUNTER.fetch_add(1, Ordering::SeqCst)) + pub(crate) fn iter_bytes_from(&self, start: State) -> impl Iterator { + self.0.transitions.get(&start).into_iter().flat_map(|transitions| { + transitions.iter().filter_map(|(t, s)| { + let s = s.iter().copied().exactly_one().unwrap(); + if let Transition::Byte(b) = t { Some((*b, s)) } else { None } + }) + }) } -} -#[cfg(test)] -impl From> for Transition -where - R: Ref, -{ - fn from(nfa_transition: nfa::Transition) -> Self { - match nfa_transition { - nfa::Transition::Byte(byte) => Transition::Byte(byte), - nfa::Transition::Ref(r) => Transition::Ref(r), - } + pub(crate) fn iter_refs_from(&self, start: State) -> impl Iterator { + self.0.transitions.get(&start).into_iter().flat_map(|transitions| { + transitions.iter().filter_map(|(t, s)| { + let s = s.iter().copied().exactly_one().unwrap(); + if let Transition::Ref(r) = t { Some((*r, s)) } else { None } + }) + }) } } diff --git a/compiler/rustc_transmute/src/layout/mod.rs b/compiler/rustc_transmute/src/layout/mod.rs index c4c01a8fac31f..496d8b7d0010e 100644 --- a/compiler/rustc_transmute/src/layout/mod.rs +++ b/compiler/rustc_transmute/src/layout/mod.rs @@ -1,6 +1,8 @@ use std::fmt::{self, Debug}; use std::hash::Hash; +pub(crate) mod automaton; + pub(crate) mod tree; pub(crate) use tree::Tree; diff --git a/compiler/rustc_transmute/src/layout/nfa.rs b/compiler/rustc_transmute/src/layout/nfa.rs index 9c21fd94f03ec..cd3ead544afb0 100644 --- a/compiler/rustc_transmute/src/layout/nfa.rs +++ b/compiler/rustc_transmute/src/layout/nfa.rs @@ -1,52 +1,11 @@ -use std::fmt; -use std::sync::atomic::{AtomicU32, Ordering}; - +use super::automaton::{Automaton, State, Transition}; use super::{Byte, Ref, Tree, Uninhabited}; use crate::{Map, Set}; /// A non-deterministic finite automaton (NFA) that represents the layout of a type. /// The transmutability of two given types is computed by comparing their `Nfa`s. #[derive(PartialEq, Debug)] -pub(crate) struct Nfa -where - R: Ref, -{ - pub(crate) transitions: Map, Set>>, - pub(crate) start: State, - pub(crate) accepting: State, -} - -/// The states in a `Nfa` represent byte offsets. -#[derive(Hash, Eq, PartialEq, PartialOrd, Ord, Copy, Clone)] -pub(crate) struct State(u32); - -/// The transitions between states in a `Nfa` reflect bit validity. -#[derive(Hash, Eq, PartialEq, Clone, Copy)] -pub(crate) enum Transition -where - R: Ref, -{ - Byte(Byte), - Ref(R), -} - -impl fmt::Debug for State { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "S_{}", self.0) - } -} - -impl fmt::Debug for Transition -where - R: Ref, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self { - Self::Byte(b) => b.fmt(f), - Self::Ref(r) => r.fmt(f), - } - } -} +pub(crate) struct Nfa(pub(crate) Automaton); impl Nfa where @@ -55,33 +14,33 @@ where pub(crate) fn unit() -> Self { let transitions: Map, Set>> = Map::default(); let start = State::new(); - let accepting = start; + let accept = start; - Nfa { transitions, start, accepting } + Nfa(Automaton { transitions, start, accept }) } pub(crate) fn from_byte(byte: Byte) -> Self { let mut transitions: Map, Set>> = Map::default(); let start = State::new(); - let accepting = State::new(); + let accept = State::new(); let source = transitions.entry(start).or_default(); let edge = source.entry(Transition::Byte(byte)).or_default(); - edge.insert(accepting); + edge.insert(accept); - Nfa { transitions, start, accepting } + Nfa(Automaton { transitions, start, accept }) } pub(crate) fn from_ref(r: R) -> Self { let mut transitions: Map, Set>> = Map::default(); let start = State::new(); - let accepting = State::new(); + let accept = State::new(); let source = transitions.entry(start).or_default(); let edge = source.entry(Transition::Ref(r)).or_default(); - edge.insert(accepting); + edge.insert(accept); - Nfa { transitions, start, accepting } + Nfa(Automaton { transitions, start, accept }) } pub(crate) fn from_tree(tree: Tree) -> Result { @@ -108,19 +67,19 @@ where /// Concatenate two `Nfa`s. pub(crate) fn concat(self, other: Self) -> Self { - if self.start == self.accepting { + if self.0.start == self.0.accept { return other; - } else if other.start == other.accepting { + } else if other.0.start == other.0.accept { return self; } - let start = self.start; - let accepting = other.accepting; + let start = self.0.start; + let accept = other.0.accept; - let mut transitions: Map, Set>> = self.transitions; + let mut transitions: Map, Set>> = self.0.transitions; - for (source, transition) in other.transitions { - let fix_state = |state| if state == other.start { self.accepting } else { state }; + for (source, transition) in other.0.transitions { + let fix_state = |state| if state == other.0.start { self.0.accept } else { state }; let entry = transitions.entry(fix_state(source)).or_default(); for (edge, destinations) in transition { let entry = entry.entry(edge).or_default(); @@ -130,40 +89,34 @@ where } } - Self { transitions, start, accepting } + Nfa(Automaton { transitions, start, accept }) } /// Compute the union of two `Nfa`s. pub(crate) fn union(self, other: Self) -> Self { - let start = self.start; - let accepting = self.accepting; + let start = self.0.start; + let accept = self.0.accept; - let mut transitions: Map, Set>> = self.transitions.clone(); + let mut transitions: Map, Set>> = + self.0.transitions.clone(); - for (&(mut source), transition) in other.transitions.iter() { + for (&(mut source), transition) in other.0.transitions.iter() { // if source is starting state of `other`, replace with starting state of `self` - if source == other.start { - source = self.start; + if source == other.0.start { + source = self.0.start; } let entry = transitions.entry(source).or_default(); for (edge, destinations) in transition { let entry = entry.entry(*edge).or_default(); for &(mut destination) in destinations { - // if dest is accepting state of `other`, replace with accepting state of `self` - if destination == other.accepting { - destination = self.accepting; + // if dest is accept state of `other`, replace with accept state of `self` + if destination == other.0.accept { + destination = self.0.accept; } entry.insert(destination); } } } - Self { transitions, start, accepting } - } -} - -impl State { - pub(crate) fn new() -> Self { - static COUNTER: AtomicU32 = AtomicU32::new(0); - Self(COUNTER.fetch_add(1, Ordering::SeqCst)) + Nfa(Automaton { transitions, start, accept }) } } diff --git a/compiler/rustc_transmute/src/maybe_transmutable/mod.rs b/compiler/rustc_transmute/src/maybe_transmutable/mod.rs index 63fabc9c83d93..8a55533c9e1b4 100644 --- a/compiler/rustc_transmute/src/maybe_transmutable/mod.rs +++ b/compiler/rustc_transmute/src/maybe_transmutable/mod.rs @@ -4,7 +4,8 @@ pub(crate) mod query_context; #[cfg(test)] mod tests; -use crate::layout::{self, Byte, Def, Dfa, Nfa, Ref, Tree, Uninhabited, dfa}; +use crate::layout::automaton::State; +use crate::layout::{self, Byte, Def, Dfa, Nfa, Ref, Tree, Uninhabited}; use crate::maybe_transmutable::query_context::QueryContext; use crate::{Answer, Condition, Map, Reason}; @@ -152,16 +153,16 @@ where { /// Answers whether a `Dfa` is transmutable into another `Dfa`. pub(crate) fn answer(self) -> Answer<::Ref> { - self.answer_memo(&mut Map::default(), self.src.start, self.dst.start) + self.answer_memo(&mut Map::default(), self.src.0.start, self.dst.0.start) } #[inline(always)] #[instrument(level = "debug", skip(self))] fn answer_memo( &self, - cache: &mut Map<(dfa::State, dfa::State), Answer<::Ref>>, - src_state: dfa::State, - dst_state: dfa::State, + cache: &mut Map<(State, State), Answer<::Ref>>, + src_state: State, + dst_state: State, ) -> Answer<::Ref> { if let Some(answer) = cache.get(&(src_state, dst_state)) { answer.clone() @@ -170,10 +171,10 @@ where debug!(src = ?self.src); debug!(dst = ?self.dst); debug!( - src_transitions_len = self.src.transitions.len(), - dst_transitions_len = self.dst.transitions.len() + src_transitions_len = self.src.0.transitions.len(), + dst_transitions_len = self.dst.0.transitions.len() ); - let answer = if dst_state == self.dst.accepting { + let answer = if dst_state == self.dst.0.accept { // truncation: `size_of(Src) >= size_of(Dst)` // // Why is truncation OK to do? Because even though the Src is bigger, all we care about @@ -190,7 +191,7 @@ where // that none of the actually-used data can introduce an invalid state for Dst's type, we // are able to safely transmute, even with truncation. Answer::Yes - } else if src_state == self.src.accepting { + } else if src_state == self.src.0.accept { // extension: `size_of(Src) >= size_of(Dst)` if let Some(dst_state_prime) = self.dst.byte_from(dst_state, Byte::Uninit) { self.answer_memo(cache, src_state, dst_state_prime) @@ -212,26 +213,22 @@ where let bytes_answer = src_quantifier.apply( // for each of the byte transitions out of the `src_state`... - self.src.bytes_from(src_state).unwrap_or(&Map::default()).into_iter().map( - |(&src_validity, &src_state_prime)| { - // ...try to find a matching transition out of `dst_state`. - if let Some(dst_state_prime) = - self.dst.byte_from(dst_state, src_validity) - { - self.answer_memo(cache, src_state_prime, dst_state_prime) - } else if let Some(dst_state_prime) = - // otherwise, see if `dst_state` has any outgoing `Uninit` transitions - // (any init byte is a valid uninit byte) - self.dst.byte_from(dst_state, Byte::Uninit) - { - self.answer_memo(cache, src_state_prime, dst_state_prime) - } else { - // otherwise, we've exhausted our options. - // the DFAs, from this point onwards, are bit-incompatible. - Answer::No(Reason::DstIsBitIncompatible) - } - }, - ), + self.src.iter_bytes_from(src_state).map(|(src_validity, src_state_prime)| { + // ...try to find a matching transition out of `dst_state`. + if let Some(dst_state_prime) = self.dst.byte_from(dst_state, src_validity) { + self.answer_memo(cache, src_state_prime, dst_state_prime) + } else if let Some(dst_state_prime) = + // otherwise, see if `dst_state` has any outgoing `Uninit` transitions + // (any init byte is a valid uninit byte) + self.dst.byte_from(dst_state, Byte::Uninit) + { + self.answer_memo(cache, src_state_prime, dst_state_prime) + } else { + // otherwise, we've exhausted our options. + // the DFAs, from this point onwards, are bit-incompatible. + Answer::No(Reason::DstIsBitIncompatible) + } + }), ); // The below early returns reflect how this code would behave: @@ -252,48 +249,38 @@ where let refs_answer = src_quantifier.apply( // for each reference transition out of `src_state`... - self.src.refs_from(src_state).unwrap_or(&Map::default()).into_iter().map( - |(&src_ref, &src_state_prime)| { - // ...there exists a reference transition out of `dst_state`... - Quantifier::ThereExists.apply( - self.dst - .refs_from(dst_state) - .unwrap_or(&Map::default()) - .into_iter() - .map(|(&dst_ref, &dst_state_prime)| { - if !src_ref.is_mutable() && dst_ref.is_mutable() { - Answer::No(Reason::DstIsMoreUnique) - } else if !self.assume.alignment - && src_ref.min_align() < dst_ref.min_align() - { - Answer::No(Reason::DstHasStricterAlignment { - src_min_align: src_ref.min_align(), - dst_min_align: dst_ref.min_align(), - }) - } else if dst_ref.size() > src_ref.size() { - Answer::No(Reason::DstRefIsTooBig { - src: src_ref, - dst: dst_ref, - }) - } else { - // ...such that `src` is transmutable into `dst`, if - // `src_ref` is transmutability into `dst_ref`. - and( - Answer::If(Condition::IfTransmutable { - src: src_ref, - dst: dst_ref, - }), - self.answer_memo( - cache, - src_state_prime, - dst_state_prime, - ), - ) - } - }), - ) - }, - ), + self.src.iter_refs_from(src_state).map(|(src_ref, src_state_prime)| { + // ...there exists a reference transition out of `dst_state`... + Quantifier::ThereExists.apply(self.dst.iter_refs_from(dst_state).map( + |(dst_ref, dst_state_prime)| { + if !src_ref.is_mutable() && dst_ref.is_mutable() { + Answer::No(Reason::DstIsMoreUnique) + } else if !self.assume.alignment + && src_ref.min_align() < dst_ref.min_align() + { + Answer::No(Reason::DstHasStricterAlignment { + src_min_align: src_ref.min_align(), + dst_min_align: dst_ref.min_align(), + }) + } else if dst_ref.size() > src_ref.size() { + Answer::No(Reason::DstRefIsTooBig { + src: src_ref, + dst: dst_ref, + }) + } else { + // ...such that `src` is transmutable into `dst`, if + // `src_ref` is transmutability into `dst_ref`. + and( + Answer::If(Condition::IfTransmutable { + src: src_ref, + dst: dst_ref, + }), + self.answer_memo(cache, src_state_prime, dst_state_prime), + ) + } + }, + )) + }), ); if self.assume.validity { diff --git a/compiler/rustc_transmute/src/maybe_transmutable/tests.rs b/compiler/rustc_transmute/src/maybe_transmutable/tests.rs index 4d81382eba02c..69a6b1b77f4b0 100644 --- a/compiler/rustc_transmute/src/maybe_transmutable/tests.rs +++ b/compiler/rustc_transmute/src/maybe_transmutable/tests.rs @@ -1,93 +1,115 @@ use itertools::Itertools; use super::query_context::test::{Def, UltraMinimal}; -use crate::maybe_transmutable::MaybeTransmutableQuery; -use crate::{Reason, layout}; +use crate::{Answer, Assume, Reason, layout}; -mod safety { - use super::*; - use crate::Answer; +type Tree = layout::Tree; +type Dfa = layout::Dfa; - type Tree = layout::Tree; +trait Representation { + fn is_transmutable(src: Self, dst: Self, assume: Assume) -> Answer; +} - const DST_HAS_SAFETY_INVARIANTS: Answer = - Answer::No(crate::Reason::DstMayHaveSafetyInvariants); +impl Representation for Tree { + fn is_transmutable(src: Self, dst: Self, assume: Assume) -> Answer { + crate::maybe_transmutable::MaybeTransmutableQuery::new(src, dst, assume, UltraMinimal) + .answer() + } +} - fn is_transmutable(src: &Tree, dst: &Tree, assume_safety: bool) -> 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. - let assume = crate::Assume { - alignment: true, - lifetimes: true, - validity: true, - safety: assume_safety, - }; +impl Representation for Dfa { + fn is_transmutable(src: Self, dst: Self, assume: Assume) -> Answer { crate::maybe_transmutable::MaybeTransmutableQuery::new(src, dst, assume, UltraMinimal) .answer() } +} + +fn is_transmutable( + 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, false), Answer::Yes); - assert_eq!(is_transmutable(&src, &dst, true), Answer::Yes); + 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, false), DST_HAS_SAFETY_INVARIANTS); - assert_eq!(is_transmutable(&src, &dst, true), Answer::Yes); + 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, false), Answer::Yes); - assert_eq!(is_transmutable(&src, &dst, true), Answer::Yes); + 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, false), DST_HAS_SAFETY_INVARIANTS); - assert_eq!(is_transmutable(&src, &dst, true), Answer::Yes); + 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 bool { use super::*; - use crate::Answer; #[test] fn should_permit_identity_transmutation_tree() { - let answer = crate::maybe_transmutable::MaybeTransmutableQuery::new( - layout::Tree::::bool(), - layout::Tree::::bool(), - crate::Assume { alignment: false, lifetimes: false, validity: true, safety: false }, - UltraMinimal, - ) - .answer(); - assert_eq!(answer, Answer::Yes); + 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 answer = crate::maybe_transmutable::MaybeTransmutableQuery::new( - layout::Dfa::::bool(), - layout::Dfa::::bool(), - crate::Assume { alignment: false, lifetimes: false, validity: true, safety: false }, - UltraMinimal, - ) - .answer(); - assert_eq!(answer, Answer::Yes); + 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] @@ -122,13 +144,7 @@ mod bool { if src_set.is_subset(&dst_set) { assert_eq!( Answer::Yes, - MaybeTransmutableQuery::new( - src_layout.clone(), - dst_layout.clone(), - crate::Assume { validity: false, ..crate::Assume::default() }, - UltraMinimal, - ) - .answer(), + is_transmutable(&src_layout, &dst_layout, Assume::default()), "{:?} SHOULD be transmutable into {:?}", src_layout, dst_layout @@ -136,13 +152,11 @@ mod bool { } else if !src_set.is_disjoint(&dst_set) { assert_eq!( Answer::Yes, - MaybeTransmutableQuery::new( - src_layout.clone(), - dst_layout.clone(), - crate::Assume { validity: true, ..crate::Assume::default() }, - UltraMinimal, - ) - .answer(), + is_transmutable( + &src_layout, + &dst_layout, + Assume { validity: true, ..Assume::default() } + ), "{:?} SHOULD be transmutable (assuming validity) into {:?}", src_layout, dst_layout @@ -150,13 +164,7 @@ mod bool { } else { assert_eq!( Answer::No(Reason::DstIsBitIncompatible), - MaybeTransmutableQuery::new( - src_layout.clone(), - dst_layout.clone(), - crate::Assume { validity: false, ..crate::Assume::default() }, - UltraMinimal, - ) - .answer(), + is_transmutable(&src_layout, &dst_layout, Assume::default()), "{:?} should NOT be transmutable into {:?}", src_layout, dst_layout