1
Fork 0

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:
Jack Wrenn 2024-02-26 16:49:25 +00:00
parent 9afdb8d1d5
commit 23ab1bda92
127 changed files with 1387 additions and 1948 deletions

View file

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