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