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 1 commit
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
Next Next commit
safe transmute: use Assume struct to provide analysis options
This was left as a TODO in #92268, and brings the trait more in
line with what was defined in MCP411.

`Assume::visibility` has been renamed to `Assume::safety`, as
library safety is what's actually being assumed; visibility is
just the mechanism by which it is currently checked (this may
change).

ref: rust-lang/compiler-team#411
ref: #99571
  • Loading branch information
jswrenn committed Aug 22, 2022
commit f46fffc276c19b2c81c9b5e84f4f8678fc4d6d0a
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
2 changes: 2 additions & 0 deletions compiler/rustc_transmute/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ edition = "2021"
[dependencies]
tracing = "0.1"
rustc_data_structures = { path = "../rustc_data_structures", optional = true}
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 @@ -18,6 +19,7 @@ rustc_target = { path = "../rustc_target", optional = true}
rustc = [
"rustc_middle",
"rustc_data_structures",
"rustc_hir",
"rustc_infer",
"rustc_macros",
"rustc_span",
Expand Down
59 changes: 58 additions & 1 deletion compiler/rustc_transmute/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,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 +69,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 +119,57 @@ 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::DestructuredConst;
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 DestructuredConst { variant, fields } = tcx.destructure_const(c);
oli-obk marked this conversation as resolved.
Show resolved Hide resolved
let variant_idx = variant.expect("The given `Const` must be an ADT.");
let variant = adt_def.variant(variant_idx);

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].try_eval_bool(tcx, param_env).expect(&format!(
"The field named `{name}` lang item could not be evaluated to a bool."
))
};

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