safe transmute: revise safety analysis
Migrate to a simplified safety analysis that does not use visibility. Closes https://github.com/rust-lang/project-safe-transmute/issues/15
This commit is contained in:
parent
9afdb8d1d5
commit
23ab1bda92
127 changed files with 1387 additions and 1948 deletions
|
@ -3,7 +3,7 @@ pub(crate) mod query_context;
|
|||
mod tests;
|
||||
|
||||
use crate::{
|
||||
layout::{self, dfa, Byte, Dfa, Nfa, Ref, Tree, Uninhabited},
|
||||
layout::{self, dfa, Byte, Def, Dfa, Nfa, Ref, Tree, Uninhabited},
|
||||
maybe_transmutable::query_context::QueryContext,
|
||||
Answer, Condition, Map, Reason,
|
||||
};
|
||||
|
@ -14,7 +14,6 @@ where
|
|||
{
|
||||
src: L,
|
||||
dst: L,
|
||||
scope: <C as QueryContext>::Scope,
|
||||
assume: crate::Assume,
|
||||
context: C,
|
||||
}
|
||||
|
@ -23,14 +22,8 @@ impl<L, C> MaybeTransmutableQuery<L, C>
|
|||
where
|
||||
C: QueryContext,
|
||||
{
|
||||
pub(crate) fn new(
|
||||
src: L,
|
||||
dst: L,
|
||||
scope: <C as QueryContext>::Scope,
|
||||
assume: crate::Assume,
|
||||
context: C,
|
||||
) -> Self {
|
||||
Self { src, dst, scope, assume, context }
|
||||
pub(crate) fn new(src: L, dst: L, assume: crate::Assume, context: C) -> Self {
|
||||
Self { src, dst, assume, context }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,7 +41,7 @@ mod rustc {
|
|||
/// then computes an answer using those trees.
|
||||
#[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))]
|
||||
pub fn answer(self) -> Answer<<TyCtxt<'tcx> as QueryContext>::Ref> {
|
||||
let Self { src, dst, scope, assume, context } = self;
|
||||
let Self { src, dst, assume, context } = self;
|
||||
|
||||
// Convert `src` and `dst` from their rustc representations, to `Tree`-based
|
||||
// representations. If these conversions fail, conclude that the transmutation is
|
||||
|
@ -67,9 +60,7 @@ mod rustc {
|
|||
(_, Err(Err::Unspecified)) => Answer::No(Reason::DstIsUnspecified),
|
||||
(Err(Err::SizeOverflow), _) => Answer::No(Reason::SrcSizeOverflow),
|
||||
(_, Err(Err::SizeOverflow)) => Answer::No(Reason::DstSizeOverflow),
|
||||
(Ok(src), Ok(dst)) => {
|
||||
MaybeTransmutableQuery { src, dst, scope, assume, context }.answer()
|
||||
}
|
||||
(Ok(src), Ok(dst)) => MaybeTransmutableQuery { src, dst, assume, context }.answer(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,43 +77,51 @@ where
|
|||
#[inline(always)]
|
||||
#[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))]
|
||||
pub(crate) fn answer(self) -> Answer<<C as QueryContext>::Ref> {
|
||||
let assume_visibility = self.assume.safety;
|
||||
let Self { src, dst, assume, context } = self;
|
||||
|
||||
let Self { src, dst, scope, assume, context } = self;
|
||||
|
||||
// Remove all `Def` nodes from `src`, without checking their visibility.
|
||||
let src = src.prune(&|def| true);
|
||||
// Unconditionally all `Def` nodes from `src`, without pruning away the
|
||||
// branches they appear in. This is valid to do for value-to-value
|
||||
// transmutations, but not for `&mut T` to `&mut U`; we will need to be
|
||||
// more sophisticated to handle transmutations between mutable
|
||||
// references.
|
||||
let src = src.prune(&|def| false);
|
||||
|
||||
trace!(?src, "pruned src");
|
||||
|
||||
// Remove all `Def` nodes from `dst`, additionally...
|
||||
let dst = if assume_visibility {
|
||||
// ...if visibility is assumed, don't check their visibility.
|
||||
dst.prune(&|def| true)
|
||||
let dst = if assume.safety {
|
||||
// ...if safety is assumed, don't check if they carry safety
|
||||
// invariants; retain all paths.
|
||||
dst.prune(&|def| false)
|
||||
} else {
|
||||
// ...otherwise, prune away all unreachable paths through the `Dst` layout.
|
||||
dst.prune(&|def| context.is_accessible_from(def, scope))
|
||||
// ...otherwise, prune away all paths with safety invariants from
|
||||
// the `Dst` layout.
|
||||
dst.prune(&|def| def.has_safety_invariants())
|
||||
};
|
||||
|
||||
trace!(?dst, "pruned dst");
|
||||
|
||||
// Convert `src` from a tree-based representation to an NFA-based representation.
|
||||
// If the conversion fails because `src` is uninhabited, conclude that the transmutation
|
||||
// is acceptable, because instances of the `src` type do not exist.
|
||||
// Convert `src` from a tree-based representation to an NFA-based
|
||||
// representation. If the conversion fails because `src` is uninhabited,
|
||||
// conclude that the transmutation is acceptable, because instances of
|
||||
// the `src` type do not exist.
|
||||
let src = match Nfa::from_tree(src) {
|
||||
Ok(src) => src,
|
||||
Err(Uninhabited) => return Answer::Yes,
|
||||
};
|
||||
|
||||
// Convert `dst` from a tree-based representation to an NFA-based representation.
|
||||
// If the conversion fails because `src` is uninhabited, conclude that the transmutation
|
||||
// is unacceptable, because instances of the `dst` type do not exist.
|
||||
// Convert `dst` from a tree-based representation to an NFA-based
|
||||
// representation. If the conversion fails because `src` is uninhabited,
|
||||
// conclude that the transmutation is unacceptable. Valid instances of
|
||||
// the `dst` type do not exist, either because it's genuinely
|
||||
// uninhabited, or because there are no branches of the tree that are
|
||||
// free of safety invariants.
|
||||
let dst = match Nfa::from_tree(dst) {
|
||||
Ok(dst) => dst,
|
||||
Err(Uninhabited) => return Answer::No(Reason::DstIsPrivate),
|
||||
Err(Uninhabited) => return Answer::No(Reason::DstMayHaveSafetyInvariants),
|
||||
};
|
||||
|
||||
MaybeTransmutableQuery { src, dst, scope, assume, context }.answer()
|
||||
MaybeTransmutableQuery { src, dst, assume, context }.answer()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,10 +135,10 @@ where
|
|||
#[inline(always)]
|
||||
#[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))]
|
||||
pub(crate) fn answer(self) -> Answer<<C as QueryContext>::Ref> {
|
||||
let Self { src, dst, scope, assume, context } = self;
|
||||
let Self { src, dst, assume, context } = self;
|
||||
let src = Dfa::from_nfa(src);
|
||||
let dst = Dfa::from_nfa(dst);
|
||||
MaybeTransmutableQuery { src, dst, scope, assume, context }.answer()
|
||||
MaybeTransmutableQuery { src, dst, assume, context }.answer()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,9 +6,6 @@ pub(crate) trait QueryContext {
|
|||
type Ref: layout::Ref;
|
||||
type Scope: Copy;
|
||||
|
||||
/// Is `def` accessible from the defining module of `scope`?
|
||||
fn is_accessible_from(&self, def: Self::Def, scope: Self::Scope) -> bool;
|
||||
|
||||
fn min_align(&self, reference: Self::Ref) -> usize;
|
||||
}
|
||||
|
||||
|
@ -20,21 +17,21 @@ pub(crate) mod test {
|
|||
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)]
|
||||
pub(crate) enum Def {
|
||||
Visible,
|
||||
Invisible,
|
||||
HasSafetyInvariants,
|
||||
NoSafetyInvariants,
|
||||
}
|
||||
|
||||
impl crate::layout::Def for Def {}
|
||||
impl crate::layout::Def for Def {
|
||||
fn has_safety_invariants(&self) -> bool {
|
||||
self == &Self::HasSafetyInvariants
|
||||
}
|
||||
}
|
||||
|
||||
impl QueryContext for UltraMinimal {
|
||||
type Def = Def;
|
||||
type Ref = !;
|
||||
type Scope = ();
|
||||
|
||||
fn is_accessible_from(&self, def: Def, scope: ()) -> bool {
|
||||
matches!(Def::Visible, def)
|
||||
}
|
||||
|
||||
fn min_align(&self, reference: !) -> usize {
|
||||
unimplemented!()
|
||||
}
|
||||
|
@ -52,34 +49,6 @@ mod rustc {
|
|||
|
||||
type Scope = Ty<'tcx>;
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
fn is_accessible_from(&self, def: Self::Def, scope: Self::Scope) -> bool {
|
||||
use layout::rustc::Def;
|
||||
use rustc_middle::ty;
|
||||
|
||||
let parent = if let ty::Adt(adt_def, ..) = scope.kind() {
|
||||
self.parent(adt_def.did())
|
||||
} else {
|
||||
// Is this always how we want to handle a non-ADT scope?
|
||||
return false;
|
||||
};
|
||||
|
||||
let def_id = match def {
|
||||
Def::Adt(adt_def) => adt_def.did(),
|
||||
Def::Variant(variant_def) => variant_def.def_id,
|
||||
Def::Field(field_def) => field_def.did,
|
||||
Def::Primitive => {
|
||||
// primitives do not have a def_id, but they're always accessible
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
let ret: bool = self.visibility(def_id).is_accessible_from(parent, *self);
|
||||
|
||||
trace!(?ret, "ret");
|
||||
ret
|
||||
}
|
||||
|
||||
fn min_align(&self, reference: Self::Ref) -> usize {
|
||||
unimplemented!()
|
||||
}
|
||||
|
|
|
@ -3,6 +3,65 @@ use crate::maybe_transmutable::MaybeTransmutableQuery;
|
|||
use crate::{layout, Reason};
|
||||
use itertools::Itertools;
|
||||
|
||||
mod safety {
|
||||
use crate::Answer;
|
||||
|
||||
use super::*;
|
||||
|
||||
type Tree = layout::Tree<Def, !>;
|
||||
|
||||
const DST_HAS_SAFETY_INVARIANTS: Answer<!> =
|
||||
Answer::No(crate::Reason::DstMayHaveSafetyInvariants);
|
||||
|
||||
fn is_transmutable(src: &Tree, dst: &Tree, assume_safety: bool) -> crate::Answer<!> {
|
||||
let src = src.clone();
|
||||
let dst = dst.clone();
|
||||
// The only dimension of the transmutability analysis we want to test
|
||||
// here is the safety analysis. To ensure this, we disable all other
|
||||
// toggleable aspects of the transmutability analysis.
|
||||
let assume = crate::Assume {
|
||||
alignment: true,
|
||||
lifetimes: true,
|
||||
validity: true,
|
||||
safety: assume_safety,
|
||||
};
|
||||
crate::maybe_transmutable::MaybeTransmutableQuery::new(src, dst, assume, UltraMinimal)
|
||||
.answer()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn src_safe_dst_safe() {
|
||||
let src = Tree::Def(Def::NoSafetyInvariants).then(Tree::u8());
|
||||
let dst = Tree::Def(Def::NoSafetyInvariants).then(Tree::u8());
|
||||
assert_eq!(is_transmutable(&src, &dst, false), Answer::Yes);
|
||||
assert_eq!(is_transmutable(&src, &dst, true), Answer::Yes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn src_safe_dst_unsafe() {
|
||||
let src = Tree::Def(Def::NoSafetyInvariants).then(Tree::u8());
|
||||
let dst = Tree::Def(Def::HasSafetyInvariants).then(Tree::u8());
|
||||
assert_eq!(is_transmutable(&src, &dst, false), DST_HAS_SAFETY_INVARIANTS);
|
||||
assert_eq!(is_transmutable(&src, &dst, true), Answer::Yes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn src_unsafe_dst_safe() {
|
||||
let src = Tree::Def(Def::HasSafetyInvariants).then(Tree::u8());
|
||||
let dst = Tree::Def(Def::NoSafetyInvariants).then(Tree::u8());
|
||||
assert_eq!(is_transmutable(&src, &dst, false), Answer::Yes);
|
||||
assert_eq!(is_transmutable(&src, &dst, true), Answer::Yes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn src_unsafe_dst_unsafe() {
|
||||
let src = Tree::Def(Def::HasSafetyInvariants).then(Tree::u8());
|
||||
let dst = Tree::Def(Def::HasSafetyInvariants).then(Tree::u8());
|
||||
assert_eq!(is_transmutable(&src, &dst, false), DST_HAS_SAFETY_INVARIANTS);
|
||||
assert_eq!(is_transmutable(&src, &dst, true), Answer::Yes);
|
||||
}
|
||||
}
|
||||
|
||||
mod bool {
|
||||
use crate::Answer;
|
||||
|
||||
|
@ -10,11 +69,9 @@ mod bool {
|
|||
|
||||
#[test]
|
||||
fn should_permit_identity_transmutation_tree() {
|
||||
println!("{:?}", layout::Tree::<!, !>::bool());
|
||||
let answer = crate::maybe_transmutable::MaybeTransmutableQuery::new(
|
||||
layout::Tree::<Def, !>::bool(),
|
||||
layout::Tree::<Def, !>::bool(),
|
||||
(),
|
||||
crate::Assume { alignment: false, lifetimes: false, validity: true, safety: false },
|
||||
UltraMinimal,
|
||||
)
|
||||
|
@ -27,7 +84,6 @@ mod bool {
|
|||
let answer = crate::maybe_transmutable::MaybeTransmutableQuery::new(
|
||||
layout::Dfa::<!>::bool(),
|
||||
layout::Dfa::<!>::bool(),
|
||||
(),
|
||||
crate::Assume { alignment: false, lifetimes: false, validity: true, safety: false },
|
||||
UltraMinimal,
|
||||
)
|
||||
|
@ -71,7 +127,6 @@ mod bool {
|
|||
MaybeTransmutableQuery::new(
|
||||
src_layout.clone(),
|
||||
dst_layout.clone(),
|
||||
(),
|
||||
crate::Assume { validity: false, ..crate::Assume::default() },
|
||||
UltraMinimal,
|
||||
)
|
||||
|
@ -86,7 +141,6 @@ mod bool {
|
|||
MaybeTransmutableQuery::new(
|
||||
src_layout.clone(),
|
||||
dst_layout.clone(),
|
||||
(),
|
||||
crate::Assume { validity: true, ..crate::Assume::default() },
|
||||
UltraMinimal,
|
||||
)
|
||||
|
@ -101,7 +155,6 @@ mod bool {
|
|||
MaybeTransmutableQuery::new(
|
||||
src_layout.clone(),
|
||||
dst_layout.clone(),
|
||||
(),
|
||||
crate::Assume { validity: false, ..crate::Assume::default() },
|
||||
UltraMinimal,
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue