Skip to content

Commit 0138df1

Browse files
committed
transmutability: ensure_sufficient_stack when answering query
1 parent 88a8679 commit 0138df1

File tree

1 file changed

+125
-117
lines changed
  • compiler/rustc_transmute/src/maybe_transmutable

1 file changed

+125
-117
lines changed

compiler/rustc_transmute/src/maybe_transmutable/mod.rs

+125-117
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use rustc_data_structures::stack::ensure_sufficient_stack;
12
use tracing::{debug, instrument, trace};
23

34
pub(crate) mod query_context;
@@ -149,128 +150,135 @@ where
149150
if let Some(answer) = cache.get(&(src_state, dst_state)) {
150151
answer.clone()
151152
} else {
152-
debug!(?src_state, ?dst_state);
153-
debug!(src = ?self.src);
154-
debug!(dst = ?self.dst);
155-
debug!(
156-
src_transitions_len = self.src.transitions.len(),
157-
dst_transitions_len = self.dst.transitions.len()
158-
);
159-
let answer = if dst_state == self.dst.accept {
160-
// truncation: `size_of(Src) >= size_of(Dst)`
161-
//
162-
// Why is truncation OK to do? Because even though the Src is bigger, all we care about
163-
// is whether we have enough data for the Dst to be valid in accordance with what its
164-
// type dictates.
165-
// For example, in a u8 to `()` transmutation, we have enough data available from the u8
166-
// to transmute it to a `()` (though in this case does `()` really need any data to
167-
// begin with? It doesn't). Same thing with u8 to fieldless struct.
168-
// Now then, why is something like u8 to bool not allowed? That is not because the bool
169-
// is smaller in size, but rather because those 2 bits that we are re-interpreting from
170-
// the u8 could introduce invalid states for the bool type.
171-
//
172-
// So, if it's possible to transmute to a smaller Dst by truncating, and we can guarantee
173-
// that none of the actually-used data can introduce an invalid state for Dst's type, we
174-
// are able to safely transmute, even with truncation.
175-
Answer::Yes
176-
} else if src_state == self.src.accept {
177-
// extension: `size_of(Src) <= size_of(Dst)`
178-
if let Some(dst_state_prime) = self.dst.get_uninit_edge_dst(dst_state) {
179-
self.answer_memo(cache, src_state, dst_state_prime)
180-
} else {
181-
Answer::No(Reason::DstIsTooBig)
182-
}
153+
let answer = ensure_sufficient_stack(|| self.answer_impl(cache, src_state, dst_state));
154+
if let Some(..) = cache.insert((src_state, dst_state), answer.clone()) {
155+
panic!("failed to correctly cache transmutability")
156+
}
157+
answer
158+
}
159+
}
160+
161+
fn answer_impl(
162+
&self,
163+
cache: &mut Map<(dfa::State, dfa::State), Answer<<C as QueryContext>::Ref>>,
164+
src_state: dfa::State,
165+
dst_state: dfa::State,
166+
) -> Answer<<C as QueryContext>::Ref> {
167+
debug!(?src_state, ?dst_state);
168+
debug!(src = ?self.src);
169+
debug!(dst = ?self.dst);
170+
debug!(
171+
src_transitions_len = self.src.transitions.len(),
172+
dst_transitions_len = self.dst.transitions.len()
173+
);
174+
if dst_state == self.dst.accept {
175+
// truncation: `size_of(Src) >= size_of(Dst)`
176+
//
177+
// Why is truncation OK to do? Because even though the Src is bigger, all we care about
178+
// is whether we have enough data for the Dst to be valid in accordance with what its
179+
// type dictates.
180+
// For example, in a u8 to `()` transmutation, we have enough data available from the u8
181+
// to transmute it to a `()` (though in this case does `()` really need any data to
182+
// begin with? It doesn't). Same thing with u8 to fieldless struct.
183+
// Now then, why is something like u8 to bool not allowed? That is not because the bool
184+
// is smaller in size, but rather because those 2 bits that we are re-interpreting from
185+
// the u8 could introduce invalid states for the bool type.
186+
//
187+
// So, if it's possible to transmute to a smaller Dst by truncating, and we can guarantee
188+
// that none of the actually-used data can introduce an invalid state for Dst's type, we
189+
// are able to safely transmute, even with truncation.
190+
Answer::Yes
191+
} else if src_state == self.src.accept {
192+
// extension: `size_of(Src) <= size_of(Dst)`
193+
if let Some(dst_state_prime) = self.dst.get_uninit_edge_dst(dst_state) {
194+
self.answer_memo(cache, src_state, dst_state_prime)
195+
} else {
196+
Answer::No(Reason::DstIsTooBig)
197+
}
198+
} else {
199+
let src_quantifier = if self.assume.validity {
200+
// if the compiler may assume that the programmer is doing additional validity checks,
201+
// (e.g.: that `src != 3u8` when the destination type is `bool`)
202+
// then there must exist at least one transition out of `src_state` such that the transmute is viable...
203+
Quantifier::ThereExists
183204
} else {
184-
let src_quantifier = if self.assume.validity {
185-
// if the compiler may assume that the programmer is doing additional validity checks,
186-
// (e.g.: that `src != 3u8` when the destination type is `bool`)
187-
// then there must exist at least one transition out of `src_state` such that the transmute is viable...
188-
Quantifier::ThereExists
189-
} else {
190-
// if the compiler cannot assume that the programmer is doing additional validity checks,
191-
// then for all transitions out of `src_state`, such that the transmute is viable...
192-
// then there must exist at least one transition out of `dst_state` such that the transmute is viable...
193-
Quantifier::ForAll
194-
};
195-
196-
let bytes_answer = src_quantifier.apply(
197-
union(self.src.bytes_from(src_state), self.dst.bytes_from(dst_state))
198-
.filter_map(|(_range, (src_state_prime, dst_state_prime))| {
199-
match (src_state_prime, dst_state_prime) {
200-
// No matching transitions in `src`. Skip.
201-
(None, _) => None,
202-
// No matching transitions in `dst`. Fail.
203-
(Some(_), None) => Some(Answer::No(Reason::DstIsBitIncompatible)),
204-
// Matching transitions. Continue with successor states.
205-
(Some(src_state_prime), Some(dst_state_prime)) => {
206-
Some(self.answer_memo(cache, src_state_prime, dst_state_prime))
207-
}
205+
// if the compiler cannot assume that the programmer is doing additional validity checks,
206+
// then for all transitions out of `src_state`, such that the transmute is viable...
207+
// then there must exist at least one transition out of `dst_state` such that the transmute is viable...
208+
Quantifier::ForAll
209+
};
210+
211+
let bytes_answer = src_quantifier.apply(
212+
union(self.src.bytes_from(src_state), self.dst.bytes_from(dst_state)).filter_map(
213+
|(_range, (src_state_prime, dst_state_prime))| {
214+
match (src_state_prime, dst_state_prime) {
215+
// No matching transitions in `src`. Skip.
216+
(None, _) => None,
217+
// No matching transitions in `dst`. Fail.
218+
(Some(_), None) => Some(Answer::No(Reason::DstIsBitIncompatible)),
219+
// Matching transitions. Continue with successor states.
220+
(Some(src_state_prime), Some(dst_state_prime)) => {
221+
Some(self.answer_memo(cache, src_state_prime, dst_state_prime))
208222
}
209-
}),
210-
);
211-
212-
// The below early returns reflect how this code would behave:
213-
// if self.assume.validity {
214-
// or(bytes_answer, refs_answer)
215-
// } else {
216-
// and(bytes_answer, refs_answer)
217-
// }
218-
// ...if `refs_answer` was computed lazily. The below early
219-
// returns can be deleted without impacting the correctness of
220-
// the algorithm; only its performance.
221-
debug!(?bytes_answer);
222-
match bytes_answer {
223-
Answer::No(_) if !self.assume.validity => return bytes_answer,
224-
Answer::Yes if self.assume.validity => return bytes_answer,
225-
_ => {}
226-
};
227-
228-
let refs_answer = src_quantifier.apply(
229-
// for each reference transition out of `src_state`...
230-
self.src.refs_from(src_state).map(|(src_ref, src_state_prime)| {
231-
// ...there exists a reference transition out of `dst_state`...
232-
Quantifier::ThereExists.apply(self.dst.refs_from(dst_state).map(
233-
|(dst_ref, dst_state_prime)| {
234-
if !src_ref.is_mutable() && dst_ref.is_mutable() {
235-
Answer::No(Reason::DstIsMoreUnique)
236-
} else if !self.assume.alignment
237-
&& src_ref.min_align() < dst_ref.min_align()
238-
{
239-
Answer::No(Reason::DstHasStricterAlignment {
240-
src_min_align: src_ref.min_align(),
241-
dst_min_align: dst_ref.min_align(),
242-
})
243-
} else if dst_ref.size() > src_ref.size() {
244-
Answer::No(Reason::DstRefIsTooBig {
223+
}
224+
},
225+
),
226+
);
227+
228+
// The below early returns reflect how this code would behave:
229+
// if self.assume.validity {
230+
// or(bytes_answer, refs_answer)
231+
// } else {
232+
// and(bytes_answer, refs_answer)
233+
// }
234+
// ...if `refs_answer` was computed lazily. The below early
235+
// returns can be deleted without impacting the correctness of
236+
// the algorithm; only its performance.
237+
debug!(?bytes_answer);
238+
match bytes_answer {
239+
Answer::No(_) if !self.assume.validity => return bytes_answer,
240+
Answer::Yes if self.assume.validity => return bytes_answer,
241+
_ => {}
242+
};
243+
244+
let refs_answer = src_quantifier.apply(
245+
// for each reference transition out of `src_state`...
246+
self.src.refs_from(src_state).map(|(src_ref, src_state_prime)| {
247+
// ...there exists a reference transition out of `dst_state`...
248+
Quantifier::ThereExists.apply(self.dst.refs_from(dst_state).map(
249+
|(dst_ref, dst_state_prime)| {
250+
if !src_ref.is_mutable() && dst_ref.is_mutable() {
251+
Answer::No(Reason::DstIsMoreUnique)
252+
} else if !self.assume.alignment
253+
&& src_ref.min_align() < dst_ref.min_align()
254+
{
255+
Answer::No(Reason::DstHasStricterAlignment {
256+
src_min_align: src_ref.min_align(),
257+
dst_min_align: dst_ref.min_align(),
258+
})
259+
} else if dst_ref.size() > src_ref.size() {
260+
Answer::No(Reason::DstRefIsTooBig { src: src_ref, dst: dst_ref })
261+
} else {
262+
// ...such that `src` is transmutable into `dst`, if
263+
// `src_ref` is transmutability into `dst_ref`.
264+
and(
265+
Answer::If(Condition::IfTransmutable {
245266
src: src_ref,
246267
dst: dst_ref,
247-
})
248-
} else {
249-
// ...such that `src` is transmutable into `dst`, if
250-
// `src_ref` is transmutability into `dst_ref`.
251-
and(
252-
Answer::If(Condition::IfTransmutable {
253-
src: src_ref,
254-
dst: dst_ref,
255-
}),
256-
self.answer_memo(cache, src_state_prime, dst_state_prime),
257-
)
258-
}
259-
},
260-
))
261-
}),
262-
);
263-
264-
if self.assume.validity {
265-
or(bytes_answer, refs_answer)
266-
} else {
267-
and(bytes_answer, refs_answer)
268-
}
269-
};
270-
if let Some(..) = cache.insert((src_state, dst_state), answer.clone()) {
271-
panic!("failed to correctly cache transmutability")
268+
}),
269+
self.answer_memo(cache, src_state_prime, dst_state_prime),
270+
)
271+
}
272+
},
273+
))
274+
}),
275+
);
276+
277+
if self.assume.validity {
278+
or(bytes_answer, refs_answer)
279+
} else {
280+
and(bytes_answer, refs_answer)
272281
}
273-
answer
274282
}
275283
}
276284
}

0 commit comments

Comments
 (0)