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}