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:
Jack Wrenn 2025-06-04 15:58:01 +00:00
parent 7c10378e1f
commit e9eae28eee
14 changed files with 385 additions and 357 deletions

View file

@ -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)"
)
}

View file

@ -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,
)]
}
}
}

View file

@ -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 {{")?;

View file

@ -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>,

View file

@ -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,
}))
}

View file

@ -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));

View file

@ -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,
)

View file

@ -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),

View file

@ -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>;
}
}

View file

@ -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: () },
]))
);
}
}

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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`.