Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

safe transmute: use Assume struct to provide analysis options #100726

Merged
merged 3 commits into from
Sep 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4581,6 +4581,7 @@ version = "0.1.0"
dependencies = [
"itertools",
"rustc_data_structures",
"rustc_hir",
"rustc_infer",
"rustc_macros",
"rustc_middle",
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_hir/src/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,8 @@ language_item_table! {
DispatchFromDyn, sym::dispatch_from_dyn, dispatch_from_dyn_trait, Target::Trait, GenericRequirement::Minimum(1);

// language items relating to transmutability
TransmuteTrait, sym::transmute_trait, transmute_trait, Target::Trait, GenericRequirement::Exact(6);
TransmuteOpts, sym::transmute_opts, transmute_opts, Target::Struct, GenericRequirement::Exact(0);
TransmuteTrait, sym::transmute_trait, transmute_trait, Target::Trait, GenericRequirement::Exact(3);

Add(Op), sym::add, add_trait, Target::Trait, GenericRequirement::Exact(1);
Sub(Op), sym::sub, sub_trait, Target::Trait, GenericRequirement::Exact(1);
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ symbols! {
alias,
align,
align_offset,
alignment,
alignstack,
all,
alloc,
Expand Down Expand Up @@ -859,6 +860,7 @@ symbols! {
lib,
libc,
lifetime,
lifetimes,
likely,
line,
line_macro,
Expand Down Expand Up @@ -1284,6 +1286,7 @@ symbols! {
rustfmt,
rvalue_static_promotion,
s,
safety,
sanitize,
sanitizer_runtime,
saturating_add,
Expand Down Expand Up @@ -1466,6 +1469,7 @@ symbols! {
trait_alias,
trait_upcasting,
transmute,
transmute_opts,
transmute_trait,
transparent,
transparent_enums,
Expand Down Expand Up @@ -1560,6 +1564,7 @@ symbols! {
va_list,
va_start,
val,
validity,
values,
var,
variant_count,
Expand Down
20 changes: 4 additions & 16 deletions compiler/rustc_trait_selection/src/traits/select/confirmation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,29 +279,17 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
let predicate = obligation.predicate;

let type_at = |i| predicate.map_bound(|p| p.trait_ref.substs.type_at(i));
let bool_at = |i| {
predicate
.skip_binder()
.trait_ref
.substs
.const_at(i)
.try_eval_bool(self.tcx(), obligation.param_env)
.unwrap_or(true)
};
let const_at = |i| predicate.skip_binder().trait_ref.substs.const_at(i);

let src_and_dst = predicate.map_bound(|p| rustc_transmute::Types {
src: p.trait_ref.substs.type_at(1),
dst: p.trait_ref.substs.type_at(0),
src: p.trait_ref.substs.type_at(1),
});

let scope = type_at(2).skip_binder();

let assume = rustc_transmute::Assume {
alignment: bool_at(3),
lifetimes: bool_at(4),
validity: bool_at(5),
visibility: bool_at(6),
};
let assume =
rustc_transmute::Assume::from_const(self.infcx.tcx, obligation.param_env, const_at(3));

let cause = obligation.cause.clone();

Expand Down
5 changes: 3 additions & 2 deletions compiler/rustc_transmute/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ edition = "2021"

[dependencies]
tracing = "0.1"
rustc_data_structures = { path = "../rustc_data_structures", optional = true}
rustc_data_structures = { path = "../rustc_data_structures"}
rustc_hir = { path = "../rustc_hir", optional = true}
rustc_infer = { path = "../rustc_infer", optional = true}
rustc_macros = { path = "../rustc_macros", optional = true}
rustc_middle = { path = "../rustc_middle", optional = true}
Expand All @@ -17,7 +18,7 @@ rustc_target = { path = "../rustc_target", optional = true}
[features]
rustc = [
"rustc_middle",
"rustc_data_structures",
"rustc_hir",
"rustc_infer",
"rustc_macros",
"rustc_span",
Expand Down
1 change: 0 additions & 1 deletion compiler/rustc_transmute/src/layout/dfa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ where
}

#[instrument(level = "debug")]
#[cfg_attr(feature = "rustc", allow(rustc::potential_query_instability))]
pub(crate) fn from_nfa(nfa: Nfa<R>) -> Self {
let Nfa { transitions: nfa_transitions, start: nfa_start, accepting: nfa_accepting } = nfa;

Expand Down
6 changes: 0 additions & 6 deletions compiler/rustc_transmute/src/layout/nfa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,6 @@ where

let mut transitions: Map<State, Map<Transition<R>, Set<State>>> = self.transitions;

// the iteration order doesn't matter
#[cfg_attr(feature = "rustc", allow(rustc::potential_query_instability))]
for (source, transition) in other.transitions {
let fix_state = |state| if state == other.start { self.accepting } else { state };
let entry = transitions.entry(fix_state(source)).or_default();
Expand All @@ -142,8 +140,6 @@ where

let mut transitions: Map<State, Map<Transition<R>, Set<State>>> = self.transitions.clone();

// the iteration order doesn't matter
#[cfg_attr(feature = "rustc", allow(rustc::potential_query_instability))]
for (&(mut source), transition) in other.transitions.iter() {
// if source is starting state of `other`, replace with starting state of `self`
if source == other.start {
Expand All @@ -152,8 +148,6 @@ where
let entry = transitions.entry(source).or_default();
for (edge, destinations) in transition {
let entry = entry.entry(edge.clone()).or_default();
// the iteration order doesn't matter
#[cfg_attr(feature = "rustc", allow(rustc::potential_query_instability))]
for &(mut destination) in destinations {
// if dest is accepting state of `other`, replace with accepting state of `self`
if destination == other.accepting {
Expand Down
62 changes: 56 additions & 6 deletions compiler/rustc_transmute/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,7 @@
#[macro_use]
extern crate tracing;

#[cfg(feature = "rustc")]
pub(crate) use rustc_data_structures::fx::{FxHashMap as Map, FxHashSet as Set};

#[cfg(not(feature = "rustc"))]
pub(crate) use std::collections::{HashMap as Map, HashSet as Set};
pub(crate) use rustc_data_structures::fx::{FxIndexMap as Map, FxIndexSet as Set};

pub(crate) mod layout;
pub(crate) mod maybe_transmutable;
Expand All @@ -26,8 +22,8 @@ pub(crate) mod maybe_transmutable;
pub struct Assume {
pub alignment: bool,
pub lifetimes: bool,
pub safety: bool,
pub validity: bool,
pub visibility: bool,
}

/// The type encodes answers to the question: "Are these types transmutable?"
Expand Down Expand Up @@ -69,11 +65,17 @@ pub enum Reason {

#[cfg(feature = "rustc")]
mod rustc {
use super::*;

use rustc_hir::lang_items::LangItem;
use rustc_infer::infer::InferCtxt;
use rustc_macros::{TypeFoldable, TypeVisitable};
use rustc_middle::traits::ObligationCause;
use rustc_middle::ty::Binder;
use rustc_middle::ty::Const;
use rustc_middle::ty::ParamEnv;
use rustc_middle::ty::Ty;
use rustc_middle::ty::TyCtxt;

/// The source and destination types of a transmutation.
#[derive(TypeFoldable, TypeVisitable, Debug, Clone, Copy)]
Expand Down Expand Up @@ -113,6 +115,54 @@ mod rustc {
.answer()
}
}

impl Assume {
/// Constructs an `Assume` from a given const-`Assume`.
pub fn from_const<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ParamEnv<'tcx>,
c: Const<'tcx>,
) -> Self {
use rustc_middle::ty::ScalarInt;
use rustc_middle::ty::TypeVisitable;
use rustc_span::symbol::sym;

let c = c.eval(tcx, param_env);

if let Some(err) = c.error_reported() {
return Self { alignment: true, lifetimes: true, safety: true, validity: true };
}

let adt_def = c.ty().ty_adt_def().expect("The given `Const` must be an ADT.");

assert_eq!(
tcx.require_lang_item(LangItem::TransmuteOpts, None),
adt_def.did(),
"The given `Const` was not marked with the `{}` lang item.",
LangItem::TransmuteOpts.name(),
);

let variant = adt_def.non_enum_variant();
let fields = c.to_valtree().unwrap_branch();

let get_field = |name| {
let (field_idx, _) = variant
.fields
.iter()
.enumerate()
.find(|(_, field_def)| name == field_def.name)
.expect(&format!("There were no fields named `{name}`."));
fields[field_idx].unwrap_leaf() == ScalarInt::TRUE
};

Self {
alignment: get_field(sym::alignment),
lifetimes: get_field(sym::lifetimes),
safety: get_field(sym::safety),
validity: get_field(sym::validity),
}
}
}
}

#[cfg(feature = "rustc")]
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_transmute/src/maybe_transmutable/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ 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.visibility;
let assume_visibility = self.assume.safety;
let query_or_answer = self.map_layouts(|src, dst, scope, context| {
// Remove all `Def` nodes from `src`, without checking their visibility.
let src = src.prune(&|def| true);
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_transmute/src/maybe_transmutable/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ mod bool {
layout::Tree::<Def, !>::bool(),
layout::Tree::<Def, !>::bool(),
(),
crate::Assume { alignment: false, lifetimes: false, validity: true, visibility: false },
crate::Assume { alignment: false, lifetimes: false, validity: true, safety: false },
UltraMinimal,
)
.answer();
Expand All @@ -26,7 +26,7 @@ mod bool {
layout::Dfa::<!>::bool(),
layout::Dfa::<!>::bool(),
(),
crate::Assume { alignment: false, lifetimes: false, validity: true, visibility: false },
crate::Assume { alignment: false, lifetimes: false, validity: true, safety: false },
UltraMinimal,
)
.answer();
Expand Down
2 changes: 2 additions & 0 deletions library/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
#![warn(missing_debug_implementations)]
#![warn(missing_docs)]
#![allow(explicit_outlives_requirements)]
#![allow(incomplete_features)]
Copy link
Member

@RalfJung RalfJung Apr 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am quite concerned about this allow. That makes it way too easy to use features such as generic_const_exprs that are just not ready yet for use in the standard library.

This PR should at least have consulted t-types to ensure that adt_const_params is sufficiently stable that it can be used internally in the standard library. Cc @lcnr

But even then I think we actually want forbid(incomplete_features) here to avoid people just adding feature gates when it seems convenient. Or even better, set some flag in bootstrap which ensures that the entire sysroot is built without incomplete features.

//
// Library features:
#![feature(const_align_offset)]
Expand Down Expand Up @@ -160,6 +161,7 @@
//
// Language features:
#![feature(abi_unadjusted)]
#![feature(adt_const_params)]
#![feature(allow_internal_unsafe)]
#![feature(allow_internal_unstable)]
#![feature(associated_type_bounds)]
Expand Down
88 changes: 76 additions & 12 deletions library/core/src/mem/transmutability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,20 @@
/// any value of type `Self` are safely transmutable into a value of type `Dst`, in a given `Context`,
/// notwithstanding whatever safety checks you have asked the compiler to [`Assume`] are satisfied.
#[unstable(feature = "transmutability", issue = "99571")]
#[lang = "transmute_trait"]
#[cfg_attr(not(bootstrap), lang = "transmute_trait")]
#[rustc_on_unimplemented(
message = "`{Src}` cannot be safely transmuted into `{Self}` in the defining scope of `{Context}`.",
label = "`{Src}` cannot be safely transmuted into `{Self}` in the defining scope of `{Context}`."
)]
pub unsafe trait BikeshedIntrinsicFrom<
Src,
Context,
const ASSUME_ALIGNMENT: bool,
const ASSUME_LIFETIMES: bool,
const ASSUME_VALIDITY: bool,
const ASSUME_VISIBILITY: bool,
> where
pub unsafe trait BikeshedIntrinsicFrom<Src, Context, const ASSUME: Assume = { Assume::NOTHING }>
where
Src: ?Sized,
{
}

/// What transmutation safety conditions shall the compiler assume that *you* are checking?
#[unstable(feature = "transmutability", issue = "99571")]
#[cfg_attr(not(bootstrap), lang = "transmute_opts")]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct Assume {
/// When `true`, the compiler assumes that *you* are ensuring (either dynamically or statically) that
Expand All @@ -33,11 +28,80 @@ pub struct Assume {
/// that violates Rust's memory model.
pub lifetimes: bool,

/// When `true`, the compiler assumes that *you* have ensured that it is safe for you to violate the
/// type and field privacy of the destination type (and sometimes of the source type, too).
pub safety: bool,

/// When `true`, the compiler assumes that *you* are ensuring that the source type is actually a valid
/// instance of the destination type.
pub validity: bool,
}

/// When `true`, the compiler assumes that *you* have ensured that it is safe for you to violate the
/// type and field privacy of the destination type (and sometimes of the source type, too).
pub visibility: bool,
impl Assume {
/// Do not assume that *you* have ensured any safety properties are met.
#[unstable(feature = "transmutability", issue = "99571")]
pub const NOTHING: Self =
Self { alignment: false, lifetimes: false, safety: false, validity: false };

/// Assume only that alignment conditions are met.
#[unstable(feature = "transmutability", issue = "99571")]
pub const ALIGNMENT: Self = Self { alignment: true, ..Self::NOTHING };

/// Assume only that lifetime conditions are met.
#[unstable(feature = "transmutability", issue = "99571")]
pub const LIFETIMES: Self = Self { lifetimes: true, ..Self::NOTHING };

/// Assume only that safety conditions are met.
#[unstable(feature = "transmutability", issue = "99571")]
pub const SAFETY: Self = Self { safety: true, ..Self::NOTHING };

/// Assume only that dynamically-satisfiable validity conditions are met.
#[unstable(feature = "transmutability", issue = "99571")]
pub const VALIDITY: Self = Self { validity: true, ..Self::NOTHING };

/// Assume both `self` and `other_assumptions`.
#[unstable(feature = "transmutability", issue = "99571")]
pub const fn and(self, other_assumptions: Self) -> Self {
Self {
alignment: self.alignment || other_assumptions.alignment,
lifetimes: self.lifetimes || other_assumptions.lifetimes,
safety: self.safety || other_assumptions.safety,
validity: self.validity || other_assumptions.validity,
}
}

/// Assume `self`, excepting `other_assumptions`.
#[unstable(feature = "transmutability", issue = "99571")]
pub const fn but_not(self, other_assumptions: Self) -> Self {
Self {
alignment: self.alignment && !other_assumptions.alignment,
lifetimes: self.lifetimes && !other_assumptions.lifetimes,
safety: self.safety && !other_assumptions.safety,
validity: self.validity && !other_assumptions.validity,
}
}
}

// FIXME(jswrenn): This const op is not actually usable. Why?
// https://github.com/rust-lang/rust/pull/100726#issuecomment-1219928926
#[unstable(feature = "transmutability", issue = "99571")]
#[rustc_const_unstable(feature = "transmutability", issue = "99571")]
impl const core::ops::Add for Assume {
type Output = Assume;

fn add(self, other_assumptions: Assume) -> Assume {
self.and(other_assumptions)
}
}

// FIXME(jswrenn): This const op is not actually usable. Why?
// https://github.com/rust-lang/rust/pull/100726#issuecomment-1219928926
#[unstable(feature = "transmutability", issue = "99571")]
#[rustc_const_unstable(feature = "transmutability", issue = "99571")]
impl const core::ops::Sub for Assume {
type Output = Assume;

fn sub(self, other_assumptions: Assume) -> Assume {
self.but_not(other_assumptions)
}
}
Loading