rustc_transmute/maybe_transmutable/
mod.rs

1use tracing::{debug, instrument, trace};
2
3pub(crate) mod query_context;
4#[cfg(test)]
5mod tests;
6
7use crate::layout::{self, Byte, Def, Dfa, Nfa, Ref, Tree, Uninhabited, dfa};
8use crate::maybe_transmutable::query_context::QueryContext;
9use crate::{Answer, Condition, Map, Reason};
10
11pub(crate) struct MaybeTransmutableQuery<L, C>
12where
13    C: QueryContext,
14{
15    src: L,
16    dst: L,
17    assume: crate::Assume,
18    context: C,
19}
20
21impl<L, C> MaybeTransmutableQuery<L, C>
22where
23    C: QueryContext,
24{
25    pub(crate) fn new(src: L, dst: L, assume: crate::Assume, context: C) -> Self {
26        Self { src, dst, assume, context }
27    }
28}
29
30// FIXME: Nix this cfg, so we can write unit tests independently of rustc
31#[cfg(feature = "rustc")]
32mod rustc {
33    use rustc_middle::ty::layout::LayoutCx;
34    use rustc_middle::ty::{Ty, TyCtxt, TypingEnv};
35
36    use super::*;
37    use crate::layout::tree::rustc::Err;
38
39    impl<'tcx> MaybeTransmutableQuery<Ty<'tcx>, TyCtxt<'tcx>> {
40        /// This method begins by converting `src` and `dst` from `Ty`s to `Tree`s,
41        /// then computes an answer using those trees.
42        #[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))]
43        pub(crate) fn answer(self) -> Answer<<TyCtxt<'tcx> as QueryContext>::Ref> {
44            let Self { src, dst, assume, context } = self;
45
46            let layout_cx = LayoutCx::new(context, TypingEnv::fully_monomorphized());
47
48            // Convert `src` and `dst` from their rustc representations, to `Tree`-based
49            // representations.
50            let src = Tree::from_ty(src, layout_cx);
51            let dst = Tree::from_ty(dst, layout_cx);
52
53            match (src, dst) {
54                (Err(Err::TypeError(_)), _) | (_, Err(Err::TypeError(_))) => {
55                    Answer::No(Reason::TypeError)
56                }
57                (Err(Err::UnknownLayout), _) => Answer::No(Reason::SrcLayoutUnknown),
58                (_, Err(Err::UnknownLayout)) => Answer::No(Reason::DstLayoutUnknown),
59                (Err(Err::NotYetSupported), _) => Answer::No(Reason::SrcIsNotYetSupported),
60                (_, Err(Err::NotYetSupported)) => Answer::No(Reason::DstIsNotYetSupported),
61                (Err(Err::SizeOverflow), _) => Answer::No(Reason::SrcSizeOverflow),
62                (_, Err(Err::SizeOverflow)) => Answer::No(Reason::DstSizeOverflow),
63                (Ok(src), Ok(dst)) => MaybeTransmutableQuery { src, dst, assume, context }.answer(),
64            }
65        }
66    }
67}
68
69impl<C> MaybeTransmutableQuery<Tree<<C as QueryContext>::Def, <C as QueryContext>::Ref>, C>
70where
71    C: QueryContext,
72{
73    /// Answers whether a `Tree` is transmutable into another `Tree`.
74    ///
75    /// This method begins by de-def'ing `src` and `dst`, and prunes private paths from `dst`,
76    /// then converts `src` and `dst` to `Nfa`s, and computes an answer using those NFAs.
77    #[inline(always)]
78    #[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))]
79    pub(crate) fn answer(self) -> Answer<<C as QueryContext>::Ref> {
80        let Self { src, dst, assume, context } = self;
81
82        // Unconditionally all `Def` nodes from `src`, without pruning away the
83        // branches they appear in. This is valid to do for value-to-value
84        // transmutations, but not for `&mut T` to `&mut U`; we will need to be
85        // more sophisticated to handle transmutations between mutable
86        // references.
87        let src = src.prune(&|def| false);
88
89        if src.is_inhabited() && !dst.is_inhabited() {
90            return Answer::No(Reason::DstUninhabited);
91        }
92
93        trace!(?src, "pruned src");
94
95        // Remove all `Def` nodes from `dst`, additionally...
96        let dst = if assume.safety {
97            // ...if safety is assumed, don't check if they carry safety
98            // invariants; retain all paths.
99            dst.prune(&|def| false)
100        } else {
101            // ...otherwise, prune away all paths with safety invariants from
102            // the `Dst` layout.
103            dst.prune(&|def| def.has_safety_invariants())
104        };
105
106        trace!(?dst, "pruned dst");
107
108        // Convert `src` from a tree-based representation to an NFA-based
109        // representation. If the conversion fails because `src` is uninhabited,
110        // conclude that the transmutation is acceptable, because instances of
111        // the `src` type do not exist.
112        let src = match Nfa::from_tree(src) {
113            Ok(src) => src,
114            Err(Uninhabited) => return Answer::Yes,
115        };
116
117        // Convert `dst` from a tree-based representation to an NFA-based
118        // representation. If the conversion fails because `src` is uninhabited,
119        // conclude that the transmutation is unacceptable. Valid instances of
120        // the `dst` type do not exist, either because it's genuinely
121        // uninhabited, or because there are no branches of the tree that are
122        // free of safety invariants.
123        let dst = match Nfa::from_tree(dst) {
124            Ok(dst) => dst,
125            Err(Uninhabited) => return Answer::No(Reason::DstMayHaveSafetyInvariants),
126        };
127
128        MaybeTransmutableQuery { src, dst, assume, context }.answer()
129    }
130}
131
132impl<C> MaybeTransmutableQuery<Nfa<<C as QueryContext>::Ref>, C>
133where
134    C: QueryContext,
135{
136    /// Answers whether a `Nfa` is transmutable into another `Nfa`.
137    ///
138    /// This method converts `src` and `dst` to DFAs, then computes an answer using those DFAs.
139    #[inline(always)]
140    #[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))]
141    pub(crate) fn answer(self) -> Answer<<C as QueryContext>::Ref> {
142        let Self { src, dst, assume, context } = self;
143        let src = Dfa::from_nfa(src);
144        let dst = Dfa::from_nfa(dst);
145        MaybeTransmutableQuery { src, dst, assume, context }.answer()
146    }
147}
148
149impl<C> MaybeTransmutableQuery<Dfa<<C as QueryContext>::Ref>, C>
150where
151    C: QueryContext,
152{
153    /// Answers whether a `Dfa` is transmutable into another `Dfa`.
154    pub(crate) fn answer(self) -> Answer<<C as QueryContext>::Ref> {
155        self.answer_memo(&mut Map::default(), self.src.start, self.dst.start)
156    }
157
158    #[inline(always)]
159    #[instrument(level = "debug", skip(self))]
160    fn answer_memo(
161        &self,
162        cache: &mut Map<(dfa::State, dfa::State), Answer<<C as QueryContext>::Ref>>,
163        src_state: dfa::State,
164        dst_state: dfa::State,
165    ) -> Answer<<C as QueryContext>::Ref> {
166        if let Some(answer) = cache.get(&(src_state, dst_state)) {
167            answer.clone()
168        } else {
169            debug!(?src_state, ?dst_state);
170            debug!(src = ?self.src);
171            debug!(dst = ?self.dst);
172            debug!(
173                src_transitions_len = self.src.transitions.len(),
174                dst_transitions_len = self.dst.transitions.len()
175            );
176            let answer = if dst_state == self.dst.accepting {
177                // truncation: `size_of(Src) >= size_of(Dst)`
178                //
179                // Why is truncation OK to do? Because even though the Src is bigger, all we care about
180                // is whether we have enough data for the Dst to be valid in accordance with what its
181                // type dictates.
182                // For example, in a u8 to `()` transmutation, we have enough data available from the u8
183                // to transmute it to a `()` (though in this case does `()` really need any data to
184                // begin with? It doesn't). Same thing with u8 to fieldless struct.
185                // Now then, why is something like u8 to bool not allowed? That is not because the bool
186                // is smaller in size, but rather because those 2 bits that we are re-interpreting from
187                // the u8 could introduce invalid states for the bool type.
188                //
189                // So, if it's possible to transmute to a smaller Dst by truncating, and we can guarantee
190                // that none of the actually-used data can introduce an invalid state for Dst's type, we
191                // are able to safely transmute, even with truncation.
192                Answer::Yes
193            } else if src_state == self.src.accepting {
194                // extension: `size_of(Src) >= size_of(Dst)`
195                if let Some(dst_state_prime) = self.dst.byte_from(dst_state, Byte::Uninit) {
196                    self.answer_memo(cache, src_state, dst_state_prime)
197                } else {
198                    Answer::No(Reason::DstIsTooBig)
199                }
200            } else {
201                let src_quantifier = if self.assume.validity {
202                    // if the compiler may assume that the programmer is doing additional validity checks,
203                    // (e.g.: that `src != 3u8` when the destination type is `bool`)
204                    // then there must exist at least one transition out of `src_state` such that the transmute is viable...
205                    Quantifier::ThereExists
206                } else {
207                    // if the compiler cannot assume that the programmer is doing additional validity checks,
208                    // then for all transitions out of `src_state`, such that the transmute is viable...
209                    // then there must exist at least one transition out of `dst_state` such that the transmute is viable...
210                    Quantifier::ForAll
211                };
212
213                let bytes_answer = src_quantifier.apply(
214                    // for each of the byte transitions out of the `src_state`...
215                    self.src.bytes_from(src_state).unwrap_or(&Map::default()).into_iter().map(
216                        |(&src_validity, &src_state_prime)| {
217                            // ...try to find a matching transition out of `dst_state`.
218                            if let Some(dst_state_prime) =
219                                self.dst.byte_from(dst_state, src_validity)
220                            {
221                                self.answer_memo(cache, src_state_prime, dst_state_prime)
222                            } else if let Some(dst_state_prime) =
223                                // otherwise, see if `dst_state` has any outgoing `Uninit` transitions
224                                // (any init byte is a valid uninit byte)
225                                self.dst.byte_from(dst_state, Byte::Uninit)
226                            {
227                                self.answer_memo(cache, src_state_prime, dst_state_prime)
228                            } else {
229                                // otherwise, we've exhausted our options.
230                                // the DFAs, from this point onwards, are bit-incompatible.
231                                Answer::No(Reason::DstIsBitIncompatible)
232                            }
233                        },
234                    ),
235                );
236
237                // The below early returns reflect how this code would behave:
238                //   if self.assume.validity {
239                //       or(bytes_answer, refs_answer)
240                //   } else {
241                //       and(bytes_answer, refs_answer)
242                //   }
243                // ...if `refs_answer` was computed lazily. The below early
244                // returns can be deleted without impacting the correctness of
245                // the algorithm; only its performance.
246                debug!(?bytes_answer);
247                match bytes_answer {
248                    Answer::No(_) if !self.assume.validity => return bytes_answer,
249                    Answer::Yes if self.assume.validity => return bytes_answer,
250                    _ => {}
251                };
252
253                let refs_answer = src_quantifier.apply(
254                    // for each reference transition out of `src_state`...
255                    self.src.refs_from(src_state).unwrap_or(&Map::default()).into_iter().map(
256                        |(&src_ref, &src_state_prime)| {
257                            // ...there exists a reference transition out of `dst_state`...
258                            Quantifier::ThereExists.apply(
259                                self.dst
260                                    .refs_from(dst_state)
261                                    .unwrap_or(&Map::default())
262                                    .into_iter()
263                                    .map(|(&dst_ref, &dst_state_prime)| {
264                                        if !src_ref.is_mutable() && dst_ref.is_mutable() {
265                                            Answer::No(Reason::DstIsMoreUnique)
266                                        } else if !self.assume.alignment
267                                            && src_ref.min_align() < dst_ref.min_align()
268                                        {
269                                            Answer::No(Reason::DstHasStricterAlignment {
270                                                src_min_align: src_ref.min_align(),
271                                                dst_min_align: dst_ref.min_align(),
272                                            })
273                                        } else if dst_ref.size() > src_ref.size() {
274                                            Answer::No(Reason::DstRefIsTooBig {
275                                                src: src_ref,
276                                                dst: dst_ref,
277                                            })
278                                        } else {
279                                            // ...such that `src` is transmutable into `dst`, if
280                                            // `src_ref` is transmutability into `dst_ref`.
281                                            and(
282                                                Answer::If(Condition::IfTransmutable {
283                                                    src: src_ref,
284                                                    dst: dst_ref,
285                                                }),
286                                                self.answer_memo(
287                                                    cache,
288                                                    src_state_prime,
289                                                    dst_state_prime,
290                                                ),
291                                            )
292                                        }
293                                    }),
294                            )
295                        },
296                    ),
297                );
298
299                if self.assume.validity {
300                    or(bytes_answer, refs_answer)
301                } else {
302                    and(bytes_answer, refs_answer)
303                }
304            };
305            if let Some(..) = cache.insert((src_state, dst_state), answer.clone()) {
306                panic!("failed to correctly cache transmutability")
307            }
308            answer
309        }
310    }
311}
312
313fn and<R>(lhs: Answer<R>, rhs: Answer<R>) -> Answer<R>
314where
315    R: PartialEq,
316{
317    match (lhs, rhs) {
318        // If both are errors, then we should return the more specific one
319        (Answer::No(Reason::DstIsBitIncompatible), Answer::No(reason))
320        | (Answer::No(reason), Answer::No(_))
321        // If either is an error, return it
322        | (Answer::No(reason), _) | (_, Answer::No(reason)) => Answer::No(reason),
323        // If only one side has a condition, pass it along
324        | (Answer::Yes, other) | (other, Answer::Yes) => other,
325        // If both sides have IfAll conditions, merge them
326        (Answer::If(Condition::IfAll(mut lhs)), Answer::If(Condition::IfAll(ref mut rhs))) => {
327            lhs.append(rhs);
328            Answer::If(Condition::IfAll(lhs))
329        }
330        // If only one side is an IfAll, add the other Condition to it
331        (Answer::If(cond), Answer::If(Condition::IfAll(mut conds)))
332        | (Answer::If(Condition::IfAll(mut conds)), Answer::If(cond)) => {
333            conds.push(cond);
334            Answer::If(Condition::IfAll(conds))
335        }
336        // Otherwise, both lhs and rhs conditions can be combined in a parent IfAll
337        (Answer::If(lhs), Answer::If(rhs)) => Answer::If(Condition::IfAll(vec![lhs, rhs])),
338    }
339}
340
341fn or<R>(lhs: Answer<R>, rhs: Answer<R>) -> Answer<R>
342where
343    R: PartialEq,
344{
345    match (lhs, rhs) {
346        // If both are errors, then we should return the more specific one
347        (Answer::No(Reason::DstIsBitIncompatible), Answer::No(reason))
348        | (Answer::No(reason), Answer::No(_)) => Answer::No(reason),
349        // Otherwise, errors can be ignored for the rest of the pattern matching
350        (Answer::No(_), other) | (other, Answer::No(_)) => or(other, Answer::Yes),
351        // If only one side has a condition, pass it along
352        (Answer::Yes, other) | (other, Answer::Yes) => other,
353        // If both sides have IfAny conditions, merge them
354        (Answer::If(Condition::IfAny(mut lhs)), Answer::If(Condition::IfAny(ref mut rhs))) => {
355            lhs.append(rhs);
356            Answer::If(Condition::IfAny(lhs))
357        }
358        // If only one side is an IfAny, add the other Condition to it
359        (Answer::If(cond), Answer::If(Condition::IfAny(mut conds)))
360        | (Answer::If(Condition::IfAny(mut conds)), Answer::If(cond)) => {
361            conds.push(cond);
362            Answer::If(Condition::IfAny(conds))
363        }
364        // Otherwise, both lhs and rhs conditions can be combined in a parent IfAny
365        (Answer::If(lhs), Answer::If(rhs)) => Answer::If(Condition::IfAny(vec![lhs, rhs])),
366    }
367}
368
369enum Quantifier {
370    ThereExists,
371    ForAll,
372}
373
374impl Quantifier {
375    fn apply<R, I>(&self, iter: I) -> Answer<R>
376    where
377        R: layout::Ref,
378        I: IntoIterator<Item = Answer<R>>,
379    {
380        use std::ops::ControlFlow::{Break, Continue};
381
382        let (init, try_fold_f): (_, fn(_, _) -> _) = match self {
383            Self::ThereExists => {
384                (Answer::No(Reason::DstIsBitIncompatible), |accum: Answer<R>, next| {
385                    match or(accum, next) {
386                        Answer::Yes => Break(Answer::Yes),
387                        maybe => Continue(maybe),
388                    }
389                })
390            }
391            Self::ForAll => (Answer::Yes, |accum: Answer<R>, next| {
392                let answer = and(accum, next);
393                match answer {
394                    Answer::No(_) => Break(answer),
395                    maybe => Continue(maybe),
396                }
397            }),
398        };
399
400        let (Continue(result) | Break(result)) = iter.into_iter().try_fold(init, try_fold_f);
401        result
402    }
403}