Safe Transmute: Refactor error handling and Answer type
- Create `Answer` type that is not just a type alias of `Result` - Remove a usage of `map_layouts` to make the code easier to read - Don't hide errors related to Unknown Layout when computing transmutability
This commit is contained in:
parent
64a54df86f
commit
f4cf8f65a5
9 changed files with 166 additions and 126 deletions
|
@ -668,6 +668,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
||||||
scope: Ty<'tcx>,
|
scope: Ty<'tcx>,
|
||||||
assume: rustc_transmute::Assume,
|
assume: rustc_transmute::Assume,
|
||||||
) -> Result<Certainty, NoSolution> {
|
) -> Result<Certainty, NoSolution> {
|
||||||
|
use rustc_transmute::Answer;
|
||||||
// FIXME(transmutability): This really should be returning nested goals for `Answer::If*`
|
// FIXME(transmutability): This really should be returning nested goals for `Answer::If*`
|
||||||
match rustc_transmute::TransmuteTypeEnv::new(self.infcx).is_transmutable(
|
match rustc_transmute::TransmuteTypeEnv::new(self.infcx).is_transmutable(
|
||||||
ObligationCause::dummy(),
|
ObligationCause::dummy(),
|
||||||
|
@ -675,11 +676,8 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
||||||
scope,
|
scope,
|
||||||
assume,
|
assume,
|
||||||
) {
|
) {
|
||||||
Ok(None) => Ok(Certainty::Yes),
|
Answer::Yes => Ok(Certainty::Yes),
|
||||||
Err(_)
|
Answer::No(_) | Answer::If(_) => Err(NoSolution),
|
||||||
| Ok(Some(rustc_transmute::Condition::IfTransmutable { .. }))
|
|
||||||
| Ok(Some(rustc_transmute::Condition::IfAll(_)))
|
|
||||||
| Ok(Some(rustc_transmute::Condition::IfAny(_))) => Err(NoSolution),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,11 @@ pub struct ImplCandidate<'tcx> {
|
||||||
pub similarity: CandidateSimilarity,
|
pub similarity: CandidateSimilarity,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum GetSafeTransmuteErrorAndReason {
|
||||||
|
Silent,
|
||||||
|
Error { err_msg: String, safe_transmute_explanation: String },
|
||||||
|
}
|
||||||
|
|
||||||
pub trait InferCtxtExt<'tcx> {
|
pub trait InferCtxtExt<'tcx> {
|
||||||
/// Given some node representing a fn-like thing in the HIR map,
|
/// Given some node representing a fn-like thing in the HIR map,
|
||||||
/// returns a span and `ArgKind` information that describes the
|
/// returns a span and `ArgKind` information that describes the
|
||||||
|
@ -724,11 +729,17 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
|
||||||
== self.tcx.lang_items().transmute_trait()
|
== self.tcx.lang_items().transmute_trait()
|
||||||
{
|
{
|
||||||
// Recompute the safe transmute reason and use that for the error reporting
|
// Recompute the safe transmute reason and use that for the error reporting
|
||||||
self.get_safe_transmute_error_and_reason(
|
match self.get_safe_transmute_error_and_reason(
|
||||||
obligation.clone(),
|
obligation.clone(),
|
||||||
trait_ref,
|
trait_ref,
|
||||||
span,
|
span,
|
||||||
)
|
) {
|
||||||
|
GetSafeTransmuteErrorAndReason::Silent => return,
|
||||||
|
GetSafeTransmuteErrorAndReason::Error {
|
||||||
|
err_msg,
|
||||||
|
safe_transmute_explanation,
|
||||||
|
} => (err_msg, Some(safe_transmute_explanation)),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
(err_msg, None)
|
(err_msg, None)
|
||||||
};
|
};
|
||||||
|
@ -1292,7 +1303,7 @@ trait InferCtxtPrivExt<'tcx> {
|
||||||
obligation: PredicateObligation<'tcx>,
|
obligation: PredicateObligation<'tcx>,
|
||||||
trait_ref: ty::PolyTraitRef<'tcx>,
|
trait_ref: ty::PolyTraitRef<'tcx>,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> (String, Option<String>);
|
) -> GetSafeTransmuteErrorAndReason;
|
||||||
|
|
||||||
fn add_tuple_trait_message(
|
fn add_tuple_trait_message(
|
||||||
&self,
|
&self,
|
||||||
|
@ -2738,7 +2749,9 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
|
||||||
obligation: PredicateObligation<'tcx>,
|
obligation: PredicateObligation<'tcx>,
|
||||||
trait_ref: ty::PolyTraitRef<'tcx>,
|
trait_ref: ty::PolyTraitRef<'tcx>,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> (String, Option<String>) {
|
) -> GetSafeTransmuteErrorAndReason {
|
||||||
|
use rustc_transmute::Answer;
|
||||||
|
|
||||||
// Erase regions because layout code doesn't particularly care about regions.
|
// Erase regions because layout code doesn't particularly care about regions.
|
||||||
let trait_ref = self.tcx.erase_regions(self.tcx.erase_late_bound_regions(trait_ref));
|
let trait_ref = self.tcx.erase_regions(self.tcx.erase_late_bound_regions(trait_ref));
|
||||||
|
|
||||||
|
@ -2758,13 +2771,13 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
|
||||||
scope,
|
scope,
|
||||||
assume,
|
assume,
|
||||||
) {
|
) {
|
||||||
Err(reason) => {
|
Answer::No(reason) => {
|
||||||
let dst = trait_ref.substs.type_at(0);
|
let dst = trait_ref.substs.type_at(0);
|
||||||
let src = trait_ref.substs.type_at(1);
|
let src = trait_ref.substs.type_at(1);
|
||||||
let custom_err_msg = format!(
|
let err_msg = format!(
|
||||||
"`{src}` cannot be safely transmuted into `{dst}` in the defining scope of `{scope}`"
|
"`{src}` cannot be safely transmuted into `{dst}` in the defining scope of `{scope}`"
|
||||||
);
|
);
|
||||||
let reason_msg = match reason {
|
let safe_transmute_explanation = match reason {
|
||||||
rustc_transmute::Reason::SrcIsUnspecified => {
|
rustc_transmute::Reason::SrcIsUnspecified => {
|
||||||
format!("`{src}` does not have a well-specified layout")
|
format!("`{src}` does not have a well-specified layout")
|
||||||
}
|
}
|
||||||
|
@ -2794,11 +2807,21 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
|
||||||
rustc_transmute::Reason::DstIsMoreUnique => {
|
rustc_transmute::Reason::DstIsMoreUnique => {
|
||||||
format!("`{src}` is a shared reference, but `{dst}` is a unique reference")
|
format!("`{src}` is a shared reference, but `{dst}` is a unique reference")
|
||||||
}
|
}
|
||||||
|
// Already reported by rustc
|
||||||
|
rustc_transmute::Reason::TypeError => {
|
||||||
|
return GetSafeTransmuteErrorAndReason::Silent;
|
||||||
|
}
|
||||||
|
rustc_transmute::Reason::SrcLayoutUnknown => {
|
||||||
|
format!("`{src}` has an unknown layout")
|
||||||
|
}
|
||||||
|
rustc_transmute::Reason::DstLayoutUnknown => {
|
||||||
|
format!("`{dst}` has an unknown layout")
|
||||||
|
}
|
||||||
};
|
};
|
||||||
(custom_err_msg, Some(reason_msg))
|
GetSafeTransmuteErrorAndReason::Error { err_msg, safe_transmute_explanation }
|
||||||
}
|
}
|
||||||
// Should never get a Yes at this point! We already ran it before, and did not get a Yes.
|
// Should never get a Yes at this point! We already ran it before, and did not get a Yes.
|
||||||
Ok(None) => span_bug!(
|
Answer::Yes => span_bug!(
|
||||||
span,
|
span,
|
||||||
"Inconsistent rustc_transmute::is_transmutable(...) result, got Yes",
|
"Inconsistent rustc_transmute::is_transmutable(...) result, got Yes",
|
||||||
),
|
),
|
||||||
|
|
|
@ -285,28 +285,22 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||||
&mut self,
|
&mut self,
|
||||||
obligation: &TraitObligation<'tcx>,
|
obligation: &TraitObligation<'tcx>,
|
||||||
) -> Result<ImplSourceBuiltinData<PredicateObligation<'tcx>>, SelectionError<'tcx>> {
|
) -> Result<ImplSourceBuiltinData<PredicateObligation<'tcx>>, SelectionError<'tcx>> {
|
||||||
|
use rustc_transmute::{Answer, Condition};
|
||||||
#[instrument(level = "debug", skip(tcx, obligation, predicate))]
|
#[instrument(level = "debug", skip(tcx, obligation, predicate))]
|
||||||
fn flatten_answer_tree<'tcx>(
|
fn flatten_answer_tree<'tcx>(
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
obligation: &TraitObligation<'tcx>,
|
obligation: &TraitObligation<'tcx>,
|
||||||
predicate: TraitPredicate<'tcx>,
|
predicate: TraitPredicate<'tcx>,
|
||||||
answer: rustc_transmute::Condition<rustc_transmute::layout::rustc::Ref<'tcx>>,
|
cond: Condition<rustc_transmute::layout::rustc::Ref<'tcx>>,
|
||||||
) -> Vec<PredicateObligation<'tcx>> {
|
) -> Vec<PredicateObligation<'tcx>> {
|
||||||
match answer {
|
match cond {
|
||||||
// FIXME(bryangarza): Add separate `IfAny` case, instead of treating as `IfAll`
|
// FIXME(bryangarza): Add separate `IfAny` case, instead of treating as `IfAll`
|
||||||
// Not possible until the trait solver supports disjunctions of obligations
|
// Not possible until the trait solver supports disjunctions of obligations
|
||||||
rustc_transmute::Condition::IfAll(answers)
|
Condition::IfAll(conds) | Condition::IfAny(conds) => conds
|
||||||
| rustc_transmute::Condition::IfAny(answers) => {
|
.into_iter()
|
||||||
let mut nested = vec![];
|
.flat_map(|cond| flatten_answer_tree(tcx, obligation, predicate, cond))
|
||||||
for flattened in answers
|
.collect(),
|
||||||
.into_iter()
|
Condition::IfTransmutable { src, dst } => {
|
||||||
.map(|answer| flatten_answer_tree(tcx, obligation, predicate, answer))
|
|
||||||
{
|
|
||||||
nested.extend(flattened);
|
|
||||||
}
|
|
||||||
nested
|
|
||||||
}
|
|
||||||
rustc_transmute::Condition::IfTransmutable { src, dst } => {
|
|
||||||
let trait_def_id = obligation.predicate.def_id();
|
let trait_def_id = obligation.predicate.def_id();
|
||||||
let scope = predicate.trait_ref.substs.type_at(2);
|
let scope = predicate.trait_ref.substs.type_at(2);
|
||||||
let assume_const = predicate.trait_ref.substs.const_at(3);
|
let assume_const = predicate.trait_ref.substs.const_at(3);
|
||||||
|
@ -333,11 +327,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||||
// If Dst is mutable, check bidirectionally.
|
// If Dst is mutable, check bidirectionally.
|
||||||
// For example, transmuting bool -> u8 is OK as long as you can't update that u8
|
// For example, transmuting bool -> u8 is OK as long as you can't update that u8
|
||||||
// to be > 1, because you could later transmute the u8 back to a bool and get UB.
|
// to be > 1, because you could later transmute the u8 back to a bool and get UB.
|
||||||
let mut obligations = vec![make_obl(src.ty, dst.ty)];
|
match dst.mutability {
|
||||||
if dst.mutability == Mutability::Mut {
|
Mutability::Not => vec![make_obl(src.ty, dst.ty)],
|
||||||
obligations.push(make_obl(dst.ty, src.ty));
|
Mutability::Mut => vec![make_obl(src.ty, dst.ty), make_obl(dst.ty, src.ty)],
|
||||||
}
|
}
|
||||||
obligations
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -370,9 +363,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||||
);
|
);
|
||||||
|
|
||||||
let fully_flattened = match maybe_transmutable {
|
let fully_flattened = match maybe_transmutable {
|
||||||
Err(_) => Err(Unimplemented)?,
|
Answer::No(_) => Err(Unimplemented)?,
|
||||||
Ok(Some(mt)) => flatten_answer_tree(self.tcx(), obligation, predicate, mt),
|
Answer::If(cond) => flatten_answer_tree(self.tcx(), obligation, predicate, cond),
|
||||||
Ok(None) => vec![],
|
Answer::Yes => vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!(?fully_flattened);
|
debug!(?fully_flattened);
|
||||||
|
|
|
@ -188,14 +188,14 @@ pub(crate) mod rustc {
|
||||||
/// The layout of the type is unspecified.
|
/// The layout of the type is unspecified.
|
||||||
Unspecified,
|
Unspecified,
|
||||||
/// This error will be surfaced elsewhere by rustc, so don't surface it.
|
/// This error will be surfaced elsewhere by rustc, so don't surface it.
|
||||||
Unknown,
|
UnknownLayout,
|
||||||
TypeError(ErrorGuaranteed),
|
TypeError(ErrorGuaranteed),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tcx> From<LayoutError<'tcx>> for Err {
|
impl<'tcx> From<LayoutError<'tcx>> for Err {
|
||||||
fn from(err: LayoutError<'tcx>) -> Self {
|
fn from(err: LayoutError<'tcx>) -> Self {
|
||||||
match err {
|
match err {
|
||||||
LayoutError::Unknown(..) => Self::Unknown,
|
LayoutError::Unknown(..) => Self::UnknownLayout,
|
||||||
err => unimplemented!("{:?}", err),
|
err => unimplemented!("{:?}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,16 @@ pub struct Assume {
|
||||||
pub validity: bool,
|
pub validity: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Either we have an error, or we have an optional Condition that must hold.
|
/// Either we have an error, transmutation is allowed, or we have an optional
|
||||||
pub type Answer<R> = Result<Option<Condition<R>>, Reason>;
|
/// Condition that must hold.
|
||||||
|
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
|
||||||
|
pub enum Answer<R> {
|
||||||
|
Yes,
|
||||||
|
No(Reason),
|
||||||
|
If(Condition<R>),
|
||||||
|
}
|
||||||
|
|
||||||
/// A condition which must hold for safe transmutation to be possible
|
/// A condition which must hold for safe transmutation to be possible.
|
||||||
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
|
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
|
||||||
pub enum Condition<R> {
|
pub enum Condition<R> {
|
||||||
/// `Src` is transmutable into `Dst`, if `src` is transmutable into `dst`.
|
/// `Src` is transmutable into `Dst`, if `src` is transmutable into `dst`.
|
||||||
|
@ -35,7 +41,7 @@ pub enum Condition<R> {
|
||||||
IfAny(Vec<Condition<R>>),
|
IfAny(Vec<Condition<R>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Answers: Why wasn't the source type transmutable into the destination type?
|
/// Answers "why wasn't the source type transmutable into the destination type?"
|
||||||
#[derive(Debug, Hash, Eq, PartialEq, PartialOrd, Ord, Clone)]
|
#[derive(Debug, Hash, Eq, PartialEq, PartialOrd, Ord, Clone)]
|
||||||
pub enum Reason {
|
pub enum Reason {
|
||||||
/// The layout of the source type is unspecified.
|
/// The layout of the source type is unspecified.
|
||||||
|
@ -52,6 +58,12 @@ pub enum Reason {
|
||||||
DstHasStricterAlignment { src_min_align: usize, dst_min_align: usize },
|
DstHasStricterAlignment { src_min_align: usize, dst_min_align: usize },
|
||||||
/// Can't go from shared pointer to unique pointer
|
/// Can't go from shared pointer to unique pointer
|
||||||
DstIsMoreUnique,
|
DstIsMoreUnique,
|
||||||
|
/// Encountered a type error
|
||||||
|
TypeError,
|
||||||
|
/// The layout of src is unknown
|
||||||
|
SrcLayoutUnknown,
|
||||||
|
/// The layout of dst is unknown
|
||||||
|
DstLayoutUnknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "rustc")]
|
#[cfg(feature = "rustc")]
|
||||||
|
|
|
@ -33,6 +33,7 @@ where
|
||||||
Self { src, dst, scope, assume, context }
|
Self { src, dst, scope, assume, context }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME(bryangarza): Delete this when all usages are removed
|
||||||
pub(crate) fn map_layouts<F, M>(
|
pub(crate) fn map_layouts<F, M>(
|
||||||
self,
|
self,
|
||||||
f: F,
|
f: F,
|
||||||
|
@ -67,30 +68,26 @@ mod rustc {
|
||||||
/// then computes an answer using those trees.
|
/// then computes an answer using those trees.
|
||||||
#[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))]
|
#[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))]
|
||||||
pub fn answer(self) -> Answer<<TyCtxt<'tcx> as QueryContext>::Ref> {
|
pub fn answer(self) -> Answer<<TyCtxt<'tcx> as QueryContext>::Ref> {
|
||||||
let query_or_answer = self.map_layouts(|src, dst, scope, &context| {
|
let Self { src, dst, scope, assume, context } = self;
|
||||||
// Convert `src` and `dst` from their rustc representations, to `Tree`-based
|
|
||||||
// representations. If these conversions fail, conclude that the transmutation is
|
|
||||||
// unacceptable; the layouts of both the source and destination types must be
|
|
||||||
// well-defined.
|
|
||||||
let src = Tree::from_ty(src, context);
|
|
||||||
let dst = Tree::from_ty(dst, context);
|
|
||||||
|
|
||||||
match (src, dst) {
|
// Convert `src` and `dst` from their rustc representations, to `Tree`-based
|
||||||
// Answer `Ok(None)` here, because 'unknown layout' and type errors will already
|
// representations. If these conversions fail, conclude that the transmutation is
|
||||||
// be reported by rustc. No need to spam the user with more errors.
|
// unacceptable; the layouts of both the source and destination types must be
|
||||||
(Err(Err::TypeError(_)), _)
|
// well-defined.
|
||||||
| (_, Err(Err::TypeError(_)))
|
let src = Tree::from_ty(src, context);
|
||||||
| (Err(Err::Unknown), _)
|
let dst = Tree::from_ty(dst, context);
|
||||||
| (_, Err(Err::Unknown)) => Err(Ok(None)),
|
|
||||||
(Err(Err::Unspecified), _) => Err(Err(Reason::SrcIsUnspecified)),
|
match (src, dst) {
|
||||||
(_, Err(Err::Unspecified)) => Err(Err(Reason::DstIsUnspecified)),
|
(Err(Err::TypeError(_)), _) | (_, Err(Err::TypeError(_))) => {
|
||||||
(Ok(src), Ok(dst)) => Ok((src, dst)),
|
Answer::No(Reason::TypeError)
|
||||||
|
}
|
||||||
|
(Err(Err::UnknownLayout), _) => Answer::No(Reason::SrcLayoutUnknown),
|
||||||
|
(_, Err(Err::UnknownLayout)) => Answer::No(Reason::DstLayoutUnknown),
|
||||||
|
(Err(Err::Unspecified), _) => Answer::No(Reason::SrcIsUnspecified),
|
||||||
|
(_, Err(Err::Unspecified)) => Answer::No(Reason::DstIsUnspecified),
|
||||||
|
(Ok(src), Ok(dst)) => {
|
||||||
|
MaybeTransmutableQuery { src, dst, scope, assume, context }.answer()
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
match query_or_answer {
|
|
||||||
Ok(query) => query.answer(),
|
|
||||||
Err(answer) => answer,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,6 +105,7 @@ where
|
||||||
#[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))]
|
#[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))]
|
||||||
pub(crate) fn answer(self) -> Answer<<C as QueryContext>::Ref> {
|
pub(crate) fn answer(self) -> Answer<<C as QueryContext>::Ref> {
|
||||||
let assume_visibility = self.assume.safety;
|
let assume_visibility = self.assume.safety;
|
||||||
|
// FIXME(bryangarza): Refactor this code to get rid of `map_layouts`
|
||||||
let query_or_answer = self.map_layouts(|src, dst, scope, context| {
|
let query_or_answer = self.map_layouts(|src, dst, scope, context| {
|
||||||
// Remove all `Def` nodes from `src`, without checking their visibility.
|
// Remove all `Def` nodes from `src`, without checking their visibility.
|
||||||
let src = src.prune(&|def| true);
|
let src = src.prune(&|def| true);
|
||||||
|
@ -128,12 +126,13 @@ where
|
||||||
// Convert `src` from a tree-based representation to an NFA-based representation.
|
// Convert `src` from a tree-based representation to an NFA-based representation.
|
||||||
// If the conversion fails because `src` is uninhabited, conclude that the transmutation
|
// If the conversion fails because `src` is uninhabited, conclude that the transmutation
|
||||||
// is acceptable, because instances of the `src` type do not exist.
|
// is acceptable, because instances of the `src` type do not exist.
|
||||||
let src = Nfa::from_tree(src).map_err(|Uninhabited| Ok(None))?;
|
let src = Nfa::from_tree(src).map_err(|Uninhabited| Answer::Yes)?;
|
||||||
|
|
||||||
// Convert `dst` from a tree-based representation to an NFA-based representation.
|
// Convert `dst` from a tree-based representation to an NFA-based representation.
|
||||||
// If the conversion fails because `src` is uninhabited, conclude that the transmutation
|
// If the conversion fails because `src` is uninhabited, conclude that the transmutation
|
||||||
// is unacceptable, because instances of the `dst` type do not exist.
|
// is unacceptable, because instances of the `dst` type do not exist.
|
||||||
let dst = Nfa::from_tree(dst).map_err(|Uninhabited| Err(Reason::DstIsPrivate))?;
|
let dst =
|
||||||
|
Nfa::from_tree(dst).map_err(|Uninhabited| Answer::No(Reason::DstIsPrivate))?;
|
||||||
|
|
||||||
Ok((src, dst))
|
Ok((src, dst))
|
||||||
});
|
});
|
||||||
|
@ -155,6 +154,7 @@ where
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))]
|
#[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))]
|
||||||
pub(crate) fn answer(self) -> Answer<<C as QueryContext>::Ref> {
|
pub(crate) fn answer(self) -> Answer<<C as QueryContext>::Ref> {
|
||||||
|
// FIXME(bryangarza): Refactor this code to get rid of `map_layouts`
|
||||||
let query_or_answer = self
|
let query_or_answer = self
|
||||||
.map_layouts(|src, dst, scope, context| Ok((Dfa::from_nfa(src), Dfa::from_nfa(dst))));
|
.map_layouts(|src, dst, scope, context| Ok((Dfa::from_nfa(src), Dfa::from_nfa(dst))));
|
||||||
|
|
||||||
|
@ -226,13 +226,13 @@ where
|
||||||
// So, if it's possible to transmute to a smaller Dst by truncating, and we can guarantee
|
// So, if it's possible to transmute to a smaller Dst by truncating, and we can guarantee
|
||||||
// that none of the actually-used data can introduce an invalid state for Dst's type, we
|
// that none of the actually-used data can introduce an invalid state for Dst's type, we
|
||||||
// are able to safely transmute, even with truncation.
|
// are able to safely transmute, even with truncation.
|
||||||
Ok(None)
|
Answer::Yes
|
||||||
} else if src_state == self.src.accepting {
|
} else if src_state == self.src.accepting {
|
||||||
// extension: `size_of(Src) >= size_of(Dst)`
|
// extension: `size_of(Src) >= size_of(Dst)`
|
||||||
if let Some(dst_state_prime) = self.dst.byte_from(dst_state, Byte::Uninit) {
|
if let Some(dst_state_prime) = self.dst.byte_from(dst_state, Byte::Uninit) {
|
||||||
self.answer_memo(cache, src_state, dst_state_prime)
|
self.answer_memo(cache, src_state, dst_state_prime)
|
||||||
} else {
|
} else {
|
||||||
Err(Reason::DstIsTooBig)
|
Answer::No(Reason::DstIsTooBig)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let src_quantifier = if self.assume.validity {
|
let src_quantifier = if self.assume.validity {
|
||||||
|
@ -265,7 +265,7 @@ where
|
||||||
} else {
|
} else {
|
||||||
// otherwise, we've exhausted our options.
|
// otherwise, we've exhausted our options.
|
||||||
// the DFAs, from this point onwards, are bit-incompatible.
|
// the DFAs, from this point onwards, are bit-incompatible.
|
||||||
Err(Reason::DstIsBitIncompatible)
|
Answer::No(Reason::DstIsBitIncompatible)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -282,8 +282,8 @@ where
|
||||||
// the algoritm; only its performance.
|
// the algoritm; only its performance.
|
||||||
debug!(?bytes_answer);
|
debug!(?bytes_answer);
|
||||||
match bytes_answer {
|
match bytes_answer {
|
||||||
Err(_) if !self.assume.validity => return bytes_answer,
|
Answer::No(_) if !self.assume.validity => return bytes_answer,
|
||||||
Ok(None) if self.assume.validity => return bytes_answer,
|
Answer::Yes if self.assume.validity => return bytes_answer,
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -299,11 +299,11 @@ where
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(&dst_ref, &dst_state_prime)| {
|
.map(|(&dst_ref, &dst_state_prime)| {
|
||||||
if !src_ref.is_mutable() && dst_ref.is_mutable() {
|
if !src_ref.is_mutable() && dst_ref.is_mutable() {
|
||||||
Err(Reason::DstIsMoreUnique)
|
Answer::No(Reason::DstIsMoreUnique)
|
||||||
} else if !self.assume.alignment
|
} else if !self.assume.alignment
|
||||||
&& src_ref.min_align() < dst_ref.min_align()
|
&& src_ref.min_align() < dst_ref.min_align()
|
||||||
{
|
{
|
||||||
Err(Reason::DstHasStricterAlignment {
|
Answer::No(Reason::DstHasStricterAlignment {
|
||||||
src_min_align: src_ref.min_align(),
|
src_min_align: src_ref.min_align(),
|
||||||
dst_min_align: dst_ref.min_align(),
|
dst_min_align: dst_ref.min_align(),
|
||||||
})
|
})
|
||||||
|
@ -311,10 +311,10 @@ where
|
||||||
// ...such that `src` is transmutable into `dst`, if
|
// ...such that `src` is transmutable into `dst`, if
|
||||||
// `src_ref` is transmutability into `dst_ref`.
|
// `src_ref` is transmutability into `dst_ref`.
|
||||||
and(
|
and(
|
||||||
Ok(Some(Condition::IfTransmutable {
|
Answer::If(Condition::IfTransmutable {
|
||||||
src: src_ref,
|
src: src_ref,
|
||||||
dst: dst_ref,
|
dst: dst_ref,
|
||||||
})),
|
}),
|
||||||
self.answer_memo(
|
self.answer_memo(
|
||||||
cache,
|
cache,
|
||||||
src_state_prime,
|
src_state_prime,
|
||||||
|
@ -346,65 +346,56 @@ fn and<R>(lhs: Answer<R>, rhs: Answer<R>) -> Answer<R>
|
||||||
where
|
where
|
||||||
R: PartialEq,
|
R: PartialEq,
|
||||||
{
|
{
|
||||||
// If both are errors, then we should return the more specific one
|
match (lhs, rhs) {
|
||||||
if lhs.is_err() && rhs.is_err() {
|
// If both are errors, then we should return the more specific one
|
||||||
if lhs == Err(Reason::DstIsBitIncompatible) {
|
(Answer::No(Reason::DstIsBitIncompatible), Answer::No(reason))
|
||||||
return rhs;
|
| (Answer::No(reason), Answer::No(_))
|
||||||
} else {
|
// If either is an error, return it
|
||||||
return lhs;
|
| (Answer::No(reason), _) | (_, Answer::No(reason)) => Answer::No(reason),
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(match (lhs?, rhs?) {
|
|
||||||
// If only one side has a condition, pass it along
|
// If only one side has a condition, pass it along
|
||||||
(None, other) | (other, None) => other,
|
| (Answer::Yes, other) | (other, Answer::Yes) => other,
|
||||||
// If both sides have IfAll conditions, merge them
|
// If both sides have IfAll conditions, merge them
|
||||||
(Some(Condition::IfAll(mut lhs)), Some(Condition::IfAll(ref mut rhs))) => {
|
(Answer::If(Condition::IfAll(mut lhs)), Answer::If(Condition::IfAll(ref mut rhs))) => {
|
||||||
lhs.append(rhs);
|
lhs.append(rhs);
|
||||||
Some(Condition::IfAll(lhs))
|
Answer::If(Condition::IfAll(lhs))
|
||||||
}
|
}
|
||||||
// If only one side is an IfAll, add the other Condition to it
|
// If only one side is an IfAll, add the other Condition to it
|
||||||
(Some(cond), Some(Condition::IfAll(mut conds)))
|
(Answer::If(cond), Answer::If(Condition::IfAll(mut conds)))
|
||||||
| (Some(Condition::IfAll(mut conds)), Some(cond)) => {
|
| (Answer::If(Condition::IfAll(mut conds)), Answer::If(cond)) => {
|
||||||
conds.push(cond);
|
conds.push(cond);
|
||||||
Some(Condition::IfAll(conds))
|
Answer::If(Condition::IfAll(conds))
|
||||||
}
|
}
|
||||||
// Otherwise, both lhs and rhs conditions can be combined in a parent IfAll
|
// Otherwise, both lhs and rhs conditions can be combined in a parent IfAll
|
||||||
(Some(lhs), Some(rhs)) => Some(Condition::IfAll(vec![lhs, rhs])),
|
(Answer::If(lhs), Answer::If(rhs)) => Answer::If(Condition::IfAll(vec![lhs, rhs])),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn or<R>(lhs: Answer<R>, rhs: Answer<R>) -> Answer<R>
|
fn or<R>(lhs: Answer<R>, rhs: Answer<R>) -> Answer<R>
|
||||||
where
|
where
|
||||||
R: PartialEq,
|
R: PartialEq,
|
||||||
{
|
{
|
||||||
// If both are errors, then we should return the more specific one
|
match (lhs, rhs) {
|
||||||
if lhs.is_err() && rhs.is_err() {
|
// If both are errors, then we should return the more specific one
|
||||||
if lhs == Err(Reason::DstIsBitIncompatible) {
|
(Answer::No(Reason::DstIsBitIncompatible), Answer::No(reason))
|
||||||
return rhs;
|
| (Answer::No(reason), Answer::No(_)) => Answer::No(reason),
|
||||||
} else {
|
// Otherwise, errors can be ignored for the rest of the pattern matching
|
||||||
return lhs;
|
(Answer::No(_), other) | (other, Answer::No(_)) => or(other, Answer::Yes),
|
||||||
}
|
|
||||||
}
|
|
||||||
// Otherwise, errors can be ignored for the rest of the pattern matching
|
|
||||||
let lhs = lhs.unwrap_or(None);
|
|
||||||
let rhs = rhs.unwrap_or(None);
|
|
||||||
Ok(match (lhs, rhs) {
|
|
||||||
// If only one side has a condition, pass it along
|
// If only one side has a condition, pass it along
|
||||||
(None, other) | (other, None) => other,
|
(Answer::Yes, other) | (other, Answer::Yes) => other,
|
||||||
// If both sides have IfAny conditions, merge them
|
// If both sides have IfAny conditions, merge them
|
||||||
(Some(Condition::IfAny(mut lhs)), Some(Condition::IfAny(ref mut rhs))) => {
|
(Answer::If(Condition::IfAny(mut lhs)), Answer::If(Condition::IfAny(ref mut rhs))) => {
|
||||||
lhs.append(rhs);
|
lhs.append(rhs);
|
||||||
Some(Condition::IfAny(lhs))
|
Answer::If(Condition::IfAny(lhs))
|
||||||
}
|
}
|
||||||
// If only one side is an IfAny, add the other Condition to it
|
// If only one side is an IfAny, add the other Condition to it
|
||||||
(Some(cond), Some(Condition::IfAny(mut conds)))
|
(Answer::If(cond), Answer::If(Condition::IfAny(mut conds)))
|
||||||
| (Some(Condition::IfAny(mut conds)), Some(cond)) => {
|
| (Answer::If(Condition::IfAny(mut conds)), Answer::If(cond)) => {
|
||||||
conds.push(cond);
|
conds.push(cond);
|
||||||
Some(Condition::IfAny(conds))
|
Answer::If(Condition::IfAny(conds))
|
||||||
}
|
}
|
||||||
// Otherwise, both lhs and rhs conditions can be combined in a parent IfAny
|
// Otherwise, both lhs and rhs conditions can be combined in a parent IfAny
|
||||||
(Some(lhs), Some(rhs)) => Some(Condition::IfAny(vec![lhs, rhs])),
|
(Answer::If(lhs), Answer::If(rhs)) => Answer::If(Condition::IfAny(vec![lhs, rhs])),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Quantifier {
|
pub enum Quantifier {
|
||||||
|
@ -421,16 +412,21 @@ impl Quantifier {
|
||||||
use std::ops::ControlFlow::{Break, Continue};
|
use std::ops::ControlFlow::{Break, Continue};
|
||||||
|
|
||||||
let (init, try_fold_f): (_, fn(_, _) -> _) = match self {
|
let (init, try_fold_f): (_, fn(_, _) -> _) = match self {
|
||||||
Self::ThereExists => (Err(Reason::DstIsBitIncompatible), |accum: Answer<R>, next| {
|
Self::ThereExists => {
|
||||||
match or(accum, next) {
|
(Answer::No(Reason::DstIsBitIncompatible), |accum: Answer<R>, next| {
|
||||||
Ok(None) => Break(Ok(None)),
|
match or(accum, next) {
|
||||||
|
Answer::Yes => Break(Answer::Yes),
|
||||||
|
maybe => Continue(maybe),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Self::ForAll => (Answer::Yes, |accum: Answer<R>, next| {
|
||||||
|
let answer = and(accum, next);
|
||||||
|
match answer {
|
||||||
|
Answer::No(_) => Break(answer),
|
||||||
maybe => Continue(maybe),
|
maybe => Continue(maybe),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
Self::ForAll => (Ok(None), |accum: Answer<R>, next| match and(accum, next) {
|
|
||||||
Err(reason) => Break(Err(reason)),
|
|
||||||
maybe => Continue(maybe),
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let (Continue(result) | Break(result)) = iter.into_iter().try_fold(init, try_fold_f);
|
let (Continue(result) | Break(result)) = iter.into_iter().try_fold(init, try_fold_f);
|
||||||
|
|
|
@ -4,6 +4,8 @@ use crate::{layout, Reason};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
mod bool {
|
mod bool {
|
||||||
|
use crate::Answer;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -17,7 +19,7 @@ mod bool {
|
||||||
UltraMinimal,
|
UltraMinimal,
|
||||||
)
|
)
|
||||||
.answer();
|
.answer();
|
||||||
assert_eq!(answer, Ok(None));
|
assert_eq!(answer, Answer::Yes);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -30,7 +32,7 @@ mod bool {
|
||||||
UltraMinimal,
|
UltraMinimal,
|
||||||
)
|
)
|
||||||
.answer();
|
.answer();
|
||||||
assert_eq!(answer, Ok(None));
|
assert_eq!(answer, Answer::Yes);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -65,7 +67,7 @@ mod bool {
|
||||||
|
|
||||||
if src_set.is_subset(&dst_set) {
|
if src_set.is_subset(&dst_set) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Ok(None),
|
Answer::Yes,
|
||||||
MaybeTransmutableQuery::new(
|
MaybeTransmutableQuery::new(
|
||||||
src_layout.clone(),
|
src_layout.clone(),
|
||||||
dst_layout.clone(),
|
dst_layout.clone(),
|
||||||
|
@ -80,7 +82,7 @@ mod bool {
|
||||||
);
|
);
|
||||||
} else if !src_set.is_disjoint(&dst_set) {
|
} else if !src_set.is_disjoint(&dst_set) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Ok(None),
|
Answer::Yes,
|
||||||
MaybeTransmutableQuery::new(
|
MaybeTransmutableQuery::new(
|
||||||
src_layout.clone(),
|
src_layout.clone(),
|
||||||
dst_layout.clone(),
|
dst_layout.clone(),
|
||||||
|
@ -95,7 +97,7 @@ mod bool {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Err(Reason::DstIsBitIncompatible),
|
Answer::No(Reason::DstIsBitIncompatible),
|
||||||
MaybeTransmutableQuery::new(
|
MaybeTransmutableQuery::new(
|
||||||
src_layout.clone(),
|
src_layout.clone(),
|
||||||
dst_layout.clone(),
|
dst_layout.clone(),
|
||||||
|
|
|
@ -18,5 +18,5 @@ fn should_gracefully_handle_unknown_dst_field() {
|
||||||
struct Context;
|
struct Context;
|
||||||
#[repr(C)] struct Src;
|
#[repr(C)] struct Src;
|
||||||
#[repr(C)] struct Dst(Missing); //~ cannot find type
|
#[repr(C)] struct Dst(Missing); //~ cannot find type
|
||||||
assert::is_transmutable::<Src, Dst, Context>();
|
assert::is_transmutable::<Src, Dst, Context>(); //~ ERROR cannot be safely transmuted
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,22 @@ error[E0412]: cannot find type `Missing` in this scope
|
||||||
LL | #[repr(C)] struct Dst(Missing);
|
LL | #[repr(C)] struct Dst(Missing);
|
||||||
| ^^^^^^^ not found in this scope
|
| ^^^^^^^ not found in this scope
|
||||||
|
|
||||||
error: aborting due to previous error
|
error[E0277]: `Src` cannot be safely transmuted into `Dst` in the defining scope of `should_gracefully_handle_unknown_dst_field::Context`
|
||||||
|
--> $DIR/unknown_src_field.rs:21:36
|
||||||
|
|
|
||||||
|
LL | assert::is_transmutable::<Src, Dst, Context>();
|
||||||
|
| ^^^ `Dst` has an unknown layout
|
||||||
|
|
|
||||||
|
note: required by a bound in `is_transmutable`
|
||||||
|
--> $DIR/unknown_src_field.rs:13:14
|
||||||
|
|
|
||||||
|
LL | pub fn is_transmutable<Src, Dst, Context>()
|
||||||
|
| --------------- required by a bound in this function
|
||||||
|
LL | where
|
||||||
|
LL | Dst: BikeshedIntrinsicFrom<Src, Context>
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `is_transmutable`
|
||||||
|
|
||||||
For more information about this error, try `rustc --explain E0412`.
|
error: aborting due to 2 previous errors
|
||||||
|
|
||||||
|
Some errors have detailed explanations: E0277, E0412.
|
||||||
|
For more information about an error, try `rustc --explain E0277`.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue