]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | use crate::Map; |
2 | use crate::{Answer, Reason}; | |
3 | ||
4 | #[cfg(test)] | |
5 | mod tests; | |
6 | ||
7 | mod query_context; | |
8 | use query_context::QueryContext; | |
9 | ||
10 | use crate::layout::{self, dfa, Byte, Dfa, Nfa, Tree, Uninhabited}; | |
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 | { | |
26 | 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 | pub(crate) fn map_layouts<F, M>( | |
37 | self, | |
38 | f: F, | |
39 | ) -> Result<MaybeTransmutableQuery<M, C>, Answer<<C as QueryContext>::Ref>> | |
40 | where | |
41 | F: FnOnce( | |
42 | L, | |
43 | L, | |
44 | <C as QueryContext>::Scope, | |
45 | &C, | |
46 | ) -> Result<(M, M), Answer<<C as QueryContext>::Ref>>, | |
47 | { | |
48 | let Self { src, dst, scope, assume, context } = self; | |
49 | ||
50 | let (src, dst) = f(src, dst, scope, &context)?; | |
51 | ||
52 | Ok(MaybeTransmutableQuery { src, dst, scope, assume, context }) | |
53 | } | |
54 | } | |
55 | ||
56 | #[cfg(feature = "rustc")] | |
57 | mod rustc { | |
58 | use super::*; | |
353b0b11 | 59 | use crate::layout::tree::rustc::Err; |
064997fb FG |
60 | |
61 | use rustc_middle::ty::Ty; | |
62 | use rustc_middle::ty::TyCtxt; | |
63 | ||
64 | impl<'tcx> MaybeTransmutableQuery<Ty<'tcx>, TyCtxt<'tcx>> { | |
65 | /// This method begins by converting `src` and `dst` from `Ty`s to `Tree`s, | |
66 | /// then computes an answer using those trees. | |
67 | #[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))] | |
68 | pub fn answer(self) -> Answer<<TyCtxt<'tcx> as QueryContext>::Ref> { | |
69 | let query_or_answer = self.map_layouts(|src, dst, scope, &context| { | |
70 | // Convert `src` and `dst` from their rustc representations, to `Tree`-based | |
71 | // representations. If these conversions fail, conclude that the transmutation is | |
72 | // unacceptable; the layouts of both the source and destination types must be | |
73 | // well-defined. | |
353b0b11 FG |
74 | let src = Tree::from_ty(src, context); |
75 | let dst = Tree::from_ty(dst, context); | |
064997fb | 76 | |
353b0b11 FG |
77 | match (src, dst) { |
78 | // Answer `Yes` here, because 'unknown layout' and type errors will already | |
79 | // be reported by rustc. No need to spam the user with more errors. | |
80 | (Err(Err::TypeError(_)), _) => Err(Answer::Yes), | |
81 | (_, Err(Err::TypeError(_))) => Err(Answer::Yes), | |
82 | (Err(Err::Unknown), _) => Err(Answer::Yes), | |
83 | (_, Err(Err::Unknown)) => Err(Answer::Yes), | |
84 | (Err(Err::Unspecified), _) => Err(Answer::No(Reason::SrcIsUnspecified)), | |
85 | (_, Err(Err::Unspecified)) => Err(Answer::No(Reason::DstIsUnspecified)), | |
86 | (Ok(src), Ok(dst)) => Ok((src, dst)), | |
87 | } | |
064997fb FG |
88 | }); |
89 | ||
90 | match query_or_answer { | |
91 | Ok(query) => query.answer(), | |
92 | Err(answer) => answer, | |
93 | } | |
94 | } | |
95 | } | |
96 | } | |
97 | ||
98 | impl<C> MaybeTransmutableQuery<Tree<<C as QueryContext>::Def, <C as QueryContext>::Ref>, C> | |
99 | where | |
100 | C: QueryContext, | |
101 | { | |
102 | /// Answers whether a `Tree` is transmutable into another `Tree`. | |
103 | /// | |
104 | /// This method begins by de-def'ing `src` and `dst`, and prunes private paths from `dst`, | |
105 | /// then converts `src` and `dst` to `Nfa`s, and computes an answer using those NFAs. | |
106 | #[inline(always)] | |
107 | #[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))] | |
108 | pub(crate) fn answer(self) -> Answer<<C as QueryContext>::Ref> { | |
f2b60f7d | 109 | let assume_visibility = self.assume.safety; |
064997fb FG |
110 | let query_or_answer = self.map_layouts(|src, dst, scope, context| { |
111 | // Remove all `Def` nodes from `src`, without checking their visibility. | |
112 | let src = src.prune(&|def| true); | |
113 | ||
f2b60f7d | 114 | trace!(?src, "pruned src"); |
064997fb FG |
115 | |
116 | // Remove all `Def` nodes from `dst`, additionally... | |
117 | let dst = if assume_visibility { | |
118 | // ...if visibility is assumed, don't check their visibility. | |
119 | dst.prune(&|def| true) | |
120 | } else { | |
121 | // ...otherwise, prune away all unreachable paths through the `Dst` layout. | |
122 | dst.prune(&|def| context.is_accessible_from(def, scope)) | |
123 | }; | |
124 | ||
f2b60f7d | 125 | trace!(?dst, "pruned dst"); |
064997fb FG |
126 | |
127 | // Convert `src` from a tree-based representation to an NFA-based representation. | |
128 | // If the conversion fails because `src` is uninhabited, conclude that the transmutation | |
129 | // is acceptable, because instances of the `src` type do not exist. | |
130 | let src = Nfa::from_tree(src).map_err(|Uninhabited| Answer::Yes)?; | |
131 | ||
132 | // Convert `dst` from a tree-based representation to an NFA-based representation. | |
133 | // If the conversion fails because `src` is uninhabited, conclude that the transmutation | |
134 | // is unacceptable, because instances of the `dst` type do not exist. | |
135 | let dst = | |
136 | Nfa::from_tree(dst).map_err(|Uninhabited| Answer::No(Reason::DstIsPrivate))?; | |
137 | ||
138 | Ok((src, dst)) | |
139 | }); | |
140 | ||
141 | match query_or_answer { | |
142 | Ok(query) => query.answer(), | |
143 | Err(answer) => answer, | |
144 | } | |
145 | } | |
146 | } | |
147 | ||
148 | impl<C> MaybeTransmutableQuery<Nfa<<C as QueryContext>::Ref>, C> | |
149 | where | |
150 | C: QueryContext, | |
151 | { | |
152 | /// Answers whether a `Nfa` is transmutable into another `Nfa`. | |
153 | /// | |
154 | /// This method converts `src` and `dst` to DFAs, then computes an answer using those DFAs. | |
155 | #[inline(always)] | |
156 | #[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))] | |
157 | pub(crate) fn answer(self) -> Answer<<C as QueryContext>::Ref> { | |
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. | |
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 | { | |
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))] | |
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 | let answer = if dst_state == self.dst.accepting { | |
207 | // truncation: `size_of(Src) >= size_of(Dst)` | |
208 | Answer::Yes | |
209 | } else if src_state == self.src.accepting { | |
210 | // extension: `size_of(Src) >= size_of(Dst)` | |
211 | if let Some(dst_state_prime) = self.dst.byte_from(dst_state, Byte::Uninit) { | |
212 | self.answer_memo(cache, src_state, dst_state_prime) | |
213 | } else { | |
214 | Answer::No(Reason::DstIsTooBig) | |
215 | } | |
216 | } else { | |
217 | let src_quantification = if self.assume.validity { | |
218 | // if the compiler may assume that the programmer is doing additional validity checks, | |
219 | // (e.g.: that `src != 3u8` when the destination type is `bool`) | |
220 | // then there must exist at least one transition out of `src_state` such that the transmute is viable... | |
221 | there_exists | |
222 | } else { | |
223 | // if the compiler cannot assume that the programmer is doing additional validity checks, | |
224 | // then for all transitions out of `src_state`, such that the transmute is viable... | |
225 | // then there must exist at least one transition out of `src_state` such that the transmute is viable... | |
226 | for_all | |
227 | }; | |
228 | ||
229 | src_quantification( | |
230 | self.src.bytes_from(src_state).unwrap_or(&Map::default()), | |
231 | |(&src_validity, &src_state_prime)| { | |
232 | if let Some(dst_state_prime) = self.dst.byte_from(dst_state, src_validity) { | |
233 | self.answer_memo(cache, src_state_prime, dst_state_prime) | |
234 | } else if let Some(dst_state_prime) = | |
235 | self.dst.byte_from(dst_state, Byte::Uninit) | |
236 | { | |
237 | self.answer_memo(cache, src_state_prime, dst_state_prime) | |
238 | } else { | |
239 | Answer::No(Reason::DstIsBitIncompatible) | |
240 | } | |
241 | }, | |
242 | ) | |
243 | }; | |
244 | cache.insert((src_state, dst_state), answer.clone()); | |
245 | answer | |
246 | } | |
247 | } | |
248 | } | |
249 | ||
250 | impl<R> Answer<R> | |
251 | where | |
252 | R: layout::Ref, | |
253 | { | |
254 | pub(crate) fn and(self, rhs: Self) -> Self { | |
255 | match (self, rhs) { | |
256 | (Self::No(reason), _) | (_, Self::No(reason)) => Self::No(reason), | |
257 | (Self::Yes, Self::Yes) => Self::Yes, | |
258 | (Self::IfAll(mut lhs), Self::IfAll(ref mut rhs)) => { | |
259 | lhs.append(rhs); | |
260 | Self::IfAll(lhs) | |
261 | } | |
262 | (constraint, Self::IfAll(mut constraints)) | |
263 | | (Self::IfAll(mut constraints), constraint) => { | |
264 | constraints.push(constraint); | |
265 | Self::IfAll(constraints) | |
266 | } | |
267 | (lhs, rhs) => Self::IfAll(vec![lhs, rhs]), | |
268 | } | |
269 | } | |
270 | ||
271 | pub(crate) fn or(self, rhs: Self) -> Self { | |
272 | match (self, rhs) { | |
273 | (Self::Yes, _) | (_, Self::Yes) => Self::Yes, | |
274 | (Self::No(lhr), Self::No(rhr)) => Self::No(lhr), | |
275 | (Self::IfAny(mut lhs), Self::IfAny(ref mut rhs)) => { | |
276 | lhs.append(rhs); | |
277 | Self::IfAny(lhs) | |
278 | } | |
279 | (constraint, Self::IfAny(mut constraints)) | |
280 | | (Self::IfAny(mut constraints), constraint) => { | |
281 | constraints.push(constraint); | |
282 | Self::IfAny(constraints) | |
283 | } | |
284 | (lhs, rhs) => Self::IfAny(vec![lhs, rhs]), | |
285 | } | |
286 | } | |
287 | } | |
288 | ||
289 | pub fn for_all<R, I, F>(iter: I, f: F) -> Answer<R> | |
290 | where | |
291 | R: layout::Ref, | |
292 | I: IntoIterator, | |
293 | F: FnMut(<I as IntoIterator>::Item) -> Answer<R>, | |
294 | { | |
295 | use std::ops::ControlFlow::{Break, Continue}; | |
296 | let (Continue(result) | Break(result)) = | |
297 | iter.into_iter().map(f).try_fold(Answer::Yes, |constraints, constraint| { | |
298 | match constraint.and(constraints) { | |
299 | Answer::No(reason) => Break(Answer::No(reason)), | |
300 | maybe => Continue(maybe), | |
301 | } | |
302 | }); | |
303 | result | |
304 | } | |
305 | ||
306 | pub fn there_exists<R, I, F>(iter: I, f: F) -> Answer<R> | |
307 | where | |
308 | R: layout::Ref, | |
309 | I: IntoIterator, | |
310 | F: FnMut(<I as IntoIterator>::Item) -> Answer<R>, | |
311 | { | |
312 | use std::ops::ControlFlow::{Break, Continue}; | |
313 | let (Continue(result) | Break(result)) = iter.into_iter().map(f).try_fold( | |
314 | Answer::No(Reason::DstIsBitIncompatible), | |
315 | |constraints, constraint| match constraint.or(constraints) { | |
316 | Answer::Yes => Break(Answer::Yes), | |
317 | maybe => Continue(maybe), | |
318 | }, | |
319 | ); | |
320 | result | |
321 | } |