From c9a581a43fc6569b1507ab65947b0f4c2c13ad57 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Tue, 19 Mar 2024 14:49:13 +0000 Subject: [PATCH] Compute transmutability from `rustc_target::abi::Layout` In its first step of computing transmutability, `rustc_transmutability` constructs a byte-level representation of type layout (`Tree`). Previously, this representation was computed for ADTs by inspecting the ADT definition and performing our own layout computations. This process was error-prone, verbose, and limited our ability to analyze many types (particularly default-repr types). In this PR, we instead construct `Tree`s from `rustc_target::abi::Layout`s. This helps ensure that layout optimizations are reflected our analyses, and increases the kinds of types we can now analyze, including: - default repr ADTs - transparent unions - `UnsafeCell`-containing types Overall, this PR expands the expressvity of `rustc_transmutability` to be much closer to the transmutability analysis performed by miri. Future PRs will work to close the remaining gaps (e.g., support for `Box`, raw pointers, `NonZero*`, coroutines, etc.). --- .../src/traits/error_reporting/mod.rs | 2 +- .../error_reporting/type_err_ctxt_ext.rs | 26 +- .../src/traits/select/confirmation.rs | 42 +- compiler/rustc_transmute/src/layout/dfa.rs | 2 + compiler/rustc_transmute/src/layout/nfa.rs | 1 + compiler/rustc_transmute/src/layout/tree.rs | 441 ++++++++---------- compiler/rustc_transmute/src/lib.rs | 4 +- .../src/maybe_transmutable/mod.rs | 19 +- .../src/maybe_transmutable/query_context.rs | 10 - .../ui/transmutability/arrays/huge-len.stderr | 4 +- .../enums/niche_optimization.rs | 156 +++++++ .../enums/repr/padding_differences.rs | 80 ++++ ...defined_layout.rs => should_handle_all.rs} | 21 +- .../should_require_well_defined_layout.stderr | 135 ------ .../unknown_src_field.stderr | 2 +- tests/ui/transmutability/maybeuninit.rs | 43 ++ tests/ui/transmutability/maybeuninit.stderr | 18 + .../transmutability/references/unsafecell.rs | 47 ++ .../references/unsafecell.stderr | 33 ++ ...defined_layout.rs => should_handle_all.rs} | 29 +- .../should_require_well_defined_layout.stderr | 267 ----------- .../transmute_infinitely_recursive_type.rs | 1 + ...transmute_infinitely_recursive_type.stderr | 19 +- .../transmutability/transmute-padding-ice.rs | 9 +- .../transmute-padding-ice.stderr | 22 - tests/ui/transmutability/uninhabited.rs | 74 +++ tests/ui/transmutability/uninhabited.stderr | 181 +++++++ .../unions/repr/should_handle_align.rs | 4 +- ...defined_layout.rs => should_handle_all.rs} | 24 +- .../unions/repr/should_handle_packed.rs | 1 - .../should_require_well_defined_layout.stderr | 47 -- 31 files changed, 989 insertions(+), 775 deletions(-) create mode 100644 tests/ui/transmutability/enums/niche_optimization.rs create mode 100644 tests/ui/transmutability/enums/repr/padding_differences.rs rename tests/ui/transmutability/enums/repr/{should_require_well_defined_layout.rs => should_handle_all.rs} (80%) delete mode 100644 tests/ui/transmutability/enums/repr/should_require_well_defined_layout.stderr create mode 100644 tests/ui/transmutability/maybeuninit.rs create mode 100644 tests/ui/transmutability/maybeuninit.stderr create mode 100644 tests/ui/transmutability/references/unsafecell.rs create mode 100644 tests/ui/transmutability/references/unsafecell.stderr rename tests/ui/transmutability/structs/repr/{should_require_well_defined_layout.rs => should_handle_all.rs} (54%) delete mode 100644 tests/ui/transmutability/structs/repr/should_require_well_defined_layout.stderr delete mode 100644 tests/ui/transmutability/transmute-padding-ice.stderr create mode 100644 tests/ui/transmutability/uninhabited.rs create mode 100644 tests/ui/transmutability/uninhabited.stderr rename tests/ui/transmutability/unions/repr/{should_require_well_defined_layout.rs => should_handle_all.rs} (58%) delete mode 100644 tests/ui/transmutability/unions/repr/should_require_well_defined_layout.stderr diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs index 0515b09ae46a0..cd3383dbfc295 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs @@ -40,7 +40,7 @@ pub struct ImplCandidate<'tcx> { enum GetSafeTransmuteErrorAndReason { Silent, - Error { err_msg: String, safe_transmute_explanation: String }, + Error { err_msg: String, safe_transmute_explanation: Option }, } struct UnsatisfiedConst(pub bool); diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs index 01f9c8bb5d1bb..859f2a1a3f4e0 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs @@ -557,7 +557,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { GetSafeTransmuteErrorAndReason::Error { err_msg, safe_transmute_explanation, - } => (err_msg, Some(safe_transmute_explanation)), + } => (err_msg, safe_transmute_explanation), } } else { (err_msg, None) @@ -3061,15 +3061,16 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { return GetSafeTransmuteErrorAndReason::Silent; }; + let dst = trait_ref.args.type_at(0); + let src = trait_ref.args.type_at(1); + let err_msg = format!("`{src}` cannot be safely transmuted into `{dst}`"); + match rustc_transmute::TransmuteTypeEnv::new(self.infcx).is_transmutable( obligation.cause, src_and_dst, assume, ) { Answer::No(reason) => { - let dst = trait_ref.args.type_at(0); - let src = trait_ref.args.type_at(1); - let err_msg = format!("`{src}` cannot be safely transmuted into `{dst}`"); let safe_transmute_explanation = match reason { rustc_transmute::Reason::SrcIsNotYetSupported => { format!("analyzing the transmutability of `{src}` is not yet supported.") @@ -3083,6 +3084,10 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { 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") } @@ -3128,14 +3133,23 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { format!("`{dst}` has an unknown layout") } }; - GetSafeTransmuteErrorAndReason::Error { err_msg, safe_transmute_explanation } + GetSafeTransmuteErrorAndReason::Error { + err_msg, + safe_transmute_explanation: Some(safe_transmute_explanation), + } } // Should never get a Yes at this point! We already ran it before, and did not get a Yes. Answer::Yes => span_bug!( span, "Inconsistent rustc_transmute::is_transmutable(...) result, got Yes", ), - other => span_bug!(span, "Unsupported rustc_transmute::Answer variant: `{other:?}`"), + // Reached when a different obligation (namely `Freeze`) causes the + // transmutability analysis to fail. In this case, silence the + // transmutability error message in favor of that more specific + // error. + Answer::If(_) => { + GetSafeTransmuteErrorAndReason::Error { err_msg, safe_transmute_explanation: None } + } } } diff --git a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs index 6f512a1173f5d..a7cfcf6b366e2 100644 --- a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs +++ b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs @@ -314,12 +314,12 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { .flat_map(|cond| flatten_answer_tree(tcx, obligation, predicate, cond)) .collect(), Condition::IfTransmutable { src, dst } => { - let trait_def_id = obligation.predicate.def_id(); + let transmute_trait = obligation.predicate.def_id(); let assume_const = predicate.trait_ref.args.const_at(2); - let make_obl = |from_ty, to_ty| { - let trait_ref1 = ty::TraitRef::new( + let make_transmute_obl = |from_ty, to_ty| { + let trait_ref = ty::TraitRef::new( tcx, - trait_def_id, + transmute_trait, [ ty::GenericArg::from(to_ty), ty::GenericArg::from(from_ty), @@ -331,17 +331,45 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { obligation.cause.clone(), obligation.recursion_depth + 1, obligation.param_env, - trait_ref1, + trait_ref, ) }; + let make_freeze_obl = |ty| { + let trait_ref = ty::TraitRef::new( + tcx, + tcx.lang_items().freeze_trait().unwrap(), + [ty::GenericArg::from(ty)], + ); + Obligation::with_depth( + tcx, + obligation.cause.clone(), + obligation.recursion_depth + 1, + obligation.param_env, + trait_ref, + ) + }; + + let mut obls = vec![]; + + // If the source is a shared reference, it must be `Freeze`; + // otherwise, transmuting could lead to data races. + if src.mutability == Mutability::Not { + obls.extend([make_freeze_obl(src.ty), make_freeze_obl(dst.ty)]) + } + // If Dst is mutable, check bidirectionally. // 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 UB. match dst.mutability { - Mutability::Not => vec![make_obl(src.ty, dst.ty)], - Mutability::Mut => vec![make_obl(src.ty, dst.ty), make_obl(dst.ty, src.ty)], + Mutability::Not => obls.push(make_transmute_obl(src.ty, dst.ty)), + Mutability::Mut => obls.extend([ + make_transmute_obl(src.ty, dst.ty), + make_transmute_obl(dst.ty, src.ty), + ]), } + + obls } } } diff --git a/compiler/rustc_transmute/src/layout/dfa.rs b/compiler/rustc_transmute/src/layout/dfa.rs index b8922696e3067..77d5a48f15847 100644 --- a/compiler/rustc_transmute/src/layout/dfa.rs +++ b/compiler/rustc_transmute/src/layout/dfa.rs @@ -35,6 +35,7 @@ impl Transitions where R: Ref, { + #[allow(dead_code)] fn insert(&mut self, transition: Transition, state: State) { match transition { Transition::Byte(b) => { @@ -82,6 +83,7 @@ impl Dfa where R: Ref, { + #[allow(dead_code)] pub(crate) fn unit() -> Self { let transitions: Map> = Map::default(); let start = State::new(); diff --git a/compiler/rustc_transmute/src/layout/nfa.rs b/compiler/rustc_transmute/src/layout/nfa.rs index 78fcceb5f2cb5..3c5963202c638 100644 --- a/compiler/rustc_transmute/src/layout/nfa.rs +++ b/compiler/rustc_transmute/src/layout/nfa.rs @@ -160,6 +160,7 @@ where Self { transitions, start, accepting } } + #[allow(dead_code)] pub(crate) fn edges_from(&self, start: State) -> Option<&Map, Set>> { self.transitions.get(&start) } diff --git a/compiler/rustc_transmute/src/layout/tree.rs b/compiler/rustc_transmute/src/layout/tree.rs index f6bc224c7e7f3..42fad91c3c071 100644 --- a/compiler/rustc_transmute/src/layout/tree.rs +++ b/compiler/rustc_transmute/src/layout/tree.rs @@ -173,16 +173,20 @@ pub(crate) mod rustc { use super::Tree; use crate::layout::rustc::{Def, Ref}; + use rustc_middle::ty::layout::HasTyCtxt; + use rustc_middle::ty::layout::LayoutCx; use rustc_middle::ty::layout::LayoutError; + use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::AdtDef; - use rustc_middle::ty::GenericArgsRef; - use rustc_middle::ty::ParamEnv; + use rustc_middle::ty::AdtKind; + use rustc_middle::ty::List; use rustc_middle::ty::ScalarInt; - use rustc_middle::ty::VariantDef; use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; use rustc_span::ErrorGuaranteed; - use rustc_target::abi::Align; - use std::alloc; + use rustc_target::abi::FieldsShape; + use rustc_target::abi::Size; + use rustc_target::abi::TyAndLayout; + use rustc_target::abi::Variants; #[derive(Debug, Copy, Clone)] pub(crate) enum Err { @@ -206,176 +210,64 @@ pub(crate) mod rustc { } } - trait LayoutExt { - fn clamp_align(&self, min_align: Align, max_align: Align) -> Self; - } - - impl LayoutExt for alloc::Layout { - fn clamp_align(&self, min_align: Align, max_align: Align) -> Self { - let min_align = min_align.bytes().try_into().unwrap(); - let max_align = max_align.bytes().try_into().unwrap(); - Self::from_size_align(self.size(), self.align().clamp(min_align, max_align)).unwrap() - } - } - - struct LayoutSummary { - total_align: Align, - total_size: usize, - discriminant_size: usize, - discriminant_align: Align, - } - - impl LayoutSummary { - fn from_ty<'tcx>(ty: Ty<'tcx>, ctx: TyCtxt<'tcx>) -> Result> { - use rustc_middle::ty::ParamEnvAnd; - use rustc_target::abi::{TyAndLayout, Variants}; - - let param_env = ParamEnv::reveal_all(); - let param_env_and_type = ParamEnvAnd { param_env, value: ty }; - let TyAndLayout { layout, .. } = ctx.layout_of(param_env_and_type)?; - - let total_size: usize = layout.size().bytes_usize(); - let total_align: Align = layout.align().abi; - let discriminant_align: Align; - let discriminant_size: usize; - - if let Variants::Multiple { tag, .. } = layout.variants() { - discriminant_align = tag.align(&ctx).abi; - discriminant_size = tag.size(&ctx).bytes_usize(); - } else { - discriminant_align = Align::ONE; - discriminant_size = 0; - }; - - Ok(Self { total_align, total_size, discriminant_align, discriminant_size }) - } - - fn into(&self) -> alloc::Layout { - alloc::Layout::from_size_align( - self.total_size, - self.total_align.bytes().try_into().unwrap(), - ) - .unwrap() - } - } - impl<'tcx> Tree, Ref<'tcx>> { - pub fn from_ty(ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> Result { - use rustc_middle::ty::FloatTy::*; - use rustc_middle::ty::IntTy::*; - use rustc_middle::ty::UintTy::*; + pub fn from_ty( + ty_and_layout: TyAndLayout<'tcx, Ty<'tcx>>, + cx: LayoutCx<'tcx, TyCtxt<'tcx>>, + ) -> Result { use rustc_target::abi::HasDataLayout; - if let Err(e) = ty.error_reported() { + if let Err(e) = ty_and_layout.ty.error_reported() { return Err(Err::TypeError(e)); } - let target = tcx.data_layout(); + let target = cx.tcx.data_layout(); + let pointer_size = target.pointer_size; - match ty.kind() { + match ty_and_layout.ty.kind() { ty::Bool => Ok(Self::bool()), - ty::Int(I8) | ty::Uint(U8) => Ok(Self::u8()), - ty::Int(I16) | ty::Uint(U16) => Ok(Self::number(2)), - ty::Int(I32) | ty::Uint(U32) | ty::Float(F32) => Ok(Self::number(4)), - ty::Int(I64) | ty::Uint(U64) | ty::Float(F64) => Ok(Self::number(8)), - ty::Int(I128) | ty::Uint(U128) => Ok(Self::number(16)), - ty::Int(Isize) | ty::Uint(Usize) => { - Ok(Self::number(target.pointer_size.bytes_usize())) + ty::Float(nty) => { + let width = nty.bit_width() / 8; + Ok(Self::number(width as _)) } - ty::Tuple(members) => { - if members.len() == 0 { - Ok(Tree::unit()) - } else { - Err(Err::NotYetSupported) - } + ty::Int(nty) => { + let width = nty.normalize(pointer_size.bits() as _).bit_width().unwrap() / 8; + Ok(Self::number(width as _)) } - ty::Array(ty, len) => { - let len = len - .try_eval_target_usize(tcx, ParamEnv::reveal_all()) - .ok_or(Err::NotYetSupported)?; - let elt = Tree::from_ty(*ty, tcx)?; - Ok(std::iter::repeat(elt) - .take(len as usize) - .fold(Tree::unit(), |tree, elt| tree.then(elt))) + ty::Uint(nty) => { + let width = nty.normalize(pointer_size.bits() as _).bit_width().unwrap() / 8; + Ok(Self::number(width as _)) } - ty::Adt(adt_def, args_ref) => { - use rustc_middle::ty::AdtKind; + ty::Tuple(members) => Self::from_tuple(ty_and_layout, members, cx), - // If the layout is ill-specified, halt. - if !(adt_def.repr().c() || adt_def.repr().int.is_some()) { + ty::Array(inner_ty, len) => { + let FieldsShape::Array { stride, count } = &ty_and_layout.fields else { return Err(Err::NotYetSupported); - } + }; + let inner_ty_and_layout = cx.layout_of(*inner_ty)?; + assert_eq!(*stride, inner_ty_and_layout.size); + let elt = Tree::from_ty(inner_ty_and_layout, cx)?; + Ok(std::iter::repeat(elt) + .take(*count as usize) + .fold(Tree::unit(), |tree, elt| tree.then(elt))) + } - // Compute a summary of the type's layout. - let layout_summary = LayoutSummary::from_ty(ty, tcx)?; - - // The layout begins with this adt's visibility. - let vis = Self::def(Def::Adt(*adt_def)); - - // And is followed the layout(s) of its variants - Ok(vis.then(match adt_def.adt_kind() { - AdtKind::Struct => Self::from_repr_c_variant( - ty, - *adt_def, - args_ref, - &layout_summary, - None, - adt_def.non_enum_variant(), - tcx, - )?, - AdtKind::Enum => { - trace!(?adt_def, "treeifying enum"); - let mut tree = Tree::uninhabited(); - - for (idx, variant) in adt_def.variants().iter_enumerated() { - let tag = tcx.tag_for_variant((ty, idx)); - tree = tree.or(Self::from_repr_c_variant( - ty, - *adt_def, - args_ref, - &layout_summary, - tag, - variant, - tcx, - )?); - } - - tree - } - AdtKind::Union => { - // is the layout well-defined? - if !adt_def.repr().c() { - return Err(Err::NotYetSupported); - } - - let ty_layout = layout_of(tcx, ty)?; - - let mut tree = Tree::uninhabited(); - - for field in adt_def.all_fields() { - let variant_ty = field.ty(tcx, args_ref); - let variant_layout = layout_of(tcx, variant_ty)?; - let padding_needed = ty_layout.size() - variant_layout.size(); - let variant = Self::def(Def::Field(field)) - .then(Self::from_ty(variant_ty, tcx)?) - .then(Self::padding(padding_needed)); - - tree = tree.or(variant); - } - - tree - } - })) + ty::Adt(adt_def, _args_ref) if !ty_and_layout.ty.is_box() => { + match adt_def.adt_kind() { + AdtKind::Struct => Self::from_struct(ty_and_layout, *adt_def, cx), + AdtKind::Enum => Self::from_enum(ty_and_layout, *adt_def, cx), + AdtKind::Union => Self::from_union(ty_and_layout, *adt_def, cx), + } } ty::Ref(lifetime, ty, mutability) => { - let layout = layout_of(tcx, *ty)?; - let align = layout.align(); - let size = layout.size(); + let ty_and_layout = cx.layout_of(*ty)?; + let align = ty_and_layout.align.abi.bytes() as _; + let size = ty_and_layout.size.bytes_usize(); Ok(Tree::Ref(Ref { lifetime: *lifetime, ty: *ty, @@ -389,80 +281,143 @@ pub(crate) mod rustc { } } - fn from_repr_c_variant( - ty: Ty<'tcx>, - adt_def: AdtDef<'tcx>, - args_ref: GenericArgsRef<'tcx>, - layout_summary: &LayoutSummary, - tag: Option, - variant_def: &'tcx VariantDef, - tcx: TyCtxt<'tcx>, + /// Constructs a `Tree` from a tuple. + fn from_tuple( + ty_and_layout: TyAndLayout<'tcx, Ty<'tcx>>, + members: &'tcx List>, + cx: LayoutCx<'tcx, TyCtxt<'tcx>>, ) -> Result { - let mut tree = Tree::unit(); - - let repr = adt_def.repr(); - let min_align = repr.align.unwrap_or(Align::ONE); - let max_align = repr.pack.unwrap_or(Align::MAX); - - let variant_span = trace_span!( - "treeifying variant", - min_align = ?min_align, - max_align = ?max_align, - ) - .entered(); - - let mut variant_layout = alloc::Layout::from_size_align( - 0, - layout_summary.total_align.bytes().try_into().unwrap(), - ) - .unwrap(); - - // The layout of the variant is prefixed by the tag, if any. - if let Some(tag) = tag { - let tag_layout = - alloc::Layout::from_size_align(tag.size().bytes_usize(), 1).unwrap(); - tree = tree.then(Self::from_tag(tag, tcx)); - variant_layout = variant_layout.extend(tag_layout).unwrap().0; + match &ty_and_layout.fields { + FieldsShape::Primitive => { + assert_eq!(members.len(), 1); + let inner_ty = members[0]; + let inner_ty_and_layout = cx.layout_of(inner_ty)?; + assert_eq!(ty_and_layout.layout, inner_ty_and_layout.layout); + Self::from_ty(inner_ty_and_layout, cx) + } + FieldsShape::Arbitrary { offsets, .. } => { + assert_eq!(offsets.len(), members.len()); + Self::from_variant(Def::Primitive, None, ty_and_layout, ty_and_layout.size, cx) + } + FieldsShape::Array { .. } | FieldsShape::Union(_) => Err(Err::NotYetSupported), + } + } + + /// Constructs a `Tree` from a struct. + /// + /// # Panics + /// + /// Panics if `def` is not a struct definition. + fn from_struct( + ty_and_layout: TyAndLayout<'tcx, Ty<'tcx>>, + def: AdtDef<'tcx>, + cx: LayoutCx<'tcx, TyCtxt<'tcx>>, + ) -> Result { + assert!(def.is_struct()); + let def = Def::Adt(def); + Self::from_variant(def, None, ty_and_layout, ty_and_layout.size, cx) + } + + /// Constructs a `Tree` from an enum. + /// + /// # Panics + /// + /// Panics if `def` is not an enum definition. + fn from_enum( + ty_and_layout: TyAndLayout<'tcx, Ty<'tcx>>, + def: AdtDef<'tcx>, + cx: LayoutCx<'tcx, TyCtxt<'tcx>>, + ) -> Result { + assert!(def.is_enum()); + let layout = ty_and_layout.layout; + + if let Variants::Multiple { tag_field, .. } = layout.variants() { + // For enums (but not coroutines), the tag field is + // currently always the first field of the layout. + assert_eq!(*tag_field, 0); } - // Next come fields. - let fields_span = trace_span!("treeifying fields").entered(); - for field_def in variant_def.fields.iter() { - let field_ty = field_def.ty(tcx, args_ref); - let _span = trace_span!("treeifying field", field = ?field_ty).entered(); + let variants = def.discriminants(cx.tcx()).try_fold( + Self::uninhabited(), + |variants, (idx, ref discriminant)| { + let tag = cx.tcx.tag_for_variant((ty_and_layout.ty, idx)); + let variant_def = Def::Variant(def.variant(idx)); + let variant_ty_and_layout = ty_and_layout.for_variant(&cx, idx); + let variant = Self::from_variant( + variant_def, + tag, + variant_ty_and_layout, + layout.size, + cx, + )?; + Result::::Ok(variants.or(variant)) + }, + )?; - // begin with the field's visibility - tree = tree.then(Self::def(Def::Field(field_def))); + return Ok(Self::def(Def::Adt(def)).then(variants)); + } - // compute the field's layout characteristics - let field_layout = layout_of(tcx, field_ty)?.clamp_align(min_align, max_align); + /// Constructs a `Tree` from a 'variant-like' layout. + /// + /// A 'variant-like' layout includes those of structs and, of course, + /// enum variants. Pragmatically speaking, this method supports anything + /// with `FieldsShape::Arbitrary`. + /// + /// Note: This routine assumes that the optional `tag` is the first + /// field, and enum callers should check that `tag_field` is, in fact, + /// `0`. + fn from_variant( + def: Def<'tcx>, + tag: Option, + ty_and_layout: TyAndLayout<'tcx, Ty<'tcx>>, + total_size: Size, + cx: LayoutCx<'tcx, TyCtxt<'tcx>>, + ) -> Result { + // This constructor does not support non-`FieldsShape::Arbitrary` + // layouts. + let FieldsShape::Arbitrary { offsets, memory_index } = ty_and_layout.layout.fields() + else { + return Err(Err::NotYetSupported); + }; - // next comes the field's padding - let padding_needed = variant_layout.padding_needed_for(field_layout.align()); - if padding_needed > 0 { - tree = tree.then(Self::padding(padding_needed)); - } + // When this function is invoked with enum variants, + // `ty_and_layout.size` does not encompass the entire size of the + // enum. We rely on `total_size` for this. + assert!(ty_and_layout.size <= total_size); - // finally, the field's layout - tree = tree.then(Self::from_ty(field_ty, tcx)?); + let mut size = Size::ZERO; + let mut struct_tree = Self::def(def); - // extend the variant layout with the field layout - variant_layout = variant_layout.extend(field_layout).unwrap().0; + // If a `tag` is provided, place it at the start of the layout. + if let Some(tag) = tag { + size += tag.size(); + struct_tree = struct_tree.then(Self::from_tag(tag, cx.tcx)); } - drop(fields_span); - // finally: padding - let padding_span = trace_span!("adding trailing padding").entered(); - if layout_summary.total_size > variant_layout.size() { - let padding_needed = layout_summary.total_size - variant_layout.size(); - tree = tree.then(Self::padding(padding_needed)); - }; - drop(padding_span); - drop(variant_span); - Ok(tree) + // Append the fields, in memory order, to the layout. + let inverse_memory_index = memory_index.invert_bijective_mapping(); + for (memory_idx, field_idx) in inverse_memory_index.iter_enumerated() { + // Add interfield padding. + let padding_needed = offsets[*field_idx] - size; + let padding = Self::padding(padding_needed.bytes_usize()); + + let field_ty_and_layout = ty_and_layout.field(&cx, field_idx.as_usize()); + let field_tree = Self::from_ty(field_ty_and_layout, cx)?; + + struct_tree = struct_tree.then(padding).then(field_tree); + + size += padding_needed + field_ty_and_layout.size; + } + + // Add trailing padding. + let padding_needed = total_size - size; + let trailing_padding = Self::padding(padding_needed.bytes_usize()); + + Ok(struct_tree.then(trailing_padding)) } - pub fn from_tag(tag: ScalarInt, tcx: TyCtxt<'tcx>) -> Self { + /// Constructs a `Tree` representing the value of a enum tag. + fn from_tag(tag: ScalarInt, tcx: TyCtxt<'tcx>) -> Self { use rustc_target::abi::Endian; let size = tag.size(); let bits = tag.to_bits(size).unwrap(); @@ -479,24 +434,42 @@ pub(crate) mod rustc { }; Self::Seq(bytes.iter().map(|&b| Self::from_bits(b)).collect()) } - } - fn layout_of<'tcx>( - ctx: TyCtxt<'tcx>, - ty: Ty<'tcx>, - ) -> Result> { - use rustc_middle::ty::ParamEnvAnd; - use rustc_target::abi::TyAndLayout; - - let param_env = ParamEnv::reveal_all(); - let param_env_and_type = ParamEnvAnd { param_env, value: ty }; - let TyAndLayout { layout, .. } = ctx.layout_of(param_env_and_type)?; - let layout = alloc::Layout::from_size_align( - layout.size().bytes_usize(), - layout.align().abi.bytes().try_into().unwrap(), - ) - .unwrap(); - trace!(?ty, ?layout, "computed layout for type"); - Ok(layout) + /// Constructs a `Tree` from a union. + /// + /// # Panics + /// + /// Panics if `def` is not a union definition. + fn from_union( + ty_and_layout: TyAndLayout<'tcx, Ty<'tcx>>, + def: AdtDef<'tcx>, + cx: LayoutCx<'tcx, TyCtxt<'tcx>>, + ) -> Result { + assert!(def.is_union()); + + let union_layout = ty_and_layout.layout; + + // This constructor does not support non-`FieldsShape::Union` + // layouts. Fields of this shape are all placed at offset 0. + let FieldsShape::Union(fields) = union_layout.fields() else { + return Err(Err::NotYetSupported); + }; + + let fields = &def.non_enum_variant().fields; + let fields = fields.iter_enumerated().try_fold( + Self::uninhabited(), + |fields, (idx, ref field_def)| { + let field_def = Def::Field(field_def); + let field_ty_and_layout = ty_and_layout.field(&cx, idx.as_usize()); + let field = Self::from_ty(field_ty_and_layout, cx)?; + let trailing_padding_needed = union_layout.size - field_ty_and_layout.size; + let trailing_padding = Self::padding(trailing_padding_needed.bytes_usize()); + let field_and_padding = field.then(trailing_padding); + Result::::Ok(fields.or(field_and_padding)) + }, + )?; + + Ok(Self::def(Def::Adt(def)).then(fields)) + } } } diff --git a/compiler/rustc_transmute/src/lib.rs b/compiler/rustc_transmute/src/lib.rs index b6f937ebe472b..12312271646e2 100644 --- a/compiler/rustc_transmute/src/lib.rs +++ b/compiler/rustc_transmute/src/lib.rs @@ -1,6 +1,6 @@ #![feature(alloc_layout_extra)] #![feature(never_type)] -#![allow(dead_code, unused_variables)] +#![allow(unused_variables)] #[macro_use] extern crate tracing; @@ -49,6 +49,8 @@ pub enum Reason { DstIsNotYetSupported, /// The layout of the destination type is bit-incompatible with the source type. DstIsBitIncompatible, + /// The destination type is uninhabited. + DstUninhabited, /// The destination type may carry safety invariants. DstMayHaveSafetyInvariants, /// `Dst` is larger than `Src`, and the excess bytes were not exclusively uninitialized. diff --git a/compiler/rustc_transmute/src/maybe_transmutable/mod.rs b/compiler/rustc_transmute/src/maybe_transmutable/mod.rs index 16d15580a05b8..2789fe8f6b1db 100644 --- a/compiler/rustc_transmute/src/maybe_transmutable/mod.rs +++ b/compiler/rustc_transmute/src/maybe_transmutable/mod.rs @@ -33,6 +33,9 @@ mod rustc { use super::*; use crate::layout::tree::rustc::Err; + use rustc_middle::ty::layout::LayoutCx; + use rustc_middle::ty::layout::LayoutOf; + use rustc_middle::ty::ParamEnv; use rustc_middle::ty::Ty; use rustc_middle::ty::TyCtxt; @@ -43,12 +46,20 @@ mod rustc { pub fn answer(self) -> Answer< as QueryContext>::Ref> { let Self { src, dst, assume, context } = self; + let layout_cx = LayoutCx { tcx: context, param_env: ParamEnv::reveal_all() }; + let layout_of = |ty| { + layout_cx + .layout_of(ty) + .map_err(|_| Err::NotYetSupported) + .and_then(|tl| Tree::from_ty(tl, layout_cx)) + }; + // Convert `src` and `dst` from their rustc representations, to `Tree`-based // representations. If these conversions fail, conclude that the transmutation is // unacceptable; the layouts of both the source and destination types must be // well-defined. - let src = Tree::from_ty(src, context); - let dst = Tree::from_ty(dst, context); + let src = layout_of(src); + let dst = layout_of(dst); match (src, dst) { (Err(Err::TypeError(_)), _) | (_, Err(Err::TypeError(_))) => { @@ -86,6 +97,10 @@ where // references. let src = src.prune(&|def| false); + if src.is_inhabited() && !dst.is_inhabited() { + return Answer::No(Reason::DstUninhabited); + } + trace!(?src, "pruned src"); // Remove all `Def` nodes from `dst`, additionally... diff --git a/compiler/rustc_transmute/src/maybe_transmutable/query_context.rs b/compiler/rustc_transmute/src/maybe_transmutable/query_context.rs index 54ed03d44e639..1ccb6f36c8ef5 100644 --- a/compiler/rustc_transmute/src/maybe_transmutable/query_context.rs +++ b/compiler/rustc_transmute/src/maybe_transmutable/query_context.rs @@ -5,8 +5,6 @@ pub(crate) trait QueryContext { type Def: layout::Def; type Ref: layout::Ref; type Scope: Copy; - - fn min_align(&self, reference: Self::Ref) -> usize; } #[cfg(test)] @@ -31,10 +29,6 @@ pub(crate) mod test { type Def = Def; type Ref = !; type Scope = (); - - fn min_align(&self, reference: !) -> usize { - unimplemented!() - } } } @@ -48,9 +42,5 @@ mod rustc { type Ref = layout::rustc::Ref<'tcx>; type Scope = Ty<'tcx>; - - fn min_align(&self, reference: Self::Ref) -> usize { - unimplemented!() - } } } diff --git a/tests/ui/transmutability/arrays/huge-len.stderr b/tests/ui/transmutability/arrays/huge-len.stderr index 37160c5c9596b..7124ae2618a09 100644 --- a/tests/ui/transmutability/arrays/huge-len.stderr +++ b/tests/ui/transmutability/arrays/huge-len.stderr @@ -2,7 +2,7 @@ error[E0277]: `()` cannot be safely transmuted into `ExplicitlyPadded` --> $DIR/huge-len.rs:21:41 | LL | assert::is_maybe_transmutable::<(), ExplicitlyPadded>(); - | ^^^^^^^^^^^^^^^^ values of the type `ExplicitlyPadded` are too big for the current architecture + | ^^^^^^^^^^^^^^^^ analyzing the transmutability of `ExplicitlyPadded` is not yet supported. | note: required by a bound in `is_maybe_transmutable` --> $DIR/huge-len.rs:8:14 @@ -17,7 +17,7 @@ error[E0277]: `ExplicitlyPadded` cannot be safely transmuted into `()` --> $DIR/huge-len.rs:24:55 | LL | assert::is_maybe_transmutable::(); - | ^^ values of the type `ExplicitlyPadded` are too big for the current architecture + | ^^ analyzing the transmutability of `ExplicitlyPadded` is not yet supported. | note: required by a bound in `is_maybe_transmutable` --> $DIR/huge-len.rs:8:14 diff --git a/tests/ui/transmutability/enums/niche_optimization.rs b/tests/ui/transmutability/enums/niche_optimization.rs new file mode 100644 index 0000000000000..23f57ecad75f1 --- /dev/null +++ b/tests/ui/transmutability/enums/niche_optimization.rs @@ -0,0 +1,156 @@ +//@ check-pass +//! Checks that niche optimizations are encoded correctly. +#![crate_type = "lib"] +#![feature(transmutability)] +#![allow(dead_code, incomplete_features, non_camel_case_types)] + +mod assert { + use std::mem::{Assume, BikeshedIntrinsicFrom}; + + pub fn is_transmutable() + where + Dst: BikeshedIntrinsicFrom + {} + + pub fn is_maybe_transmutable() + where + Dst: BikeshedIntrinsicFrom + {} +} + +#[repr(u8)] enum V0 { V = 0 } +#[repr(u8)] enum V1 { V = 1 } +#[repr(u8)] enum V2 { V = 2 } +#[repr(u8)] enum V253 { V = 253 } +#[repr(u8)] enum V254 { V = 254 } +#[repr(u8)] enum V255 { V = 255 } + +fn bool() { + enum OptionLike { + A(bool), + B, + } + + const _: () = { + assert!(std::mem::size_of::() == 1); + }; + + assert::is_transmutable::(); + + assert::is_transmutable::(); + assert::is_transmutable::(); + assert::is_transmutable::(); + assert::is_transmutable::(); +} + +fn one_niche() { + #[repr(u8)] + enum N1 { + S = 0, + E = 255 - 1, + } + + enum OptionLike { + A(N1), + B, + } + + const _: () = { + assert!(std::mem::size_of::() == 1); + }; + + assert::is_transmutable::(); + assert::is_transmutable::(); + assert::is_transmutable::(); + assert::is_transmutable::(); +} + +fn one_niche_alt() { + #[repr(u8)] + enum N1 { + S = 1, + E = 255 - 1, + } + + enum OptionLike { + A(N1), + B, + C, + } + + const _: () = { + assert!(std::mem::size_of::() == 1); + }; + + assert::is_transmutable::(); + assert::is_transmutable::(); + assert::is_transmutable::(); + assert::is_transmutable::(); +} + +fn two_niche() { + #[repr(u8)] + enum Niche { + S = 0, + E = 255 - 2, + } + + enum OptionLike { + A(Niche), + B, + C, + } + + const _: () = { + assert!(std::mem::size_of::() == 1); + }; + + assert::is_transmutable::(); + assert::is_transmutable::(); + assert::is_transmutable::(); + assert::is_transmutable::(); + assert::is_transmutable::(); +} + +fn no_niche() { + use std::mem::MaybeUninit; + + #[repr(u8)] + enum Niche { + S = 0, + E = 255 - 1, + } + + enum OptionLike { + A(Niche), + B, + C, + } + + const _: () = { + assert!(std::mem::size_of::() == 2); + }; + + #[repr(C)] + struct Pair(T, U); + + assert::is_transmutable::(); + assert::is_transmutable::(); + assert::is_transmutable::, OptionLike>(); + assert::is_transmutable::>, OptionLike>(); + assert::is_transmutable::>, OptionLike>(); +} diff --git a/tests/ui/transmutability/enums/repr/padding_differences.rs b/tests/ui/transmutability/enums/repr/padding_differences.rs new file mode 100644 index 0000000000000..d0e1502b5e296 --- /dev/null +++ b/tests/ui/transmutability/enums/repr/padding_differences.rs @@ -0,0 +1,80 @@ +//@ check-pass +//! Adapted from https://rust-lang.github.io/unsafe-code-guidelines/layout/enums.html#explicit-repr-annotation-without-c-compatibility +#![crate_type = "lib"] +#![feature(transmutability)] +#![allow(dead_code, incomplete_features, non_camel_case_types)] + +use std::mem::MaybeUninit; + +mod assert { + use std::mem::{Assume, BikeshedIntrinsicFrom}; + + pub fn is_transmutable() + where + Dst: BikeshedIntrinsicFrom + {} + + pub fn is_maybe_transmutable() + where + Dst: BikeshedIntrinsicFrom + {} +} + +#[repr(u8)] enum V0 { V = 0 } +#[repr(u8)] enum V1 { V = 1 } + +fn repr_u8() { + #[repr(u8)] + enum TwoCases { + A(u8, u16), // 0x00 INIT INIT INIT + B(u16), // 0x01 PADD INIT INIT + } + + const _: () = { + assert!(std::mem::size_of::() == 4); + }; + + #[repr(C)] struct TwoCasesA(V0, u8, u8, u8); + #[repr(C)] struct TwoCasesB(V1, MaybeUninit, u8, u8); + + assert::is_transmutable::(); + assert::is_transmutable::(); + + assert::is_maybe_transmutable::(); + assert::is_maybe_transmutable::(); +} + +fn repr_c_u8() { + #[repr(C, u8)] + enum TwoCases { + A(u8, u16), // 0x00 PADD INIT PADD INIT INIT + B(u16), // 0x01 PADD INIT INIT PADD PADD + } + + const _: () = { + assert!(std::mem::size_of::() == 6); + }; + + #[repr(C)] struct TwoCasesA(V0, MaybeUninit, u8, MaybeUninit, u8, u8); + #[repr(C)] struct TwoCasesB(V1, MaybeUninit, u8, u8, MaybeUninit, MaybeUninit); + + assert::is_transmutable::(); + assert::is_transmutable::(); + + assert::is_maybe_transmutable::(); + assert::is_maybe_transmutable::(); +} diff --git a/tests/ui/transmutability/enums/repr/should_require_well_defined_layout.rs b/tests/ui/transmutability/enums/repr/should_handle_all.rs similarity index 80% rename from tests/ui/transmutability/enums/repr/should_require_well_defined_layout.rs rename to tests/ui/transmutability/enums/repr/should_handle_all.rs index 630e662b926c4..a8ec86fa40df6 100644 --- a/tests/ui/transmutability/enums/repr/should_require_well_defined_layout.rs +++ b/tests/ui/transmutability/enums/repr/should_handle_all.rs @@ -1,5 +1,4 @@ -//! An enum must have a well-defined layout to participate in a transmutation. - +//@ check-pass #![crate_type = "lib"] #![feature(repr128)] #![feature(transmutability)] @@ -21,23 +20,17 @@ mod assert { {} } -fn should_reject_repr_rust() { - fn void() { - enum repr_rust {} - assert::is_maybe_transmutable::(); //~ ERROR cannot be safely transmuted - assert::is_maybe_transmutable::(); //~ ERROR cannot be safely transmuted - } - +fn should_accept_repr_rust() { fn singleton() { enum repr_rust { V } - assert::is_maybe_transmutable::(); //~ ERROR cannot be safely transmuted - assert::is_maybe_transmutable::(); //~ ERROR cannot be safely transmuted + assert::is_maybe_transmutable::(); + assert::is_maybe_transmutable::(); } fn duplex() { enum repr_rust { A, B } - assert::is_maybe_transmutable::(); //~ ERROR cannot be safely transmuted - assert::is_maybe_transmutable::(); //~ ERROR cannot be safely transmuted + assert::is_maybe_transmutable::(); + assert::is_maybe_transmutable::(); } } @@ -116,7 +109,7 @@ fn should_accept_primitive_reprs() } } -fn should_accept_repr_C() { +fn should_accept_repr_c() { #[repr(C)] enum repr_c { V } assert::is_maybe_transmutable::(); assert::is_maybe_transmutable::(); diff --git a/tests/ui/transmutability/enums/repr/should_require_well_defined_layout.stderr b/tests/ui/transmutability/enums/repr/should_require_well_defined_layout.stderr deleted file mode 100644 index 2a683de6a65cb..0000000000000 --- a/tests/ui/transmutability/enums/repr/should_require_well_defined_layout.stderr +++ /dev/null @@ -1,135 +0,0 @@ -error[E0277]: `void::repr_rust` cannot be safely transmuted into `()` - --> $DIR/should_require_well_defined_layout.rs:27:52 - | -LL | assert::is_maybe_transmutable::(); - | ^^ analyzing the transmutability of `void::repr_rust` is not yet supported. - | -note: required by a bound in `is_maybe_transmutable` - --> $DIR/should_require_well_defined_layout.rs:13:14 - | -LL | pub fn is_maybe_transmutable() - | --------------------- required by a bound in this function -LL | where -LL | Dst: BikeshedIntrinsicFrom - | |__________^ required by this bound in `is_maybe_transmutable` - -error[E0277]: `u128` cannot be safely transmuted into `void::repr_rust` - --> $DIR/should_require_well_defined_layout.rs:28:47 - | -LL | assert::is_maybe_transmutable::(); - | ^^^^^^^^^ analyzing the transmutability of `void::repr_rust` is not yet supported. - | -note: required by a bound in `is_maybe_transmutable` - --> $DIR/should_require_well_defined_layout.rs:13:14 - | -LL | pub fn is_maybe_transmutable() - | --------------------- required by a bound in this function -LL | where -LL | Dst: BikeshedIntrinsicFrom - | |__________^ required by this bound in `is_maybe_transmutable` - -error[E0277]: `singleton::repr_rust` cannot be safely transmuted into `()` - --> $DIR/should_require_well_defined_layout.rs:33:52 - | -LL | assert::is_maybe_transmutable::(); - | ^^ analyzing the transmutability of `singleton::repr_rust` is not yet supported. - | -note: required by a bound in `is_maybe_transmutable` - --> $DIR/should_require_well_defined_layout.rs:13:14 - | -LL | pub fn is_maybe_transmutable() - | --------------------- required by a bound in this function -LL | where -LL | Dst: BikeshedIntrinsicFrom - | |__________^ required by this bound in `is_maybe_transmutable` - -error[E0277]: `u128` cannot be safely transmuted into `singleton::repr_rust` - --> $DIR/should_require_well_defined_layout.rs:34:47 - | -LL | assert::is_maybe_transmutable::(); - | ^^^^^^^^^ analyzing the transmutability of `singleton::repr_rust` is not yet supported. - | -note: required by a bound in `is_maybe_transmutable` - --> $DIR/should_require_well_defined_layout.rs:13:14 - | -LL | pub fn is_maybe_transmutable() - | --------------------- required by a bound in this function -LL | where -LL | Dst: BikeshedIntrinsicFrom - | |__________^ required by this bound in `is_maybe_transmutable` - -error[E0277]: `duplex::repr_rust` cannot be safely transmuted into `()` - --> $DIR/should_require_well_defined_layout.rs:39:52 - | -LL | assert::is_maybe_transmutable::(); - | ^^ analyzing the transmutability of `duplex::repr_rust` is not yet supported. - | -note: required by a bound in `is_maybe_transmutable` - --> $DIR/should_require_well_defined_layout.rs:13:14 - | -LL | pub fn is_maybe_transmutable() - | --------------------- required by a bound in this function -LL | where -LL | Dst: BikeshedIntrinsicFrom - | |__________^ required by this bound in `is_maybe_transmutable` - -error[E0277]: `u128` cannot be safely transmuted into `duplex::repr_rust` - --> $DIR/should_require_well_defined_layout.rs:40:47 - | -LL | assert::is_maybe_transmutable::(); - | ^^^^^^^^^ analyzing the transmutability of `duplex::repr_rust` is not yet supported. - | -note: required by a bound in `is_maybe_transmutable` - --> $DIR/should_require_well_defined_layout.rs:13:14 - | -LL | pub fn is_maybe_transmutable() - | --------------------- required by a bound in this function -LL | where -LL | Dst: BikeshedIntrinsicFrom - | |__________^ required by this bound in `is_maybe_transmutable` - -error: aborting due to 6 previous errors - -For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/transmutability/malformed-program-gracefulness/unknown_src_field.stderr b/tests/ui/transmutability/malformed-program-gracefulness/unknown_src_field.stderr index eeed8a62a2a26..82b86a86d9b77 100644 --- a/tests/ui/transmutability/malformed-program-gracefulness/unknown_src_field.stderr +++ b/tests/ui/transmutability/malformed-program-gracefulness/unknown_src_field.stderr @@ -8,7 +8,7 @@ error[E0277]: `Src` cannot be safely transmuted into `Dst` --> $DIR/unknown_src_field.rs:19:36 | LL | assert::is_transmutable::(); - | ^^^ `Dst` has an unknown layout + | ^^^ analyzing the transmutability of `Dst` is not yet supported. | note: required by a bound in `is_transmutable` --> $DIR/unknown_src_field.rs:12:14 diff --git a/tests/ui/transmutability/maybeuninit.rs b/tests/ui/transmutability/maybeuninit.rs new file mode 100644 index 0000000000000..77c3381c77497 --- /dev/null +++ b/tests/ui/transmutability/maybeuninit.rs @@ -0,0 +1,43 @@ +#![crate_type = "lib"] +#![feature(transmutability)] +#![allow(dead_code, incomplete_features, non_camel_case_types)] + +use std::mem::MaybeUninit; + +mod assert { + use std::mem::{Assume, BikeshedIntrinsicFrom}; + + pub fn is_maybe_transmutable() + where + Dst: BikeshedIntrinsicFrom + {} +} + +fn validity() { + // An initialized byte is a valid uninitialized byte. + assert::is_maybe_transmutable::>(); + + // An uninitialized byte is never a valid initialized byte. + assert::is_maybe_transmutable::, u8>(); //~ ERROR: cannot be safely transmuted +} + +fn padding() { + #[repr(align(8))] + struct Align8; + + #[repr(u8)] + enum ImplicitlyPadded { + A(Align8), + } + + #[repr(u8)] + enum V0 { + V0 = 0, + } + + #[repr(C)] + struct ExplicitlyPadded(V0, MaybeUninit<[u8; 7]>); + + assert::is_maybe_transmutable::(); + assert::is_maybe_transmutable::(); +} diff --git a/tests/ui/transmutability/maybeuninit.stderr b/tests/ui/transmutability/maybeuninit.stderr new file mode 100644 index 0000000000000..be7dcaf35eab9 --- /dev/null +++ b/tests/ui/transmutability/maybeuninit.stderr @@ -0,0 +1,18 @@ +error[E0277]: `MaybeUninit` cannot be safely transmuted into `u8` + --> $DIR/maybeuninit.rs:21:54 + | +LL | assert::is_maybe_transmutable::, u8>(); + | ^^ at least one value of `MaybeUninit` isn't a bit-valid value of `u8` + | +note: required by a bound in `is_maybe_transmutable` + --> $DIR/maybeuninit.rs:12:14 + | +LL | pub fn is_maybe_transmutable() + | --------------------- required by a bound in this function +LL | where +LL | Dst: BikeshedIntrinsicFrom + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `is_maybe_transmutable` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/transmutability/references/unsafecell.rs b/tests/ui/transmutability/references/unsafecell.rs new file mode 100644 index 0000000000000..a8a1f969fb493 --- /dev/null +++ b/tests/ui/transmutability/references/unsafecell.rs @@ -0,0 +1,47 @@ +#![crate_type = "lib"] +#![feature(transmutability)] +#![allow(dead_code, incomplete_features, non_camel_case_types)] + +use std::cell::UnsafeCell; + +mod assert { + use std::mem::{Assume, BikeshedIntrinsicFrom}; + + pub fn is_maybe_transmutable() + where + Dst: BikeshedIntrinsicFrom + {} +} + +fn value_to_value() { + // We accept value-to-value transmutations of `UnsafeCell`-containing types, + // because owning a value implies exclusive access. + assert::is_maybe_transmutable::, u8>(); + assert::is_maybe_transmutable::>(); + assert::is_maybe_transmutable::, UnsafeCell>(); +} + +fn ref_to_ref() { + // We forbid `UnsafeCell`-containing ref-to-ref transmutations, because the + // two types may use different, incompatible synchronization strategies. + assert::is_maybe_transmutable::<&'static u8, &'static UnsafeCell>(); //~ ERROR: cannot be safely transmuted + + assert::is_maybe_transmutable::<&'static UnsafeCell, &'static UnsafeCell>(); //~ ERROR: cannot be safely transmuted +} + +fn mut_to_mut() { + // `UnsafeCell` does't matter for `&mut T` to `&mut U`, since exclusive + // borrows can't be used for shared access. + assert::is_maybe_transmutable::<&'static mut u8, &'static mut UnsafeCell>(); + assert::is_maybe_transmutable::<&'static mut UnsafeCell, &'static mut u8>(); + assert::is_maybe_transmutable::<&'static mut UnsafeCell, &'static mut UnsafeCell>(); +} + +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>(); + assert::is_maybe_transmutable::<&'static mut UnsafeCell, &'static u8>(); + assert::is_maybe_transmutable::<&'static mut UnsafeCell, &'static UnsafeCell>(); +} diff --git a/tests/ui/transmutability/references/unsafecell.stderr b/tests/ui/transmutability/references/unsafecell.stderr new file mode 100644 index 0000000000000..651eb8ceb2676 --- /dev/null +++ b/tests/ui/transmutability/references/unsafecell.stderr @@ -0,0 +1,33 @@ +error[E0277]: `&u8` cannot be safely transmuted into `&UnsafeCell` + --> $DIR/unsafecell.rs:27:50 + | +LL | assert::is_maybe_transmutable::<&'static u8, &'static UnsafeCell>(); + | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `Freeze` is not implemented for `&'static UnsafeCell` + | +note: required by a bound in `is_maybe_transmutable` + --> $DIR/unsafecell.rs:12:14 + | +LL | pub fn is_maybe_transmutable() + | --------------------- required by a bound in this function +LL | where +LL | Dst: BikeshedIntrinsicFrom + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `is_maybe_transmutable` + +error[E0277]: `&UnsafeCell` cannot be safely transmuted into `&UnsafeCell` + --> $DIR/unsafecell.rs:29:62 + | +LL | assert::is_maybe_transmutable::<&'static UnsafeCell, &'static UnsafeCell>(); + | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `Freeze` is not implemented for `&'static UnsafeCell` + | +note: required by a bound in `is_maybe_transmutable` + --> $DIR/unsafecell.rs:12:14 + | +LL | pub fn is_maybe_transmutable() + | --------------------- required by a bound in this function +LL | where +LL | Dst: BikeshedIntrinsicFrom + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `is_maybe_transmutable` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/transmutability/structs/repr/should_require_well_defined_layout.rs b/tests/ui/transmutability/structs/repr/should_handle_all.rs similarity index 54% rename from tests/ui/transmutability/structs/repr/should_require_well_defined_layout.rs rename to tests/ui/transmutability/structs/repr/should_handle_all.rs index 2e673601baf07..52c24eecf1246 100644 --- a/tests/ui/transmutability/structs/repr/should_require_well_defined_layout.rs +++ b/tests/ui/transmutability/structs/repr/should_handle_all.rs @@ -1,3 +1,4 @@ +//@ check-pass //! A struct must have a well-defined layout to participate in a transmutation. #![crate_type = "lib"] @@ -20,47 +21,47 @@ mod assert { {} } -fn should_reject_repr_rust() +fn should_accept_repr_rust() { fn unit() { struct repr_rust; - assert::is_maybe_transmutable::(); //~ ERROR cannot be safely transmuted - assert::is_maybe_transmutable::(); //~ ERROR cannot be safely transmuted + assert::is_maybe_transmutable::(); + assert::is_maybe_transmutable::(); } fn tuple() { struct repr_rust(); - assert::is_maybe_transmutable::(); //~ ERROR cannot be safely transmuted - assert::is_maybe_transmutable::(); //~ ERROR cannot be safely transmuted + assert::is_maybe_transmutable::(); + assert::is_maybe_transmutable::(); } fn braces() { struct repr_rust{} - assert::is_maybe_transmutable::(); //~ ERROR cannot be safely transmuted - assert::is_maybe_transmutable::(); //~ ERROR cannot be safely transmuted + assert::is_maybe_transmutable::(); + assert::is_maybe_transmutable::(); } fn aligned() { #[repr(align(1))] struct repr_rust{} - assert::is_maybe_transmutable::(); //~ ERROR cannot be safely transmuted - assert::is_maybe_transmutable::(); //~ ERROR cannot be safely transmuted + assert::is_maybe_transmutable::(); + assert::is_maybe_transmutable::(); } fn packed() { #[repr(packed)] struct repr_rust{} - assert::is_maybe_transmutable::(); //~ ERROR cannot be safely transmuted - assert::is_maybe_transmutable::(); //~ ERROR cannot be safely transmuted + assert::is_maybe_transmutable::(); + assert::is_maybe_transmutable::(); } fn nested() { struct repr_rust; #[repr(C)] struct repr_c(repr_rust); - assert::is_maybe_transmutable::(); //~ ERROR cannot be safely transmuted - assert::is_maybe_transmutable::(); //~ ERROR cannot be safely transmuted + assert::is_maybe_transmutable::(); + assert::is_maybe_transmutable::(); } } -fn should_accept_repr_C() +fn should_accept_repr_c() { fn unit() { #[repr(C)] struct repr_c; diff --git a/tests/ui/transmutability/structs/repr/should_require_well_defined_layout.stderr b/tests/ui/transmutability/structs/repr/should_require_well_defined_layout.stderr deleted file mode 100644 index 77788f72c2168..0000000000000 --- a/tests/ui/transmutability/structs/repr/should_require_well_defined_layout.stderr +++ /dev/null @@ -1,267 +0,0 @@ -error[E0277]: `should_reject_repr_rust::unit::repr_rust` cannot be safely transmuted into `()` - --> $DIR/should_require_well_defined_layout.rs:27:52 - | -LL | assert::is_maybe_transmutable::(); - | ^^ analyzing the transmutability of `should_reject_repr_rust::unit::repr_rust` is not yet supported. - | -note: required by a bound in `is_maybe_transmutable` - --> $DIR/should_require_well_defined_layout.rs:12:14 - | -LL | pub fn is_maybe_transmutable() - | --------------------- required by a bound in this function -LL | where -LL | Dst: BikeshedIntrinsicFrom - | |__________^ required by this bound in `is_maybe_transmutable` - -error[E0277]: `u128` cannot be safely transmuted into `should_reject_repr_rust::unit::repr_rust` - --> $DIR/should_require_well_defined_layout.rs:28:47 - | -LL | assert::is_maybe_transmutable::(); - | ^^^^^^^^^ analyzing the transmutability of `should_reject_repr_rust::unit::repr_rust` is not yet supported. - | -note: required by a bound in `is_maybe_transmutable` - --> $DIR/should_require_well_defined_layout.rs:12:14 - | -LL | pub fn is_maybe_transmutable() - | --------------------- required by a bound in this function -LL | where -LL | Dst: BikeshedIntrinsicFrom - | |__________^ required by this bound in `is_maybe_transmutable` - -error[E0277]: `should_reject_repr_rust::tuple::repr_rust` cannot be safely transmuted into `()` - --> $DIR/should_require_well_defined_layout.rs:33:52 - | -LL | assert::is_maybe_transmutable::(); - | ^^ analyzing the transmutability of `should_reject_repr_rust::tuple::repr_rust` is not yet supported. - | -note: required by a bound in `is_maybe_transmutable` - --> $DIR/should_require_well_defined_layout.rs:12:14 - | -LL | pub fn is_maybe_transmutable() - | --------------------- required by a bound in this function -LL | where -LL | Dst: BikeshedIntrinsicFrom - | |__________^ required by this bound in `is_maybe_transmutable` - -error[E0277]: `u128` cannot be safely transmuted into `should_reject_repr_rust::tuple::repr_rust` - --> $DIR/should_require_well_defined_layout.rs:34:47 - | -LL | assert::is_maybe_transmutable::(); - | ^^^^^^^^^ analyzing the transmutability of `should_reject_repr_rust::tuple::repr_rust` is not yet supported. - | -note: required by a bound in `is_maybe_transmutable` - --> $DIR/should_require_well_defined_layout.rs:12:14 - | -LL | pub fn is_maybe_transmutable() - | --------------------- required by a bound in this function -LL | where -LL | Dst: BikeshedIntrinsicFrom - | |__________^ required by this bound in `is_maybe_transmutable` - -error[E0277]: `should_reject_repr_rust::braces::repr_rust` cannot be safely transmuted into `()` - --> $DIR/should_require_well_defined_layout.rs:39:52 - | -LL | assert::is_maybe_transmutable::(); - | ^^ analyzing the transmutability of `should_reject_repr_rust::braces::repr_rust` is not yet supported. - | -note: required by a bound in `is_maybe_transmutable` - --> $DIR/should_require_well_defined_layout.rs:12:14 - | -LL | pub fn is_maybe_transmutable() - | --------------------- required by a bound in this function -LL | where -LL | Dst: BikeshedIntrinsicFrom - | |__________^ required by this bound in `is_maybe_transmutable` - -error[E0277]: `u128` cannot be safely transmuted into `should_reject_repr_rust::braces::repr_rust` - --> $DIR/should_require_well_defined_layout.rs:40:47 - | -LL | assert::is_maybe_transmutable::(); - | ^^^^^^^^^ analyzing the transmutability of `should_reject_repr_rust::braces::repr_rust` is not yet supported. - | -note: required by a bound in `is_maybe_transmutable` - --> $DIR/should_require_well_defined_layout.rs:12:14 - | -LL | pub fn is_maybe_transmutable() - | --------------------- required by a bound in this function -LL | where -LL | Dst: BikeshedIntrinsicFrom - | |__________^ required by this bound in `is_maybe_transmutable` - -error[E0277]: `aligned::repr_rust` cannot be safely transmuted into `()` - --> $DIR/should_require_well_defined_layout.rs:45:52 - | -LL | assert::is_maybe_transmutable::(); - | ^^ analyzing the transmutability of `aligned::repr_rust` is not yet supported. - | -note: required by a bound in `is_maybe_transmutable` - --> $DIR/should_require_well_defined_layout.rs:12:14 - | -LL | pub fn is_maybe_transmutable() - | --------------------- required by a bound in this function -LL | where -LL | Dst: BikeshedIntrinsicFrom - | |__________^ required by this bound in `is_maybe_transmutable` - -error[E0277]: `u128` cannot be safely transmuted into `aligned::repr_rust` - --> $DIR/should_require_well_defined_layout.rs:46:47 - | -LL | assert::is_maybe_transmutable::(); - | ^^^^^^^^^ analyzing the transmutability of `aligned::repr_rust` is not yet supported. - | -note: required by a bound in `is_maybe_transmutable` - --> $DIR/should_require_well_defined_layout.rs:12:14 - | -LL | pub fn is_maybe_transmutable() - | --------------------- required by a bound in this function -LL | where -LL | Dst: BikeshedIntrinsicFrom - | |__________^ required by this bound in `is_maybe_transmutable` - -error[E0277]: `packed::repr_rust` cannot be safely transmuted into `()` - --> $DIR/should_require_well_defined_layout.rs:51:52 - | -LL | assert::is_maybe_transmutable::(); - | ^^ analyzing the transmutability of `packed::repr_rust` is not yet supported. - | -note: required by a bound in `is_maybe_transmutable` - --> $DIR/should_require_well_defined_layout.rs:12:14 - | -LL | pub fn is_maybe_transmutable() - | --------------------- required by a bound in this function -LL | where -LL | Dst: BikeshedIntrinsicFrom - | |__________^ required by this bound in `is_maybe_transmutable` - -error[E0277]: `u128` cannot be safely transmuted into `packed::repr_rust` - --> $DIR/should_require_well_defined_layout.rs:52:47 - | -LL | assert::is_maybe_transmutable::(); - | ^^^^^^^^^ analyzing the transmutability of `packed::repr_rust` is not yet supported. - | -note: required by a bound in `is_maybe_transmutable` - --> $DIR/should_require_well_defined_layout.rs:12:14 - | -LL | pub fn is_maybe_transmutable() - | --------------------- required by a bound in this function -LL | where -LL | Dst: BikeshedIntrinsicFrom - | |__________^ required by this bound in `is_maybe_transmutable` - -error[E0277]: `nested::repr_c` cannot be safely transmuted into `()` - --> $DIR/should_require_well_defined_layout.rs:58:49 - | -LL | assert::is_maybe_transmutable::(); - | ^^ analyzing the transmutability of `nested::repr_c` is not yet supported. - | -note: required by a bound in `is_maybe_transmutable` - --> $DIR/should_require_well_defined_layout.rs:12:14 - | -LL | pub fn is_maybe_transmutable() - | --------------------- required by a bound in this function -LL | where -LL | Dst: BikeshedIntrinsicFrom - | |__________^ required by this bound in `is_maybe_transmutable` - -error[E0277]: `u128` cannot be safely transmuted into `nested::repr_c` - --> $DIR/should_require_well_defined_layout.rs:59:47 - | -LL | assert::is_maybe_transmutable::(); - | ^^^^^^ analyzing the transmutability of `nested::repr_c` is not yet supported. - | -note: required by a bound in `is_maybe_transmutable` - --> $DIR/should_require_well_defined_layout.rs:12:14 - | -LL | pub fn is_maybe_transmutable() - | --------------------- required by a bound in this function -LL | where -LL | Dst: BikeshedIntrinsicFrom - | |__________^ required by this bound in `is_maybe_transmutable` - -error: aborting due to 12 previous errors - -For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/transmutability/structs/repr/transmute_infinitely_recursive_type.rs b/tests/ui/transmutability/structs/repr/transmute_infinitely_recursive_type.rs index 4c285a616b3d9..64110753832cb 100644 --- a/tests/ui/transmutability/structs/repr/transmute_infinitely_recursive_type.rs +++ b/tests/ui/transmutability/structs/repr/transmute_infinitely_recursive_type.rs @@ -22,4 +22,5 @@ fn should_pad_explicitly_packed_field() { //~^ ERROR: recursive type assert::is_maybe_transmutable::(); + //~^ ERROR: cannot be safely transmuted } diff --git a/tests/ui/transmutability/structs/repr/transmute_infinitely_recursive_type.stderr b/tests/ui/transmutability/structs/repr/transmute_infinitely_recursive_type.stderr index 7fb051f66250e..7587730d4efe4 100644 --- a/tests/ui/transmutability/structs/repr/transmute_infinitely_recursive_type.stderr +++ b/tests/ui/transmutability/structs/repr/transmute_infinitely_recursive_type.stderr @@ -15,7 +15,22 @@ error[E0391]: cycle detected when computing layout of `should_pad_explicitly_pac = note: cycle used when evaluating trait selection obligation `(): core::mem::transmutability::BikeshedIntrinsicFrom` = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information -error: aborting due to 2 previous errors +error[E0277]: `ExplicitlyPadded` cannot be safely transmuted into `()` + --> $DIR/transmute_infinitely_recursive_type.rs:24:55 + | +LL | assert::is_maybe_transmutable::(); + | ^^ analyzing the transmutability of `ExplicitlyPadded` is not yet supported. + | +note: required by a bound in `is_maybe_transmutable` + --> $DIR/transmute_infinitely_recursive_type.rs:14:14 + | +LL | pub fn is_maybe_transmutable() + | --------------------- required by a bound in this function +LL | where +LL | Dst: BikeshedIntrinsicFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `is_maybe_transmutable` + +error: aborting due to 3 previous errors -Some errors have detailed explanations: E0072, E0391. +Some errors have detailed explanations: E0072, E0277, E0391. For more information about an error, try `rustc --explain E0072`. diff --git a/tests/ui/transmutability/transmute-padding-ice.rs b/tests/ui/transmutability/transmute-padding-ice.rs index 3f3f75bc08611..f5935a0009e77 100644 --- a/tests/ui/transmutability/transmute-padding-ice.rs +++ b/tests/ui/transmutability/transmute-padding-ice.rs @@ -1,7 +1,13 @@ +//@ check-pass +//! This UI test was introduced as check-fail by a buggy bug-fix for an ICE. In +//! fact, this transmutation should be valid. + #![crate_type = "lib"] #![feature(transmutability)] #![allow(dead_code)] +use std::mem::size_of; + mod assert { use std::mem::{Assume, BikeshedIntrinsicFrom}; @@ -22,6 +28,7 @@ fn test() { #[repr(C)] struct B(u8, u8); + assert_eq!(size_of::(), size_of::()); + assert::is_maybe_transmutable::(); - //~^ ERROR cannot be safely transmuted } diff --git a/tests/ui/transmutability/transmute-padding-ice.stderr b/tests/ui/transmutability/transmute-padding-ice.stderr deleted file mode 100644 index 4c121d463c602..0000000000000 --- a/tests/ui/transmutability/transmute-padding-ice.stderr +++ /dev/null @@ -1,22 +0,0 @@ -error[E0277]: `B` cannot be safely transmuted into `A` - --> $DIR/transmute-padding-ice.rs:25:40 - | -LL | assert::is_maybe_transmutable::(); - | ^ the size of `B` is smaller than the size of `A` - | -note: required by a bound in `is_maybe_transmutable` - --> $DIR/transmute-padding-ice.rs:10:14 - | -LL | pub fn is_maybe_transmutable() - | --------------------- required by a bound in this function -LL | where -LL | Dst: BikeshedIntrinsicFrom< - | ______________^ -LL | | Src, -LL | | { Assume { alignment: true, lifetimes: true, safety: true, validity: true } }, -LL | | >, - | |_________^ required by this bound in `is_maybe_transmutable` - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/transmutability/uninhabited.rs b/tests/ui/transmutability/uninhabited.rs new file mode 100644 index 0000000000000..479a317b3345b --- /dev/null +++ b/tests/ui/transmutability/uninhabited.rs @@ -0,0 +1,74 @@ +#![crate_type = "lib"] +#![feature(rustc_attrs, transmutability)] +#![allow(dead_code, incomplete_features, non_camel_case_types)] + +mod assert { + use std::mem::{Assume, BikeshedIntrinsicFrom}; + + pub fn is_maybe_transmutable() + where + Dst: BikeshedIntrinsicFrom + {} +} + +fn void() { + #[rustc_layout(debug)] + enum Void {} //~ERROR: layout_of + + // This transmutation is vacuously acceptable; since one cannot construct a + // `Void`, unsoundness cannot directly arise from transmuting a void into + // anything else. + assert::is_maybe_transmutable::(); + + assert::is_maybe_transmutable::<(), Void>(); //~ ERROR: cannot be safely transmuted +} + +// Non-ZST uninhabited types are, nonetheless, uninhabited. +fn yawning_void() { + enum Void {} + + #[rustc_layout(debug)] + struct YawningVoid(Void, u128); //~ERROR: layout_of + + const _: () = { + assert!(std::mem::size_of::() == std::mem::size_of::()); + // Just to be sure the above constant actually evaluated: + assert!(false); //~ ERROR: evaluation of constant value failed + }; + + // This transmutation is vacuously acceptable; since one cannot construct a + // `Void`, unsoundness cannot directly arise from transmuting a void into + // anything else. + assert::is_maybe_transmutable::(); + + assert::is_maybe_transmutable::<(), Void>(); //~ ERROR: cannot be safely transmuted +} + +// References to uninhabited types are, logically, uninhabited, but for layout +// purposes are not ZSTs, and aren't treated as uninhabited when they appear in +// enum variants. +fn distant_void() { + enum Void {} + + #[rustc_layout(debug)] + enum DistantVoid { //~ERROR: layout_of + A(&'static Void) + } + + const _: () = { + assert!(std::mem::size_of::() == std::mem::size_of::()); + // Just to be sure the above constant actually evaluated: + assert!(false); //~ ERROR: evaluation of constant value failed + }; + + assert::is_maybe_transmutable::(); + assert::is_maybe_transmutable::(); + assert::is_maybe_transmutable::(); //~ ERROR: cannot be safely transmuted +} diff --git a/tests/ui/transmutability/uninhabited.stderr b/tests/ui/transmutability/uninhabited.stderr new file mode 100644 index 0000000000000..198b6218a1ea4 --- /dev/null +++ b/tests/ui/transmutability/uninhabited.stderr @@ -0,0 +1,181 @@ +error[E0080]: evaluation of constant value failed + --> $DIR/uninhabited.rs:43:9 + | +LL | assert!(false); + | ^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: false', $DIR/uninhabited.rs:43:9 + | + = note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> $DIR/uninhabited.rs:68:9 + | +LL | assert!(false); + | ^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: false', $DIR/uninhabited.rs:68:9 + | + = note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `()` cannot be safely transmuted into `void::Void` + --> $DIR/uninhabited.rs:30:41 + | +LL | assert::is_maybe_transmutable::<(), Void>(); + | ^^^^ `void::Void` is uninhabited + | +note: required by a bound in `is_maybe_transmutable` + --> $DIR/uninhabited.rs:10:14 + | +LL | pub fn is_maybe_transmutable() + | --------------------- required by a bound in this function +LL | where +LL | Dst: BikeshedIntrinsicFrom + | |__________^ required by this bound in `is_maybe_transmutable` + +error[E0277]: `()` cannot be safely transmuted into `yawning_void::Void` + --> $DIR/uninhabited.rs:51:41 + | +LL | assert::is_maybe_transmutable::<(), Void>(); + | ^^^^ `yawning_void::Void` is uninhabited + | +note: required by a bound in `is_maybe_transmutable` + --> $DIR/uninhabited.rs:10:14 + | +LL | pub fn is_maybe_transmutable() + | --------------------- required by a bound in this function +LL | where +LL | Dst: BikeshedIntrinsicFrom + | |__________^ required by this bound in `is_maybe_transmutable` + +error[E0277]: `u128` cannot be safely transmuted into `DistantVoid` + --> $DIR/uninhabited.rs:73:43 + | +LL | assert::is_maybe_transmutable::(); + | ^^^^^^^^^^^ at least one value of `u128` isn't a bit-valid value of `DistantVoid` + | +note: required by a bound in `is_maybe_transmutable` + --> $DIR/uninhabited.rs:10:14 + | +LL | pub fn is_maybe_transmutable() + | --------------------- required by a bound in this function +LL | where +LL | Dst: BikeshedIntrinsicFrom + | |__________^ required by this bound in `is_maybe_transmutable` + +error: layout_of(void::Void) = Layout { + size: Size(0 bytes), + align: AbiAndPrefAlign { + abi: Align(1 bytes), + pref: Align(1 bytes), + }, + abi: Uninhabited, + fields: Primitive, + largest_niche: None, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(1 bytes), + } + --> $DIR/uninhabited.rs:23:5 + | +LL | enum Void {} + | ^^^^^^^^^ + +error: layout_of(YawningVoid) = Layout { + size: Size(16 bytes), + align: AbiAndPrefAlign { + abi: Align(16 bytes), + pref: Align(16 bytes), + }, + abi: Uninhabited, + fields: Arbitrary { + offsets: [ + Size(16 bytes), + Size(0 bytes), + ], + memory_index: [ + 1, + 0, + ], + }, + largest_niche: None, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(16 bytes), + } + --> $DIR/uninhabited.rs:38:5 + | +LL | struct YawningVoid(Void, u128); + | ^^^^^^^^^^^^^^^^^^ + +error: layout_of(DistantVoid) = Layout { + size: Size(8 bytes), + align: AbiAndPrefAlign { + abi: Align(8 bytes), + pref: Align(8 bytes), + }, + abi: Scalar( + Initialized { + value: Pointer( + AddressSpace( + 0, + ), + ), + valid_range: 1..=18446744073709551615, + }, + ), + fields: Arbitrary { + offsets: [ + Size(0 bytes), + ], + memory_index: [ + 0, + ], + }, + largest_niche: Some( + Niche { + offset: Size(0 bytes), + value: Pointer( + AddressSpace( + 0, + ), + ), + valid_range: 1..=18446744073709551615, + }, + ), + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(8 bytes), + } + --> $DIR/uninhabited.rs:61:5 + | +LL | enum DistantVoid { + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 8 previous errors + +Some errors have detailed explanations: E0080, E0277. +For more information about an error, try `rustc --explain E0080`. diff --git a/tests/ui/transmutability/unions/repr/should_handle_align.rs b/tests/ui/transmutability/unions/repr/should_handle_align.rs index 8668cca3cb50d..ba4e904e16197 100644 --- a/tests/ui/transmutability/unions/repr/should_handle_align.rs +++ b/tests/ui/transmutability/unions/repr/should_handle_align.rs @@ -25,13 +25,13 @@ fn should_pad_explicitly_aligned_field() { #[derive(Clone, Copy)] #[repr(u8)] enum V0u8 { V = 0 } #[derive(Clone, Copy)] #[repr(u8)] enum V1u8 { V = 1 } - #[repr(C)] + #[repr(align(1))] pub union Uninit { a: (), b: V1u8, } - #[repr(C, align(2))] + #[repr(align(2))] pub union align_2 { a: V0u8, } diff --git a/tests/ui/transmutability/unions/repr/should_require_well_defined_layout.rs b/tests/ui/transmutability/unions/repr/should_handle_all.rs similarity index 58% rename from tests/ui/transmutability/unions/repr/should_require_well_defined_layout.rs rename to tests/ui/transmutability/unions/repr/should_handle_all.rs index 8495b0ea88f70..85d48dd9b7ff1 100644 --- a/tests/ui/transmutability/unions/repr/should_require_well_defined_layout.rs +++ b/tests/ui/transmutability/unions/repr/should_handle_all.rs @@ -1,7 +1,7 @@ -//! A struct must have a well-defined layout to participate in a transmutation. +//@ check-pass #![crate_type = "lib"] -#![feature(transmutability)] +#![feature(transmutability, transparent_unions)] #![allow(dead_code, incomplete_features, non_camel_case_types)] mod assert { @@ -20,17 +20,17 @@ mod assert { {} } -fn should_reject_repr_rust() +fn should_accept_repr_rust() { union repr_rust { a: u8 } - assert::is_maybe_transmutable::(); //~ ERROR cannot be safely transmuted - assert::is_maybe_transmutable::(); //~ ERROR cannot be safely transmuted + assert::is_maybe_transmutable::(); + assert::is_maybe_transmutable::(); } -fn should_accept_repr_C() +fn should_accept_repr_c() { #[repr(C)] union repr_c { @@ -41,3 +41,15 @@ fn should_accept_repr_C() assert::is_maybe_transmutable::(); assert::is_maybe_transmutable::(); } + + +fn should_accept_transparent() +{ + #[repr(transparent)] + union repr_transparent { + a: u8 + } + + assert::is_maybe_transmutable::(); + assert::is_maybe_transmutable::(); +} diff --git a/tests/ui/transmutability/unions/repr/should_handle_packed.rs b/tests/ui/transmutability/unions/repr/should_handle_packed.rs index 4af6c1d3a6138..fc06eba4353b2 100644 --- a/tests/ui/transmutability/unions/repr/should_handle_packed.rs +++ b/tests/ui/transmutability/unions/repr/should_handle_packed.rs @@ -27,7 +27,6 @@ fn should_pad_explicitly_packed_field() { #[derive(Clone, Copy)] #[repr(u8)] enum V2u8 { V = 2 } #[derive(Clone, Copy)] #[repr(u32)] enum V3u32 { V = 3 } - #[repr(C)] pub union Uninit { a: (), b: V1u8, diff --git a/tests/ui/transmutability/unions/repr/should_require_well_defined_layout.stderr b/tests/ui/transmutability/unions/repr/should_require_well_defined_layout.stderr deleted file mode 100644 index bec07f131039b..0000000000000 --- a/tests/ui/transmutability/unions/repr/should_require_well_defined_layout.stderr +++ /dev/null @@ -1,47 +0,0 @@ -error[E0277]: `should_reject_repr_rust::repr_rust` cannot be safely transmuted into `()` - --> $DIR/should_require_well_defined_layout.rs:29:48 - | -LL | assert::is_maybe_transmutable::(); - | ^^ analyzing the transmutability of `should_reject_repr_rust::repr_rust` is not yet supported. - | -note: required by a bound in `is_maybe_transmutable` - --> $DIR/should_require_well_defined_layout.rs:12:14 - | -LL | pub fn is_maybe_transmutable() - | --------------------- required by a bound in this function -LL | where -LL | Dst: BikeshedIntrinsicFrom - | |__________^ required by this bound in `is_maybe_transmutable` - -error[E0277]: `u128` cannot be safely transmuted into `should_reject_repr_rust::repr_rust` - --> $DIR/should_require_well_defined_layout.rs:30:43 - | -LL | assert::is_maybe_transmutable::(); - | ^^^^^^^^^ analyzing the transmutability of `should_reject_repr_rust::repr_rust` is not yet supported. - | -note: required by a bound in `is_maybe_transmutable` - --> $DIR/should_require_well_defined_layout.rs:12:14 - | -LL | pub fn is_maybe_transmutable() - | --------------------- required by a bound in this function -LL | where -LL | Dst: BikeshedIntrinsicFrom - | |__________^ required by this bound in `is_maybe_transmutable` - -error: aborting due to 2 previous errors - -For more information about this error, try `rustc --explain E0277`.