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