transmutability: shift abstraction boundary
Previously, `rustc_transmute`'s layout representations were genericized over `R`, a reference. Now, it's instead genericized over representations of type and region. This allows us to move reference transmutability logic from `rustc_trait_selection` to `rustc_transmutability` (and thus unit test it independently of the compiler), and — in a follow-up PR — will make it possible to support analyzing function pointer transmutability with minimal surgery.
This commit is contained in:
parent
7c10378e1f
commit
e9eae28eee
14 changed files with 385 additions and 357 deletions
|
|
@ -2558,32 +2558,31 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
|
|||
rustc_transmute::Reason::SrcIsNotYetSupported => {
|
||||
format!("analyzing the transmutability of `{src}` is not yet supported")
|
||||
}
|
||||
|
||||
rustc_transmute::Reason::DstIsNotYetSupported => {
|
||||
format!("analyzing the transmutability of `{dst}` is not yet supported")
|
||||
}
|
||||
|
||||
rustc_transmute::Reason::DstIsBitIncompatible => {
|
||||
format!(
|
||||
"at least one value of `{src}` isn't a bit-valid value of `{dst}`"
|
||||
)
|
||||
}
|
||||
|
||||
rustc_transmute::Reason::DstUninhabited => {
|
||||
format!("`{dst}` is uninhabited")
|
||||
}
|
||||
|
||||
rustc_transmute::Reason::DstMayHaveSafetyInvariants => {
|
||||
format!("`{dst}` may carry safety invariants")
|
||||
}
|
||||
rustc_transmute::Reason::DstIsTooBig => {
|
||||
format!("the size of `{src}` is smaller than the size of `{dst}`")
|
||||
}
|
||||
rustc_transmute::Reason::DstRefIsTooBig { src, dst } => {
|
||||
let src_size = src.size;
|
||||
let dst_size = dst.size;
|
||||
rustc_transmute::Reason::DstRefIsTooBig {
|
||||
src,
|
||||
src_size,
|
||||
dst,
|
||||
dst_size,
|
||||
} => {
|
||||
format!(
|
||||
"the referent size of `{src}` ({src_size} bytes) \
|
||||
"the size of `{src}` ({src_size} bytes) \
|
||||
is smaller than that of `{dst}` ({dst_size} bytes)"
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,13 +9,12 @@
|
|||
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use rustc_ast::Mutability;
|
||||
use rustc_data_structures::stack::ensure_sufficient_stack;
|
||||
use rustc_hir::lang_items::LangItem;
|
||||
use rustc_infer::infer::{DefineOpaqueTypes, HigherRankedType, InferOk};
|
||||
use rustc_infer::traits::ObligationCauseCode;
|
||||
use rustc_middle::traits::{BuiltinImplSource, SignatureMismatchData};
|
||||
use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt, Upcast, elaborate};
|
||||
use rustc_middle::ty::{self, GenericArgsRef, Region, Ty, TyCtxt, Upcast, elaborate};
|
||||
use rustc_middle::{bug, span_bug};
|
||||
use rustc_span::def_id::DefId;
|
||||
use thin_vec::thin_vec;
|
||||
|
|
@ -286,99 +285,12 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
|||
) -> Result<PredicateObligations<'tcx>, SelectionError<'tcx>> {
|
||||
use rustc_transmute::{Answer, Assume, Condition};
|
||||
|
||||
/// Generate sub-obligations for reference-to-reference transmutations.
|
||||
fn reference_obligations<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
obligation: &PolyTraitObligation<'tcx>,
|
||||
(src_lifetime, src_ty, src_mut): (ty::Region<'tcx>, Ty<'tcx>, Mutability),
|
||||
(dst_lifetime, dst_ty, dst_mut): (ty::Region<'tcx>, Ty<'tcx>, Mutability),
|
||||
assume: Assume,
|
||||
) -> PredicateObligations<'tcx> {
|
||||
let make_transmute_obl = |src, dst| {
|
||||
let transmute_trait = obligation.predicate.def_id();
|
||||
let assume = obligation.predicate.skip_binder().trait_ref.args.const_at(2);
|
||||
let trait_ref = ty::TraitRef::new(
|
||||
tcx,
|
||||
transmute_trait,
|
||||
[
|
||||
ty::GenericArg::from(dst),
|
||||
ty::GenericArg::from(src),
|
||||
ty::GenericArg::from(assume),
|
||||
],
|
||||
);
|
||||
Obligation::with_depth(
|
||||
tcx,
|
||||
obligation.cause.clone(),
|
||||
obligation.recursion_depth + 1,
|
||||
obligation.param_env,
|
||||
trait_ref,
|
||||
)
|
||||
};
|
||||
|
||||
let make_freeze_obl = |ty| {
|
||||
let trait_ref = ty::TraitRef::new(
|
||||
tcx,
|
||||
tcx.require_lang_item(LangItem::Freeze, obligation.cause.span),
|
||||
[ty::GenericArg::from(ty)],
|
||||
);
|
||||
Obligation::with_depth(
|
||||
tcx,
|
||||
obligation.cause.clone(),
|
||||
obligation.recursion_depth + 1,
|
||||
obligation.param_env,
|
||||
trait_ref,
|
||||
)
|
||||
};
|
||||
|
||||
let make_outlives_obl = |target, region| {
|
||||
let outlives = ty::OutlivesPredicate(target, region);
|
||||
Obligation::with_depth(
|
||||
tcx,
|
||||
obligation.cause.clone(),
|
||||
obligation.recursion_depth + 1,
|
||||
obligation.param_env,
|
||||
outlives,
|
||||
)
|
||||
};
|
||||
|
||||
// Given a transmutation from `&'a (mut) Src` and `&'dst (mut) Dst`,
|
||||
// it is always the case that `Src` must be transmutable into `Dst`,
|
||||
// and that that `'src` must outlive `'dst`.
|
||||
let mut obls = PredicateObligations::with_capacity(1);
|
||||
obls.push(make_transmute_obl(src_ty, dst_ty));
|
||||
if !assume.lifetimes {
|
||||
obls.push(make_outlives_obl(src_lifetime, dst_lifetime));
|
||||
}
|
||||
|
||||
// Given a transmutation from `&Src`, both `Src` and `Dst` must be
|
||||
// `Freeze`, otherwise, using the transmuted value could lead to
|
||||
// data races.
|
||||
if src_mut == Mutability::Not {
|
||||
obls.extend([make_freeze_obl(src_ty), make_freeze_obl(dst_ty)])
|
||||
}
|
||||
|
||||
// Given a transmutation into `&'dst mut Dst`, it also must be the
|
||||
// case that `Dst` is transmutable into `Src`. 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 undefined behavior. It also must be the case that
|
||||
// `'dst` lives exactly as long as `'src`.
|
||||
if dst_mut == Mutability::Mut {
|
||||
obls.push(make_transmute_obl(dst_ty, src_ty));
|
||||
if !assume.lifetimes {
|
||||
obls.push(make_outlives_obl(dst_lifetime, src_lifetime));
|
||||
}
|
||||
}
|
||||
|
||||
obls
|
||||
}
|
||||
|
||||
/// Flatten the `Condition` tree into a conjunction of obligations.
|
||||
#[instrument(level = "debug", skip(tcx, obligation))]
|
||||
fn flatten_answer_tree<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
obligation: &PolyTraitObligation<'tcx>,
|
||||
cond: Condition<rustc_transmute::layout::rustc::Ref<'tcx>>,
|
||||
cond: Condition<Region<'tcx>, Ty<'tcx>>,
|
||||
assume: Assume,
|
||||
) -> PredicateObligations<'tcx> {
|
||||
match cond {
|
||||
|
|
@ -388,13 +300,50 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
|||
.into_iter()
|
||||
.flat_map(|cond| flatten_answer_tree(tcx, obligation, cond, assume))
|
||||
.collect(),
|
||||
Condition::IfTransmutable { src, dst } => reference_obligations(
|
||||
tcx,
|
||||
obligation,
|
||||
(src.lifetime, src.ty, src.mutability),
|
||||
(dst.lifetime, dst.ty, dst.mutability),
|
||||
assume,
|
||||
),
|
||||
Condition::Immutable { ty } => {
|
||||
let trait_ref = ty::TraitRef::new(
|
||||
tcx,
|
||||
tcx.require_lang_item(LangItem::Freeze, obligation.cause.span),
|
||||
[ty::GenericArg::from(ty)],
|
||||
);
|
||||
thin_vec![Obligation::with_depth(
|
||||
tcx,
|
||||
obligation.cause.clone(),
|
||||
obligation.recursion_depth + 1,
|
||||
obligation.param_env,
|
||||
trait_ref,
|
||||
)]
|
||||
}
|
||||
Condition::Outlives { long, short } => {
|
||||
let outlives = ty::OutlivesPredicate(long, short);
|
||||
thin_vec![Obligation::with_depth(
|
||||
tcx,
|
||||
obligation.cause.clone(),
|
||||
obligation.recursion_depth + 1,
|
||||
obligation.param_env,
|
||||
outlives,
|
||||
)]
|
||||
}
|
||||
Condition::Transmutable { src, dst } => {
|
||||
let transmute_trait = obligation.predicate.def_id();
|
||||
let assume = obligation.predicate.skip_binder().trait_ref.args.const_at(2);
|
||||
let trait_ref = ty::TraitRef::new(
|
||||
tcx,
|
||||
transmute_trait,
|
||||
[
|
||||
ty::GenericArg::from(dst),
|
||||
ty::GenericArg::from(src),
|
||||
ty::GenericArg::from(assume),
|
||||
],
|
||||
);
|
||||
thin_vec![Obligation::with_depth(
|
||||
tcx,
|
||||
obligation.cause.clone(),
|
||||
obligation.recursion_depth + 1,
|
||||
obligation.param_env,
|
||||
trait_ref,
|
||||
)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,32 +2,35 @@ use std::fmt;
|
|||
use std::iter::Peekable;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
use super::{Byte, Ref, Tree, Uninhabited};
|
||||
use super::{Byte, Reference, Region, Tree, Type, Uninhabited};
|
||||
use crate::{Map, Set};
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[cfg_attr(test, derive(Clone))]
|
||||
pub(crate) struct Dfa<R>
|
||||
pub(crate) struct Dfa<R, T>
|
||||
where
|
||||
R: Ref,
|
||||
R: Region,
|
||||
T: Type,
|
||||
{
|
||||
pub(crate) transitions: Map<State, Transitions<R>>,
|
||||
pub(crate) transitions: Map<State, Transitions<R, T>>,
|
||||
pub(crate) start: State,
|
||||
pub(crate) accept: State,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub(crate) struct Transitions<R>
|
||||
pub(crate) struct Transitions<R, T>
|
||||
where
|
||||
R: Ref,
|
||||
R: Region,
|
||||
T: Type,
|
||||
{
|
||||
byte_transitions: EdgeSet<State>,
|
||||
ref_transitions: Map<R, State>,
|
||||
ref_transitions: Map<Reference<R, T>, State>,
|
||||
}
|
||||
|
||||
impl<R> Default for Transitions<R>
|
||||
impl<R, T> Default for Transitions<R, T>
|
||||
where
|
||||
R: Ref,
|
||||
R: Region,
|
||||
T: Type,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self { byte_transitions: EdgeSet::empty(), ref_transitions: Map::default() }
|
||||
|
|
@ -51,9 +54,10 @@ impl fmt::Debug for State {
|
|||
}
|
||||
}
|
||||
|
||||
impl<R> Dfa<R>
|
||||
impl<R, T> Dfa<R, T>
|
||||
where
|
||||
R: Ref,
|
||||
R: Region,
|
||||
T: Type,
|
||||
{
|
||||
#[cfg(test)]
|
||||
pub(crate) fn bool() -> Self {
|
||||
|
|
@ -64,7 +68,7 @@ where
|
|||
}
|
||||
|
||||
pub(crate) fn unit() -> Self {
|
||||
let transitions: Map<State, Transitions<R>> = Map::default();
|
||||
let transitions: Map<State, Transitions<R, T>> = Map::default();
|
||||
let start = State::new();
|
||||
let accept = start;
|
||||
|
||||
|
|
@ -78,21 +82,21 @@ where
|
|||
})
|
||||
}
|
||||
|
||||
pub(crate) fn from_ref(r: R) -> Self {
|
||||
pub(crate) fn from_ref(r: Reference<R, T>) -> Self {
|
||||
Self::from_transitions(|accept| Transitions {
|
||||
byte_transitions: EdgeSet::empty(),
|
||||
ref_transitions: [(r, accept)].into_iter().collect(),
|
||||
})
|
||||
}
|
||||
|
||||
fn from_transitions(f: impl FnOnce(State) -> Transitions<R>) -> Self {
|
||||
fn from_transitions(f: impl FnOnce(State) -> Transitions<R, T>) -> Self {
|
||||
let start = State::new();
|
||||
let accept = State::new();
|
||||
|
||||
Self { transitions: [(start, f(accept))].into_iter().collect(), start, accept }
|
||||
}
|
||||
|
||||
pub(crate) fn from_tree(tree: Tree<!, R>) -> Result<Self, Uninhabited> {
|
||||
pub(crate) fn from_tree(tree: Tree<!, R, T>) -> Result<Self, Uninhabited> {
|
||||
Ok(match tree {
|
||||
Tree::Byte(b) => Self::from_byte(b),
|
||||
Tree::Ref(r) => Self::from_ref(r),
|
||||
|
|
@ -125,7 +129,7 @@ where
|
|||
let start = self.start;
|
||||
let accept = other.accept;
|
||||
|
||||
let mut transitions: Map<State, Transitions<R>> = self.transitions;
|
||||
let mut transitions: Map<State, Transitions<R, T>> = self.transitions;
|
||||
|
||||
for (source, transition) in other.transitions {
|
||||
let fix_state = |state| if state == other.start { self.accept } else { state };
|
||||
|
|
@ -169,7 +173,7 @@ where
|
|||
};
|
||||
|
||||
let start = mapped((Some(a.start), Some(b.start)));
|
||||
let mut transitions: Map<State, Transitions<R>> = Map::default();
|
||||
let mut transitions: Map<State, Transitions<R, T>> = Map::default();
|
||||
let empty_transitions = Transitions::default();
|
||||
|
||||
struct WorkQueue {
|
||||
|
|
@ -257,7 +261,7 @@ where
|
|||
.flat_map(|transitions| transitions.byte_transitions.iter())
|
||||
}
|
||||
|
||||
pub(crate) fn refs_from(&self, start: State) -> impl Iterator<Item = (R, State)> {
|
||||
pub(crate) fn refs_from(&self, start: State) -> impl Iterator<Item = (Reference<R, T>, State)> {
|
||||
self.transitions
|
||||
.get(&start)
|
||||
.into_iter()
|
||||
|
|
@ -297,9 +301,10 @@ where
|
|||
}
|
||||
|
||||
/// Serialize the DFA using the Graphviz DOT format.
|
||||
impl<R> fmt::Debug for Dfa<R>
|
||||
impl<R, T> fmt::Debug for Dfa<R, T>
|
||||
where
|
||||
R: Ref,
|
||||
R: Region,
|
||||
T: Type,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "digraph {{")?;
|
||||
|
|
|
|||
|
|
@ -78,16 +78,41 @@ impl From<u8> for Byte {
|
|||
}
|
||||
}
|
||||
|
||||
/// A reference, i.e., `&'region T` or `&'region mut T`.
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
|
||||
pub(crate) struct Reference<R, T>
|
||||
where
|
||||
R: Region,
|
||||
T: Type,
|
||||
{
|
||||
pub(crate) region: R,
|
||||
pub(crate) is_mut: bool,
|
||||
pub(crate) referent: T,
|
||||
pub(crate) referent_size: usize,
|
||||
pub(crate) referent_align: usize,
|
||||
}
|
||||
|
||||
impl<R, T> fmt::Display for Reference<R, T>
|
||||
where
|
||||
R: Region,
|
||||
T: Type,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("&")?;
|
||||
if self.is_mut {
|
||||
f.write_str("mut ")?;
|
||||
}
|
||||
self.referent.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait Def: Debug + Hash + Eq + PartialEq + Copy + Clone {
|
||||
fn has_safety_invariants(&self) -> bool;
|
||||
}
|
||||
pub trait Ref: Debug + Hash + Eq + PartialEq + Copy + Clone {
|
||||
fn min_align(&self) -> usize;
|
||||
|
||||
fn size(&self) -> usize;
|
||||
pub(crate) trait Region: Debug + Hash + Eq + PartialEq + Copy + Clone {}
|
||||
|
||||
fn is_mutable(&self) -> bool;
|
||||
}
|
||||
pub(crate) trait Type: Debug + Hash + Eq + PartialEq + Copy + Clone {}
|
||||
|
||||
impl Def for ! {
|
||||
fn has_safety_invariants(&self) -> bool {
|
||||
|
|
@ -95,79 +120,21 @@ impl Def for ! {
|
|||
}
|
||||
}
|
||||
|
||||
impl Ref for ! {
|
||||
fn min_align(&self) -> usize {
|
||||
unreachable!()
|
||||
}
|
||||
fn size(&self) -> usize {
|
||||
unreachable!()
|
||||
}
|
||||
fn is_mutable(&self) -> bool {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
impl Region for ! {}
|
||||
|
||||
impl Type for ! {}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<const N: usize> Ref for [(); N] {
|
||||
fn min_align(&self) -> usize {
|
||||
N
|
||||
}
|
||||
impl Region for usize {}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
N
|
||||
}
|
||||
|
||||
fn is_mutable(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
impl Type for () {}
|
||||
|
||||
#[cfg(feature = "rustc")]
|
||||
pub mod rustc {
|
||||
use std::fmt::{self, Write};
|
||||
|
||||
use rustc_abi::Layout;
|
||||
use rustc_middle::mir::Mutability;
|
||||
use rustc_middle::ty::layout::{HasTyCtxt, LayoutCx, LayoutError};
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
|
||||
/// A reference in the layout.
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)]
|
||||
pub struct Ref<'tcx> {
|
||||
pub lifetime: ty::Region<'tcx>,
|
||||
pub ty: Ty<'tcx>,
|
||||
pub mutability: Mutability,
|
||||
pub align: usize,
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
impl<'tcx> super::Ref for Ref<'tcx> {
|
||||
fn min_align(&self) -> usize {
|
||||
self.align
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
self.size
|
||||
}
|
||||
|
||||
fn is_mutable(&self) -> bool {
|
||||
match self.mutability {
|
||||
Mutability::Mut => true,
|
||||
Mutability::Not => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'tcx> Ref<'tcx> {}
|
||||
|
||||
impl<'tcx> fmt::Display for Ref<'tcx> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_char('&')?;
|
||||
if self.mutability == Mutability::Mut {
|
||||
f.write_str("mut ")?;
|
||||
}
|
||||
self.ty.fmt(f)
|
||||
}
|
||||
}
|
||||
use rustc_middle::ty::{self, Region, Ty};
|
||||
|
||||
/// A visibility node in the layout.
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)]
|
||||
|
|
@ -187,6 +154,10 @@ pub mod rustc {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'tcx> super::Region for Region<'tcx> {}
|
||||
|
||||
impl<'tcx> super::Type for Ty<'tcx> {}
|
||||
|
||||
pub(crate) fn layout_of<'tcx>(
|
||||
cx: LayoutCx<'tcx>,
|
||||
ty: Ty<'tcx>,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::ops::{ControlFlow, RangeInclusive};
|
||||
|
||||
use super::{Byte, Def, Ref};
|
||||
use super::{Byte, Def, Reference, Region, Type};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
@ -15,10 +15,11 @@ mod tests;
|
|||
/// 2. A `Seq` is never directly nested beneath another `Seq`.
|
||||
/// 3. `Seq`s and `Alt`s with a single member do not exist.
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub(crate) enum Tree<D, R>
|
||||
pub(crate) enum Tree<D, R, T>
|
||||
where
|
||||
D: Def,
|
||||
R: Ref,
|
||||
R: Region,
|
||||
T: Type,
|
||||
{
|
||||
/// A sequence of successive layouts.
|
||||
Seq(Vec<Self>),
|
||||
|
|
@ -27,7 +28,7 @@ where
|
|||
/// A definition node.
|
||||
Def(D),
|
||||
/// A reference node.
|
||||
Ref(R),
|
||||
Ref(Reference<R, T>),
|
||||
/// A byte node.
|
||||
Byte(Byte),
|
||||
}
|
||||
|
|
@ -48,10 +49,11 @@ impl From<rustc_abi::Endian> for Endian {
|
|||
}
|
||||
}
|
||||
|
||||
impl<D, R> Tree<D, R>
|
||||
impl<D, R, T> Tree<D, R, T>
|
||||
where
|
||||
D: Def,
|
||||
R: Ref,
|
||||
R: Region,
|
||||
T: Type,
|
||||
{
|
||||
/// A `Tree` consisting only of a definition node.
|
||||
pub(crate) fn def(def: D) -> Self {
|
||||
|
|
@ -138,7 +140,7 @@ where
|
|||
|
||||
/// Remove all `Def` nodes, and all branches of the layout for which `f`
|
||||
/// produces `true`.
|
||||
pub(crate) fn prune<F>(self, f: &F) -> Tree<!, R>
|
||||
pub(crate) fn prune<F>(self, f: &F) -> Tree<!, R, T>
|
||||
where
|
||||
F: Fn(D) -> bool,
|
||||
{
|
||||
|
|
@ -198,13 +200,13 @@ where
|
|||
|
||||
/// Produces a `Tree` where each of the trees in `trees` are sequenced one
|
||||
/// after another.
|
||||
pub(crate) fn seq<const N: usize>(trees: [Tree<D, R>; N]) -> Self {
|
||||
pub(crate) fn seq<const N: usize>(trees: [Tree<D, R, T>; N]) -> Self {
|
||||
trees.into_iter().fold(Tree::unit(), Self::then)
|
||||
}
|
||||
|
||||
/// Produces a `Tree` where each of the trees in `trees` are accepted as
|
||||
/// alternative layouts.
|
||||
pub(crate) fn alt<const N: usize>(trees: [Tree<D, R>; N]) -> Self {
|
||||
pub(crate) fn alt<const N: usize>(trees: [Tree<D, R, T>; N]) -> Self {
|
||||
trees.into_iter().fold(Tree::uninhabited(), Self::or)
|
||||
}
|
||||
|
||||
|
|
@ -251,11 +253,14 @@ pub(crate) mod rustc {
|
|||
FieldIdx, FieldsShape, Layout, Size, TagEncoding, TyAndLayout, VariantIdx, Variants,
|
||||
};
|
||||
use rustc_middle::ty::layout::{HasTyCtxt, LayoutCx, LayoutError};
|
||||
use rustc_middle::ty::{self, AdtDef, AdtKind, List, ScalarInt, Ty, TyCtxt, TypeVisitableExt};
|
||||
use rustc_middle::ty::{
|
||||
self, AdtDef, AdtKind, List, Region, ScalarInt, Ty, TyCtxt, TypeVisitableExt,
|
||||
};
|
||||
use rustc_span::ErrorGuaranteed;
|
||||
|
||||
use super::Tree;
|
||||
use crate::layout::rustc::{Def, Ref, layout_of};
|
||||
use crate::layout::Reference;
|
||||
use crate::layout::rustc::{Def, layout_of};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) enum Err {
|
||||
|
|
@ -281,7 +286,7 @@ pub(crate) mod rustc {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'tcx> Tree<Def<'tcx>, Ref<'tcx>> {
|
||||
impl<'tcx> Tree<Def<'tcx>, Region<'tcx>, Ty<'tcx>> {
|
||||
pub(crate) fn from_ty(ty: Ty<'tcx>, cx: LayoutCx<'tcx>) -> Result<Self, Err> {
|
||||
use rustc_abi::HasDataLayout;
|
||||
let layout = layout_of(cx, ty)?;
|
||||
|
|
@ -353,16 +358,17 @@ pub(crate) mod rustc {
|
|||
}
|
||||
}
|
||||
|
||||
ty::Ref(lifetime, ty, mutability) => {
|
||||
ty::Ref(region, ty, mutability) => {
|
||||
let layout = layout_of(cx, *ty)?;
|
||||
let align = layout.align.abi.bytes_usize();
|
||||
let size = layout.size.bytes_usize();
|
||||
Ok(Tree::Ref(Ref {
|
||||
lifetime: *lifetime,
|
||||
ty: *ty,
|
||||
mutability: *mutability,
|
||||
align,
|
||||
size,
|
||||
let referent_align = layout.align.abi.bytes_usize();
|
||||
let referent_size = layout.size.bytes_usize();
|
||||
|
||||
Ok(Tree::Ref(Reference {
|
||||
region: *region,
|
||||
is_mut: mutability.is_mut(),
|
||||
referent: *ty,
|
||||
referent_align,
|
||||
referent_size,
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,13 +20,13 @@ mod prune {
|
|||
|
||||
#[test]
|
||||
fn seq_1() {
|
||||
let layout: Tree<Def, !> = Tree::def(Def::NoSafetyInvariants).then(Tree::byte(0x00));
|
||||
let layout: Tree<Def, !, !> = Tree::def(Def::NoSafetyInvariants).then(Tree::byte(0x00));
|
||||
assert_eq!(layout.prune(&|d| matches!(d, Def::HasSafetyInvariants)), Tree::byte(0x00));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seq_2() {
|
||||
let layout: Tree<Def, !> =
|
||||
let layout: Tree<Def, !, !> =
|
||||
Tree::byte(0x00).then(Tree::def(Def::NoSafetyInvariants)).then(Tree::byte(0x01));
|
||||
|
||||
assert_eq!(
|
||||
|
|
@ -41,7 +41,7 @@ mod prune {
|
|||
|
||||
#[test]
|
||||
fn invisible_def() {
|
||||
let layout: Tree<Def, !> = Tree::def(Def::HasSafetyInvariants);
|
||||
let layout: Tree<Def, !, !> = Tree::def(Def::HasSafetyInvariants);
|
||||
assert_eq!(
|
||||
layout.prune(&|d| matches!(d, Def::HasSafetyInvariants)),
|
||||
Tree::uninhabited()
|
||||
|
|
@ -50,7 +50,7 @@ mod prune {
|
|||
|
||||
#[test]
|
||||
fn invisible_def_in_seq_len_2() {
|
||||
let layout: Tree<Def, !> =
|
||||
let layout: Tree<Def, !, !> =
|
||||
Tree::def(Def::NoSafetyInvariants).then(Tree::def(Def::HasSafetyInvariants));
|
||||
assert_eq!(
|
||||
layout.prune(&|d| matches!(d, Def::HasSafetyInvariants)),
|
||||
|
|
@ -60,7 +60,7 @@ mod prune {
|
|||
|
||||
#[test]
|
||||
fn invisible_def_in_seq_len_3() {
|
||||
let layout: Tree<Def, !> = Tree::def(Def::NoSafetyInvariants)
|
||||
let layout: Tree<Def, !, !> = Tree::def(Def::NoSafetyInvariants)
|
||||
.then(Tree::byte(0x00))
|
||||
.then(Tree::def(Def::HasSafetyInvariants));
|
||||
assert_eq!(
|
||||
|
|
@ -75,20 +75,20 @@ mod prune {
|
|||
|
||||
#[test]
|
||||
fn visible_def() {
|
||||
let layout: Tree<Def, !> = Tree::def(Def::NoSafetyInvariants);
|
||||
let layout: Tree<Def, !, !> = Tree::def(Def::NoSafetyInvariants);
|
||||
assert_eq!(layout.prune(&|d| matches!(d, Def::HasSafetyInvariants)), Tree::unit());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn visible_def_in_seq_len_2() {
|
||||
let layout: Tree<Def, !> =
|
||||
let layout: Tree<Def, !, !> =
|
||||
Tree::def(Def::NoSafetyInvariants).then(Tree::def(Def::NoSafetyInvariants));
|
||||
assert_eq!(layout.prune(&|d| matches!(d, Def::HasSafetyInvariants)), Tree::unit());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn visible_def_in_seq_len_3() {
|
||||
let layout: Tree<Def, !> = Tree::def(Def::NoSafetyInvariants)
|
||||
let layout: Tree<Def, !, !> = Tree::def(Def::NoSafetyInvariants)
|
||||
.then(Tree::byte(0x00))
|
||||
.then(Tree::def(Def::NoSafetyInvariants));
|
||||
assert_eq!(layout.prune(&|d| matches!(d, Def::HasSafetyInvariants)), Tree::byte(0x00));
|
||||
|
|
|
|||
|
|
@ -19,23 +19,29 @@ pub struct Assume {
|
|||
/// Either transmutation is allowed, we have an error, or we have an optional
|
||||
/// Condition that must hold.
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
|
||||
pub enum Answer<R> {
|
||||
pub enum Answer<R, T> {
|
||||
Yes,
|
||||
No(Reason<R>),
|
||||
If(Condition<R>),
|
||||
No(Reason<T>),
|
||||
If(Condition<R, T>),
|
||||
}
|
||||
|
||||
/// A condition which must hold for safe transmutation to be possible.
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
|
||||
pub enum Condition<R> {
|
||||
pub enum Condition<R, T> {
|
||||
/// `Src` is transmutable into `Dst`, if `src` is transmutable into `dst`.
|
||||
IfTransmutable { src: R, dst: R },
|
||||
Transmutable { src: T, dst: T },
|
||||
|
||||
/// The region `long` must outlive `short`.
|
||||
Outlives { long: R, short: R },
|
||||
|
||||
/// The `ty` is immutable.
|
||||
Immutable { ty: T },
|
||||
|
||||
/// `Src` is transmutable into `Dst`, if all of the enclosed requirements are met.
|
||||
IfAll(Vec<Condition<R>>),
|
||||
IfAll(Vec<Condition<R, T>>),
|
||||
|
||||
/// `Src` is transmutable into `Dst` if any of the enclosed requirements are met.
|
||||
IfAny(Vec<Condition<R>>),
|
||||
IfAny(Vec<Condition<R, T>>),
|
||||
}
|
||||
|
||||
/// Answers "why wasn't the source type transmutable into the destination type?"
|
||||
|
|
@ -53,12 +59,16 @@ pub enum Reason<T> {
|
|||
DstMayHaveSafetyInvariants,
|
||||
/// `Dst` is larger than `Src`, and the excess bytes were not exclusively uninitialized.
|
||||
DstIsTooBig,
|
||||
/// A referent of `Dst` is larger than a referent in `Src`.
|
||||
/// `Dst` is larger `Src`.
|
||||
DstRefIsTooBig {
|
||||
/// The referent of the source type.
|
||||
src: T,
|
||||
/// The size of the source type's referent.
|
||||
src_size: usize,
|
||||
/// The too-large referent of the destination type.
|
||||
dst: T,
|
||||
/// The size of the destination type's referent.
|
||||
dst_size: usize,
|
||||
},
|
||||
/// Src should have a stricter alignment than Dst, but it does not.
|
||||
DstHasStricterAlignment { src_min_align: usize, dst_min_align: usize },
|
||||
|
|
@ -79,7 +89,7 @@ pub enum Reason<T> {
|
|||
#[cfg(feature = "rustc")]
|
||||
mod rustc {
|
||||
use rustc_hir::lang_items::LangItem;
|
||||
use rustc_middle::ty::{Const, Ty, TyCtxt};
|
||||
use rustc_middle::ty::{Const, Region, Ty, TyCtxt};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
|
@ -105,7 +115,7 @@ mod rustc {
|
|||
&mut self,
|
||||
types: Types<'tcx>,
|
||||
assume: crate::Assume,
|
||||
) -> crate::Answer<crate::layout::rustc::Ref<'tcx>> {
|
||||
) -> crate::Answer<Region<'tcx>, Ty<'tcx>> {
|
||||
crate::maybe_transmutable::MaybeTransmutableQuery::new(
|
||||
types.src, types.dst, assume, self.tcx,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ pub(crate) mod query_context;
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::layout::{self, Def, Dfa, Ref, Tree, dfa, union};
|
||||
use crate::layout::{self, Def, Dfa, Reference, Tree, dfa, union};
|
||||
use crate::maybe_transmutable::query_context::QueryContext;
|
||||
use crate::{Answer, Condition, Map, Reason};
|
||||
|
||||
|
|
@ -41,7 +41,10 @@ mod rustc {
|
|||
/// This method begins by converting `src` and `dst` from `Ty`s to `Tree`s,
|
||||
/// then computes an answer using those trees.
|
||||
#[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))]
|
||||
pub(crate) fn answer(self) -> Answer<<TyCtxt<'tcx> as QueryContext>::Ref> {
|
||||
pub(crate) fn answer(
|
||||
self,
|
||||
) -> Answer<<TyCtxt<'tcx> as QueryContext>::Region, <TyCtxt<'tcx> as QueryContext>::Type>
|
||||
{
|
||||
let Self { src, dst, assume, context } = self;
|
||||
|
||||
let layout_cx = LayoutCx::new(context, TypingEnv::fully_monomorphized());
|
||||
|
|
@ -67,7 +70,11 @@ mod rustc {
|
|||
}
|
||||
}
|
||||
|
||||
impl<C> MaybeTransmutableQuery<Tree<<C as QueryContext>::Def, <C as QueryContext>::Ref>, C>
|
||||
impl<C>
|
||||
MaybeTransmutableQuery<
|
||||
Tree<<C as QueryContext>::Def, <C as QueryContext>::Region, <C as QueryContext>::Type>,
|
||||
C,
|
||||
>
|
||||
where
|
||||
C: QueryContext,
|
||||
{
|
||||
|
|
@ -77,7 +84,7 @@ where
|
|||
/// then converts `src` and `dst` to `Dfa`s, and computes an answer using those DFAs.
|
||||
#[inline(always)]
|
||||
#[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>::Region, <C as QueryContext>::Type> {
|
||||
let Self { src, dst, assume, context } = self;
|
||||
|
||||
// Unconditionally remove all `Def` nodes from `src`, without pruning away the
|
||||
|
|
@ -130,12 +137,12 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<C> MaybeTransmutableQuery<Dfa<<C as QueryContext>::Ref>, C>
|
||||
impl<C> MaybeTransmutableQuery<Dfa<<C as QueryContext>::Region, <C as QueryContext>::Type>, C>
|
||||
where
|
||||
C: QueryContext,
|
||||
{
|
||||
/// Answers whether a `Dfa` is transmutable into another `Dfa`.
|
||||
pub(crate) fn answer(self) -> Answer<<C as QueryContext>::Ref> {
|
||||
pub(crate) fn answer(self) -> Answer<<C as QueryContext>::Region, <C as QueryContext>::Type> {
|
||||
self.answer_memo(&mut Map::default(), self.src.start, self.dst.start)
|
||||
}
|
||||
|
||||
|
|
@ -143,10 +150,13 @@ where
|
|||
#[instrument(level = "debug", skip(self))]
|
||||
fn answer_memo(
|
||||
&self,
|
||||
cache: &mut Map<(dfa::State, dfa::State), Answer<<C as QueryContext>::Ref>>,
|
||||
cache: &mut Map<
|
||||
(dfa::State, dfa::State),
|
||||
Answer<<C as QueryContext>::Region, <C as QueryContext>::Type>,
|
||||
>,
|
||||
src_state: dfa::State,
|
||||
dst_state: dfa::State,
|
||||
) -> Answer<<C as QueryContext>::Ref> {
|
||||
) -> Answer<<C as QueryContext>::Region, <C as QueryContext>::Type> {
|
||||
if let Some(answer) = cache.get(&(src_state, dst_state)) {
|
||||
answer.clone()
|
||||
} else {
|
||||
|
|
@ -160,10 +170,13 @@ where
|
|||
|
||||
fn answer_impl(
|
||||
&self,
|
||||
cache: &mut Map<(dfa::State, dfa::State), Answer<<C as QueryContext>::Ref>>,
|
||||
cache: &mut Map<
|
||||
(dfa::State, dfa::State),
|
||||
Answer<<C as QueryContext>::Region, <C as QueryContext>::Type>,
|
||||
>,
|
||||
src_state: dfa::State,
|
||||
dst_state: dfa::State,
|
||||
) -> Answer<<C as QueryContext>::Ref> {
|
||||
) -> Answer<<C as QueryContext>::Region, <C as QueryContext>::Type> {
|
||||
debug!(?src_state, ?dst_state);
|
||||
debug!(src = ?self.src);
|
||||
debug!(dst = ?self.dst);
|
||||
|
|
@ -247,27 +260,51 @@ where
|
|||
// ...there exists a reference transition out of `dst_state`...
|
||||
Quantifier::ThereExists.apply(self.dst.refs_from(dst_state).map(
|
||||
|(dst_ref, dst_state_prime)| {
|
||||
if !src_ref.is_mutable() && dst_ref.is_mutable() {
|
||||
if !src_ref.is_mut && dst_ref.is_mut {
|
||||
Answer::No(Reason::DstIsMoreUnique)
|
||||
} else if !self.assume.alignment
|
||||
&& src_ref.min_align() < dst_ref.min_align()
|
||||
&& src_ref.referent_align < dst_ref.referent_align
|
||||
{
|
||||
Answer::No(Reason::DstHasStricterAlignment {
|
||||
src_min_align: src_ref.min_align(),
|
||||
dst_min_align: dst_ref.min_align(),
|
||||
src_min_align: src_ref.referent_align,
|
||||
dst_min_align: dst_ref.referent_align,
|
||||
})
|
||||
} else if dst_ref.referent_size > src_ref.referent_size {
|
||||
Answer::No(Reason::DstRefIsTooBig {
|
||||
src: src_ref.referent,
|
||||
src_size: src_ref.referent_size,
|
||||
dst: dst_ref.referent,
|
||||
dst_size: dst_ref.referent_size,
|
||||
})
|
||||
} else if dst_ref.size() > src_ref.size() {
|
||||
Answer::No(Reason::DstRefIsTooBig { src: src_ref, dst: dst_ref })
|
||||
} else {
|
||||
// ...such that `src` is transmutable into `dst`, if
|
||||
// `src_ref` is transmutability into `dst_ref`.
|
||||
and(
|
||||
Answer::If(Condition::IfTransmutable {
|
||||
src: src_ref,
|
||||
dst: dst_ref,
|
||||
}),
|
||||
self.answer_memo(cache, src_state_prime, dst_state_prime),
|
||||
)
|
||||
let mut conditions = Vec::with_capacity(4);
|
||||
let mut is_transmutable =
|
||||
|src: Reference<_, _>, dst: Reference<_, _>| {
|
||||
conditions.push(Condition::Transmutable {
|
||||
src: src.referent,
|
||||
dst: dst.referent,
|
||||
});
|
||||
if !self.assume.lifetimes {
|
||||
conditions.push(Condition::Outlives {
|
||||
long: src.region,
|
||||
short: dst.region,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
is_transmutable(src_ref, dst_ref);
|
||||
|
||||
if dst_ref.is_mut {
|
||||
is_transmutable(dst_ref, src_ref);
|
||||
} else {
|
||||
conditions.push(Condition::Immutable { ty: dst_ref.referent });
|
||||
}
|
||||
|
||||
Answer::If(Condition::IfAll(conditions)).and(self.answer_memo(
|
||||
cache,
|
||||
src_state_prime,
|
||||
dst_state_prime,
|
||||
))
|
||||
}
|
||||
},
|
||||
))
|
||||
|
|
@ -275,67 +312,65 @@ where
|
|||
);
|
||||
|
||||
if self.assume.validity {
|
||||
or(bytes_answer, refs_answer)
|
||||
bytes_answer.or(refs_answer)
|
||||
} else {
|
||||
and(bytes_answer, refs_answer)
|
||||
bytes_answer.and(refs_answer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn and<R>(lhs: Answer<R>, rhs: Answer<R>) -> Answer<R>
|
||||
where
|
||||
R: PartialEq,
|
||||
{
|
||||
match (lhs, rhs) {
|
||||
// If both are errors, then we should return the more specific one
|
||||
(Answer::No(Reason::DstIsBitIncompatible), Answer::No(reason))
|
||||
| (Answer::No(reason), Answer::No(_))
|
||||
// If either is an error, return it
|
||||
| (Answer::No(reason), _) | (_, Answer::No(reason)) => Answer::No(reason),
|
||||
// If only one side has a condition, pass it along
|
||||
| (Answer::Yes, other) | (other, Answer::Yes) => other,
|
||||
// If both sides have IfAll conditions, merge them
|
||||
(Answer::If(Condition::IfAll(mut lhs)), Answer::If(Condition::IfAll(ref mut rhs))) => {
|
||||
lhs.append(rhs);
|
||||
Answer::If(Condition::IfAll(lhs))
|
||||
impl<R, T> Answer<R, T> {
|
||||
fn and(self, rhs: Answer<R, T>) -> Answer<R, T> {
|
||||
let lhs = self;
|
||||
match (lhs, rhs) {
|
||||
// If both are errors, then we should return the more specific one
|
||||
(Answer::No(Reason::DstIsBitIncompatible), Answer::No(reason))
|
||||
| (Answer::No(reason), Answer::No(_))
|
||||
// If either is an error, return it
|
||||
| (Answer::No(reason), _) | (_, Answer::No(reason)) => Answer::No(reason),
|
||||
// If only one side has a condition, pass it along
|
||||
| (Answer::Yes, other) | (other, Answer::Yes) => other,
|
||||
// If both sides have IfAll conditions, merge them
|
||||
(Answer::If(Condition::IfAll(mut lhs)), Answer::If(Condition::IfAll(ref mut rhs))) => {
|
||||
lhs.append(rhs);
|
||||
Answer::If(Condition::IfAll(lhs))
|
||||
}
|
||||
// If only one side is an IfAll, add the other Condition to it
|
||||
(Answer::If(cond), Answer::If(Condition::IfAll(mut conds)))
|
||||
| (Answer::If(Condition::IfAll(mut conds)), Answer::If(cond)) => {
|
||||
conds.push(cond);
|
||||
Answer::If(Condition::IfAll(conds))
|
||||
}
|
||||
// Otherwise, both lhs and rhs conditions can be combined in a parent IfAll
|
||||
(Answer::If(lhs), Answer::If(rhs)) => Answer::If(Condition::IfAll(vec![lhs, rhs])),
|
||||
}
|
||||
// If only one side is an IfAll, add the other Condition to it
|
||||
(Answer::If(cond), Answer::If(Condition::IfAll(mut conds)))
|
||||
| (Answer::If(Condition::IfAll(mut conds)), Answer::If(cond)) => {
|
||||
conds.push(cond);
|
||||
Answer::If(Condition::IfAll(conds))
|
||||
}
|
||||
// Otherwise, both lhs and rhs conditions can be combined in a parent IfAll
|
||||
(Answer::If(lhs), Answer::If(rhs)) => Answer::If(Condition::IfAll(vec![lhs, rhs])),
|
||||
}
|
||||
}
|
||||
|
||||
fn or<R>(lhs: Answer<R>, rhs: Answer<R>) -> Answer<R>
|
||||
where
|
||||
R: PartialEq,
|
||||
{
|
||||
match (lhs, rhs) {
|
||||
// If both are errors, then we should return the more specific one
|
||||
(Answer::No(Reason::DstIsBitIncompatible), Answer::No(reason))
|
||||
| (Answer::No(reason), Answer::No(_)) => Answer::No(reason),
|
||||
// Otherwise, errors can be ignored for the rest of the pattern matching
|
||||
(Answer::No(_), other) | (other, Answer::No(_)) => or(other, Answer::Yes),
|
||||
// If only one side has a condition, pass it along
|
||||
(Answer::Yes, other) | (other, Answer::Yes) => other,
|
||||
// If both sides have IfAny conditions, merge them
|
||||
(Answer::If(Condition::IfAny(mut lhs)), Answer::If(Condition::IfAny(ref mut rhs))) => {
|
||||
lhs.append(rhs);
|
||||
Answer::If(Condition::IfAny(lhs))
|
||||
fn or(self, rhs: Answer<R, T>) -> Answer<R, T> {
|
||||
let lhs = self;
|
||||
match (lhs, rhs) {
|
||||
// If both are errors, then we should return the more specific one
|
||||
(Answer::No(Reason::DstIsBitIncompatible), Answer::No(reason))
|
||||
| (Answer::No(reason), Answer::No(_)) => Answer::No(reason),
|
||||
// Otherwise, errors can be ignored for the rest of the pattern matching
|
||||
(Answer::No(_), other) | (other, Answer::No(_)) => other.or(Answer::Yes),
|
||||
// If only one side has a condition, pass it along
|
||||
(Answer::Yes, other) | (other, Answer::Yes) => other,
|
||||
// If both sides have IfAny conditions, merge them
|
||||
(Answer::If(Condition::IfAny(mut lhs)), Answer::If(Condition::IfAny(ref mut rhs))) => {
|
||||
lhs.append(rhs);
|
||||
Answer::If(Condition::IfAny(lhs))
|
||||
}
|
||||
// If only one side is an IfAny, add the other Condition to it
|
||||
(Answer::If(cond), Answer::If(Condition::IfAny(mut conds)))
|
||||
| (Answer::If(Condition::IfAny(mut conds)), Answer::If(cond)) => {
|
||||
conds.push(cond);
|
||||
Answer::If(Condition::IfAny(conds))
|
||||
}
|
||||
// Otherwise, both lhs and rhs conditions can be combined in a parent IfAny
|
||||
(Answer::If(lhs), Answer::If(rhs)) => Answer::If(Condition::IfAny(vec![lhs, rhs])),
|
||||
}
|
||||
// If only one side is an IfAny, add the other Condition to it
|
||||
(Answer::If(cond), Answer::If(Condition::IfAny(mut conds)))
|
||||
| (Answer::If(Condition::IfAny(mut conds)), Answer::If(cond)) => {
|
||||
conds.push(cond);
|
||||
Answer::If(Condition::IfAny(conds))
|
||||
}
|
||||
// Otherwise, both lhs and rhs conditions can be combined in a parent IfAny
|
||||
(Answer::If(lhs), Answer::If(rhs)) => Answer::If(Condition::IfAny(vec![lhs, rhs])),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -345,24 +380,25 @@ enum Quantifier {
|
|||
}
|
||||
|
||||
impl Quantifier {
|
||||
fn apply<R, I>(&self, iter: I) -> Answer<R>
|
||||
fn apply<R, T, I>(&self, iter: I) -> Answer<R, T>
|
||||
where
|
||||
R: layout::Ref,
|
||||
I: IntoIterator<Item = Answer<R>>,
|
||||
R: layout::Region,
|
||||
T: layout::Type,
|
||||
I: IntoIterator<Item = Answer<R, T>>,
|
||||
{
|
||||
use std::ops::ControlFlow::{Break, Continue};
|
||||
|
||||
let (init, try_fold_f): (_, fn(_, _) -> _) = match self {
|
||||
Self::ThereExists => {
|
||||
(Answer::No(Reason::DstIsBitIncompatible), |accum: Answer<R>, next| {
|
||||
match or(accum, next) {
|
||||
Answer::Yes => Break(Answer::Yes),
|
||||
maybe => Continue(maybe),
|
||||
}
|
||||
(Answer::No(Reason::DstIsBitIncompatible), |accum: Answer<R, T>, next| match accum
|
||||
.or(next)
|
||||
{
|
||||
Answer::Yes => Break(Answer::Yes),
|
||||
maybe => Continue(maybe),
|
||||
})
|
||||
}
|
||||
Self::ForAll => (Answer::Yes, |accum: Answer<R>, next| {
|
||||
let answer = and(accum, next);
|
||||
Self::ForAll => (Answer::Yes, |accum: Answer<R, T>, next| {
|
||||
let answer = accum.and(next);
|
||||
match answer {
|
||||
Answer::No(_) => Break(answer),
|
||||
maybe => Continue(maybe),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ use crate::layout;
|
|||
/// Context necessary to answer the question "Are these types transmutable?".
|
||||
pub(crate) trait QueryContext {
|
||||
type Def: layout::Def;
|
||||
type Ref: layout::Ref;
|
||||
type Region: layout::Region;
|
||||
type Type: layout::Type;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -12,9 +13,9 @@ pub(crate) mod test {
|
|||
|
||||
use super::QueryContext;
|
||||
|
||||
pub(crate) struct UltraMinimal<R = !>(PhantomData<R>);
|
||||
pub(crate) struct UltraMinimal<R = !, T = !>(PhantomData<(R, T)>);
|
||||
|
||||
impl<R> Default for UltraMinimal<R> {
|
||||
impl<R, T> Default for UltraMinimal<R, T> {
|
||||
fn default() -> Self {
|
||||
Self(PhantomData)
|
||||
}
|
||||
|
|
@ -32,20 +33,26 @@ pub(crate) mod test {
|
|||
}
|
||||
}
|
||||
|
||||
impl<R: crate::layout::Ref> QueryContext for UltraMinimal<R> {
|
||||
impl<R, T> QueryContext for UltraMinimal<R, T>
|
||||
where
|
||||
R: crate::layout::Region,
|
||||
T: crate::layout::Type,
|
||||
{
|
||||
type Def = Def;
|
||||
type Ref = R;
|
||||
type Region = R;
|
||||
type Type = T;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustc")]
|
||||
mod rustc {
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_middle::ty::{Region, Ty, TyCtxt};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<'tcx> super::QueryContext for TyCtxt<'tcx> {
|
||||
type Def = layout::rustc::Def<'tcx>;
|
||||
type Ref = layout::rustc::Ref<'tcx>;
|
||||
type Region = Region<'tcx>;
|
||||
type Type = Ty<'tcx>;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,17 +3,17 @@ extern crate test;
|
|||
use itertools::Itertools;
|
||||
|
||||
use super::query_context::test::{Def, UltraMinimal};
|
||||
use crate::{Answer, Assume, Reason, layout};
|
||||
use crate::{Answer, Assume, Condition, Reason, layout};
|
||||
|
||||
type Tree = layout::Tree<Def, !>;
|
||||
type Dfa = layout::Dfa<!>;
|
||||
type Tree = layout::Tree<Def, !, !>;
|
||||
type Dfa = layout::Dfa<!, !>;
|
||||
|
||||
trait Representation {
|
||||
fn is_transmutable(src: Self, dst: Self, assume: Assume) -> Answer<!>;
|
||||
fn is_transmutable(src: Self, dst: Self, assume: Assume) -> Answer<!, !>;
|
||||
}
|
||||
|
||||
impl Representation for Tree {
|
||||
fn is_transmutable(src: Self, dst: Self, assume: Assume) -> Answer<!> {
|
||||
fn is_transmutable(src: Self, dst: Self, assume: Assume) -> Answer<!, !> {
|
||||
crate::maybe_transmutable::MaybeTransmutableQuery::new(
|
||||
src,
|
||||
dst,
|
||||
|
|
@ -25,7 +25,7 @@ impl Representation for Tree {
|
|||
}
|
||||
|
||||
impl Representation for Dfa {
|
||||
fn is_transmutable(src: Self, dst: Self, assume: Assume) -> Answer<!> {
|
||||
fn is_transmutable(src: Self, dst: Self, assume: Assume) -> Answer<!, !> {
|
||||
crate::maybe_transmutable::MaybeTransmutableQuery::new(
|
||||
src,
|
||||
dst,
|
||||
|
|
@ -40,7 +40,7 @@ fn is_transmutable<R: Representation + Clone>(
|
|||
src: &R,
|
||||
dst: &R,
|
||||
assume: Assume,
|
||||
) -> crate::Answer<!> {
|
||||
) -> crate::Answer<!, !> {
|
||||
let src = src.clone();
|
||||
let dst = dst.clone();
|
||||
// The only dimension of the transmutability analysis we want to test
|
||||
|
|
@ -53,7 +53,7 @@ mod safety {
|
|||
use super::*;
|
||||
use crate::Answer;
|
||||
|
||||
const DST_HAS_SAFETY_INVARIANTS: Answer<!> =
|
||||
const DST_HAS_SAFETY_INVARIANTS: Answer<!, !> =
|
||||
Answer::No(crate::Reason::DstMayHaveSafetyInvariants);
|
||||
|
||||
#[test]
|
||||
|
|
@ -177,14 +177,15 @@ mod bool {
|
|||
|
||||
#[test]
|
||||
fn should_permit_validity_expansion_and_reject_contraction() {
|
||||
let b0 = layout::Tree::<Def, !>::byte(0);
|
||||
let b1 = layout::Tree::<Def, !>::byte(1);
|
||||
let b2 = layout::Tree::<Def, !>::byte(2);
|
||||
let b0 = layout::Tree::<Def, !, !>::byte(0);
|
||||
let b1 = layout::Tree::<Def, !, !>::byte(1);
|
||||
let b2 = layout::Tree::<Def, !, !>::byte(2);
|
||||
|
||||
let alts = [b0, b1, b2];
|
||||
|
||||
let into_layout = |alts: Vec<_>| {
|
||||
alts.into_iter().fold(layout::Tree::<Def, !>::uninhabited(), layout::Tree::<Def, !>::or)
|
||||
alts.into_iter()
|
||||
.fold(layout::Tree::<Def, !, !>::uninhabited(), layout::Tree::<Def, !, !>::or)
|
||||
};
|
||||
|
||||
let into_set = |alts: Vec<_>| {
|
||||
|
|
@ -277,7 +278,7 @@ mod alt {
|
|||
|
||||
#[test]
|
||||
fn should_permit_identity_transmutation() {
|
||||
type Tree = layout::Tree<Def, !>;
|
||||
type Tree = layout::Tree<Def, !, !>;
|
||||
|
||||
let x = Tree::Seq(vec![Tree::byte(0), Tree::byte(0)]);
|
||||
let y = Tree::Seq(vec![Tree::bool(), Tree::byte(1)]);
|
||||
|
|
@ -331,7 +332,7 @@ mod char {
|
|||
fn should_permit_valid_transmutation() {
|
||||
for order in [Endian::Big, Endian::Little] {
|
||||
use Answer::*;
|
||||
let char_layout = layout::Tree::<Def, !>::char(order);
|
||||
let char_layout = layout::Tree::<Def, !, !>::char(order);
|
||||
|
||||
// `char`s can be in the following ranges:
|
||||
// - [0, 0xD7FF]
|
||||
|
|
@ -353,7 +354,7 @@ mod char {
|
|||
(0xFFFFFFFF, no),
|
||||
] {
|
||||
let src_layout =
|
||||
layout::tree::Tree::<Def, !>::from_big_endian(order, src.to_be_bytes());
|
||||
layout::tree::Tree::<Def, !, !>::from_big_endian(order, src.to_be_bytes());
|
||||
|
||||
let a = is_transmutable(&src_layout, &char_layout, Assume::default());
|
||||
assert_eq!(a, answer, "endian:{order:?},\nsrc:{src:x}");
|
||||
|
|
@ -371,7 +372,7 @@ mod nonzero {
|
|||
#[test]
|
||||
fn should_permit_identity_transmutation() {
|
||||
for width in NONZERO_BYTE_WIDTHS {
|
||||
let layout = layout::Tree::<Def, !>::nonzero(width);
|
||||
let layout = layout::Tree::<Def, !, !>::nonzero(width);
|
||||
assert_eq!(is_transmutable(&layout, &layout, Assume::default()), Answer::Yes);
|
||||
}
|
||||
}
|
||||
|
|
@ -381,8 +382,8 @@ mod nonzero {
|
|||
for width in NONZERO_BYTE_WIDTHS {
|
||||
use Answer::*;
|
||||
|
||||
let num = layout::Tree::<Def, !>::number(width);
|
||||
let nz = layout::Tree::<Def, !>::nonzero(width);
|
||||
let num = layout::Tree::<Def, !, !>::number(width);
|
||||
let nz = layout::Tree::<Def, !, !>::nonzero(width);
|
||||
|
||||
let a = is_transmutable(&num, &nz, Assume::default());
|
||||
assert_eq!(a, No(Reason::DstIsBitIncompatible), "width:{width}");
|
||||
|
|
@ -395,13 +396,23 @@ mod nonzero {
|
|||
|
||||
mod r#ref {
|
||||
use super::*;
|
||||
use crate::layout::Reference;
|
||||
|
||||
#[test]
|
||||
fn should_permit_identity_transmutation() {
|
||||
type Tree = crate::layout::Tree<Def, [(); 1]>;
|
||||
type Tree = crate::layout::Tree<Def, usize, ()>;
|
||||
|
||||
for validity in [false, true] {
|
||||
let layout = Tree::Seq(vec![Tree::byte(0x00), Tree::Ref([()])]);
|
||||
let layout = Tree::Seq(vec![
|
||||
Tree::byte(0x00),
|
||||
Tree::Ref(Reference {
|
||||
region: 42,
|
||||
is_mut: false,
|
||||
referent: (),
|
||||
referent_size: 0,
|
||||
referent_align: 1,
|
||||
}),
|
||||
]);
|
||||
|
||||
let assume = Assume { validity, ..Assume::default() };
|
||||
|
||||
|
|
@ -414,7 +425,11 @@ mod r#ref {
|
|||
.answer();
|
||||
assert_eq!(
|
||||
answer,
|
||||
Answer::If(crate::Condition::IfTransmutable { src: [()], dst: [()] })
|
||||
Answer::If(Condition::IfAll(vec![
|
||||
Condition::Transmutable { src: (), dst: () },
|
||||
Condition::Outlives { long: 42, short: 42 },
|
||||
Condition::Immutable { ty: () },
|
||||
]))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ error[E0277]: `&Packed<Two>` cannot be safely transmuted into `&Packed<Four>`
|
|||
--> $DIR/reject_extension.rs:48:37
|
||||
|
|
||||
LL | assert::is_transmutable::<&Src, &Dst>();
|
||||
| ^^^^ the referent size of `&Packed<Two>` (2 bytes) is smaller than that of `&Packed<Four>` (4 bytes)
|
||||
| ^^^^ the size of `Packed<Two>` (2 bytes) is smaller than that of `Packed<Four>` (4 bytes)
|
||||
|
|
||||
note: required by a bound in `is_transmutable`
|
||||
--> $DIR/reject_extension.rs:13:14
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ error[E0277]: `&Unit` cannot be safely transmuted into `&u8`
|
|||
--> $DIR/unit-to-u8.rs:22:52
|
||||
|
|
||||
LL | assert::is_maybe_transmutable::<&'static Unit, &'static u8>();
|
||||
| ^^^^^^^^^^^ the referent size of `&Unit` (0 bytes) is smaller than that of `&u8` (1 bytes)
|
||||
| ^^^^^^^^^^^ the size of `Unit` (0 bytes) is smaller than that of `u8` (1 bytes)
|
||||
|
|
||||
note: required by a bound in `is_maybe_transmutable`
|
||||
--> $DIR/unit-to-u8.rs:9:14
|
||||
|
|
|
|||
|
|
@ -38,10 +38,10 @@ fn mut_to_mut() {
|
|||
}
|
||||
|
||||
fn mut_to_ref() {
|
||||
// We don't care about `UnsafeCell` for transmutations in the form `&mut T
|
||||
// -> &U`, because downgrading a `&mut T` to a `&U` deactivates `&mut T` for
|
||||
// the lifetime of `&U`.
|
||||
assert::is_maybe_transmutable::<&'static mut u8, &'static UnsafeCell<u8>>();
|
||||
assert::is_maybe_transmutable::<&'static mut UnsafeCell<u8>, &'static u8>();
|
||||
assert::is_maybe_transmutable::<&'static mut UnsafeCell<u8>, &'static UnsafeCell<u8>>();
|
||||
// `&mut UnsafeCell` is irrelevant in the source.
|
||||
assert::is_maybe_transmutable::<&'static mut UnsafeCell<bool>, &'static u8>();
|
||||
// `&UnsafeCell` in forbidden in the destination, since the destination can be used to
|
||||
// invalidate a shadowed source reference.
|
||||
assert::is_maybe_transmutable::<&'static mut bool, &'static UnsafeCell<u8>>(); //~ ERROR: cannot be safely transmuted
|
||||
assert::is_maybe_transmutable::<&'static mut UnsafeCell<bool>, &'static UnsafeCell<u8>>(); //~ ERROR: cannot be safely transmuted
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,36 @@ LL | where
|
|||
LL | Dst: TransmuteFrom<Src, { Assume::SAFETY }>
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `is_maybe_transmutable`
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
error[E0277]: `&mut bool` cannot be safely transmuted into `&UnsafeCell<u8>`
|
||||
--> $DIR/unsafecell.rs:45:56
|
||||
|
|
||||
LL | assert::is_maybe_transmutable::<&'static mut bool, &'static UnsafeCell<u8>>();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ the trait `Freeze` is not implemented for `UnsafeCell<u8>`
|
||||
|
|
||||
note: required by a bound in `is_maybe_transmutable`
|
||||
--> $DIR/unsafecell.rs:12:14
|
||||
|
|
||||
LL | pub fn is_maybe_transmutable<Src, Dst>()
|
||||
| --------------------- required by a bound in this function
|
||||
LL | where
|
||||
LL | Dst: TransmuteFrom<Src, { Assume::SAFETY }>
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `is_maybe_transmutable`
|
||||
|
||||
error[E0277]: `&mut UnsafeCell<bool>` cannot be safely transmuted into `&UnsafeCell<u8>`
|
||||
--> $DIR/unsafecell.rs:46:68
|
||||
|
|
||||
LL | assert::is_maybe_transmutable::<&'static mut UnsafeCell<bool>, &'static UnsafeCell<u8>>();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ the trait `Freeze` is not implemented for `UnsafeCell<u8>`
|
||||
|
|
||||
note: required by a bound in `is_maybe_transmutable`
|
||||
--> $DIR/unsafecell.rs:12:14
|
||||
|
|
||||
LL | pub fn is_maybe_transmutable<Src, Dst>()
|
||||
| --------------------- required by a bound in this function
|
||||
LL | where
|
||||
LL | Dst: TransmuteFrom<Src, { Assume::SAFETY }>
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `is_maybe_transmutable`
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
|
||||
For more information about this error, try `rustc --explain E0277`.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue