diff --git a/Cargo.lock b/Cargo.lock index 2bf07149cc86c..f8790be242afc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3664,6 +3664,7 @@ dependencies = [ "rustc_arena", "rustc_ast", "rustc_attr", + "rustc_const_eval", "rustc_data_structures", "rustc_errors", "rustc_fs_util", diff --git a/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs b/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs index 6937e658ed5ee..9201bb035eadc 100644 --- a/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs +++ b/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs @@ -55,6 +55,7 @@ mod simd; pub(crate) use cpuid::codegen_cpuid_call; pub(crate) use llvm::codegen_llvm_intrinsic_call; +use rustc_const_eval::might_permit_raw_init::might_permit_raw_init; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::subst::SubstsRef; use rustc_span::symbol::{kw, sym, Symbol}; @@ -673,10 +674,7 @@ fn codegen_regular_intrinsic_call<'tcx>( } if intrinsic == sym::assert_zero_valid - && !layout.might_permit_raw_init( - fx, - InitKind::Zero, - fx.tcx.sess.opts.debugging_opts.strict_init_checks) { + && !might_permit_raw_init(fx.tcx, layout, InitKind::Zero) { with_no_trimmed_paths!({ crate::base::codegen_panic( @@ -689,10 +687,7 @@ fn codegen_regular_intrinsic_call<'tcx>( } if intrinsic == sym::assert_uninit_valid - && !layout.might_permit_raw_init( - fx, - InitKind::Uninit, - fx.tcx.sess.opts.debugging_opts.strict_init_checks) { + && !might_permit_raw_init(fx.tcx, layout, InitKind::Uninit) { with_no_trimmed_paths!({ crate::base::codegen_panic( diff --git a/compiler/rustc_codegen_cranelift/src/lib.rs b/compiler/rustc_codegen_cranelift/src/lib.rs index be2d3108c5fa9..23724538194a4 100644 --- a/compiler/rustc_codegen_cranelift/src/lib.rs +++ b/compiler/rustc_codegen_cranelift/src/lib.rs @@ -8,6 +8,7 @@ extern crate rustc_middle; extern crate rustc_ast; extern crate rustc_codegen_ssa; +extern crate rustc_const_eval; extern crate rustc_data_structures; extern crate rustc_errors; extern crate rustc_fs_util; diff --git a/compiler/rustc_codegen_ssa/Cargo.toml b/compiler/rustc_codegen_ssa/Cargo.toml index faabea92f5a6c..81c8b9ceb136e 100644 --- a/compiler/rustc_codegen_ssa/Cargo.toml +++ b/compiler/rustc_codegen_ssa/Cargo.toml @@ -40,6 +40,7 @@ rustc_metadata = { path = "../rustc_metadata" } rustc_query_system = { path = "../rustc_query_system" } rustc_target = { path = "../rustc_target" } rustc_session = { path = "../rustc_session" } +rustc_const_eval = { path = "../rustc_const_eval" } [dependencies.object] version = "0.29.0" diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index b8e3cb32ef633..a0a9065180a65 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -528,7 +528,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { source_info: mir::SourceInfo, target: Option, cleanup: Option, - strict_validity: bool, ) -> bool { // Emit a panic or a no-op for `assert_*` intrinsics. // These are intrinsics that compile to panics so that we can get a message @@ -546,13 +545,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { _ => None, }); if let Some(intrinsic) = panic_intrinsic { + use rustc_const_eval::might_permit_raw_init::might_permit_raw_init; use AssertIntrinsic::*; + let ty = instance.unwrap().substs.type_at(0); let layout = bx.layout_of(ty); let do_panic = match intrinsic { Inhabited => layout.abi.is_uninhabited(), - ZeroValid => !layout.might_permit_raw_init(bx, InitKind::Zero, strict_validity), - UninitValid => !layout.might_permit_raw_init(bx, InitKind::Uninit, strict_validity), + ZeroValid => !might_permit_raw_init(bx.tcx(), layout, InitKind::Zero), + UninitValid => !might_permit_raw_init(bx.tcx(), layout, InitKind::Uninit), }; if do_panic { let msg_str = with_no_visible_paths!({ @@ -687,7 +688,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { source_info, target, cleanup, - self.cx.tcx().sess.opts.debugging_opts.strict_init_checks, ) { return; } diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 29ab1d187719c..e00e667fb71e2 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -104,7 +104,7 @@ pub struct CompileTimeInterpreter<'mir, 'tcx> { } impl<'mir, 'tcx> CompileTimeInterpreter<'mir, 'tcx> { - pub(super) fn new(const_eval_limit: Limit, can_access_statics: bool) -> Self { + pub(crate) fn new(const_eval_limit: Limit, can_access_statics: bool) -> Self { CompileTimeInterpreter { steps_remaining: const_eval_limit.0, stack: Vec::new(), diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 93b64d9d37a49..0503c93beccf5 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -22,6 +22,8 @@ use super::{ Pointer, }; +use crate::might_permit_raw_init::might_permit_raw_init; + mod caller_location; mod type_name; @@ -413,35 +415,33 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { ), )?; } - if intrinsic_name == sym::assert_zero_valid - && !layout.might_permit_raw_init( - self, - InitKind::Zero, - self.tcx.sess.opts.debugging_opts.strict_init_checks, - ) - { - M::abort( - self, - format!( - "aborted execution: attempted to zero-initialize type `{}`, which is invalid", - ty - ), - )?; + + if intrinsic_name == sym::assert_zero_valid { + let should_panic = !might_permit_raw_init(*self.tcx, layout, InitKind::Zero); + + if should_panic { + M::abort( + self, + format!( + "aborted execution: attempted to zero-initialize type `{}`, which is invalid", + ty + ), + )?; + } } - if intrinsic_name == sym::assert_uninit_valid - && !layout.might_permit_raw_init( - self, - InitKind::Uninit, - self.tcx.sess.opts.debugging_opts.strict_init_checks, - ) - { - M::abort( - self, - format!( - "aborted execution: attempted to leave type `{}` uninitialized, which is invalid", - ty - ), - )?; + + if intrinsic_name == sym::assert_uninit_valid { + let should_panic = !might_permit_raw_init(*self.tcx, layout, InitKind::Uninit); + + if should_panic { + M::abort( + self, + format!( + "aborted execution: attempted to leave type `{}` uninitialized, which is invalid", + ty + ), + )?; + } } } sym::simd_insert => { diff --git a/compiler/rustc_const_eval/src/lib.rs b/compiler/rustc_const_eval/src/lib.rs index d65d4f7eb720e..70bfd109c437d 100644 --- a/compiler/rustc_const_eval/src/lib.rs +++ b/compiler/rustc_const_eval/src/lib.rs @@ -33,6 +33,7 @@ extern crate rustc_middle; pub mod const_eval; mod errors; pub mod interpret; +pub mod might_permit_raw_init; pub mod transform; pub mod util; diff --git a/compiler/rustc_const_eval/src/might_permit_raw_init.rs b/compiler/rustc_const_eval/src/might_permit_raw_init.rs new file mode 100644 index 0000000000000..7a3741c541a1f --- /dev/null +++ b/compiler/rustc_const_eval/src/might_permit_raw_init.rs @@ -0,0 +1,44 @@ +use crate::const_eval::CompileTimeInterpreter; +use crate::interpret::{InterpCx, MemoryKind, OpTy}; +use rustc_middle::ty::layout::LayoutCx; +use rustc_middle::ty::{layout::TyAndLayout, ParamEnv, TyCtxt}; +use rustc_session::Limit; +use rustc_target::abi::InitKind; + +pub fn might_permit_raw_init<'tcx>( + tcx: TyCtxt<'tcx>, + ty: TyAndLayout<'tcx>, + kind: InitKind, +) -> bool { + let strict = tcx.sess.opts.debugging_opts.strict_init_checks; + + if strict { + let machine = CompileTimeInterpreter::new(Limit::new(0), false); + + let mut cx = InterpCx::new(tcx, rustc_span::DUMMY_SP, ParamEnv::reveal_all(), machine); + + // We could panic here... Or we could just return "yeah it's valid whatever". Or let + // codegen_panic_intrinsic return an error that halts compilation. + // I'm not exactly sure *when* this can fail. OOM? + let allocated = cx + .allocate(ty, MemoryKind::Machine(crate::const_eval::MemoryKind::Heap)) + .expect("failed to allocate for uninit check"); + + if kind == InitKind::Zero { + // Again, unclear what to do here if it fails. + cx.write_bytes_ptr( + allocated.ptr, + std::iter::repeat(0_u8).take(ty.layout.size().bytes_usize()), + ) + .expect("failed to write bytes for zero valid check"); + } + + let ot: OpTy<'_, _> = allocated.into(); + + // Assume that if it failed, it's a validation failure. + cx.validate_operand(&ot).is_ok() + } else { + let layout_cx = LayoutCx { tcx, param_env: ParamEnv::reveal_all() }; + ty.might_permit_raw_init(&layout_cx, kind) + } +} diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 9fc2249b29019..ae973e6a56382 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -3132,6 +3132,56 @@ declare_lint! { "detects unexpected names and values in `#[cfg]` conditions", } +declare_lint! { + /// The `repr_transparent_external_private_fields` lint + /// detects types marked #[repr(trasparent)] that (transitively) + /// contain an external ZST type marked #[non_exhaustive] + /// + /// ### Example + /// + /// ```rust,ignore (needs external crate) + /// #![deny(repr_transparent_external_private_fields)] + /// use foo::NonExhaustiveZst; + /// + /// #[repr(transparent)] + /// struct Bar(u32, ([u32; 0], NonExhaustiveZst)); + /// ``` + /// + /// This will produce: + /// + /// ```text + /// error: deprecated `#[macro_use]` attribute used to import macros should be replaced at use sites with a `use` item to import the macro instead + /// --> src/main.rs:3:1 + /// | + /// 3 | #[macro_use] + /// | ^^^^^^^^^^^^ + /// | + /// note: the lint level is defined here + /// --> src/main.rs:1:9 + /// | + /// 1 | #![deny(repr_transparent_external_private_fields)] + /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + /// ``` + /// + /// ### Explanation + /// + /// Previous, Rust accepted fields that contain external private zero-sized types, + /// even though it should not be a breaking change to add a non-zero-sized field to + /// that private type. + /// + /// This is a [future-incompatible] lint to transition this + /// to a hard error in the future. See [issue #78586] for more details. + /// + /// [issue #78586]: https://github.com/rust-lang/rust/issues/78586 + /// [future-incompatible]: ../index.md#future-incompatible-lints + pub REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS, + Warn, + "tranparent type contains an external ZST that is marked #[non_exhaustive] or contains private fields", + @future_incompatible = FutureIncompatibleInfo { + reference: "issue #78586 ", + }; +} + declare_lint_pass! { /// Does nothing as a lint pass, but registers some `Lint`s /// that are used by other parts of the compiler. @@ -3237,6 +3287,7 @@ declare_lint_pass! { DEPRECATED_WHERE_CLAUSE_LOCATION, TEST_UNSTABLE_LINT, FFI_UNWIND_CALLS, + REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS, ] } diff --git a/compiler/rustc_middle/src/ty/generics.rs b/compiler/rustc_middle/src/ty/generics.rs index 84547dca45363..add2df25884e3 100644 --- a/compiler/rustc_middle/src/ty/generics.rs +++ b/compiler/rustc_middle/src/ty/generics.rs @@ -85,10 +85,10 @@ impl GenericParamDef { ) -> Option>> { match self.kind { GenericParamDefKind::Type { has_default, .. } if has_default => { - Some(EarlyBinder(tcx.type_of(self.def_id).into())) + Some(tcx.bound_type_of(self.def_id).map_bound(|t| t.into())) } GenericParamDefKind::Const { has_default } if has_default => { - Some(EarlyBinder(tcx.const_param_default(self.def_id).into())) + Some(tcx.bound_const_param_default(self.def_id).map_bound(|c| c.into())) } _ => None, } diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index 4b51daadabf34..d663f1a3ec6e7 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -932,6 +932,10 @@ impl EarlyBinder { let value = f(self.0)?; Ok(EarlyBinder(value)) } + + pub fn rebind(&self, value: U) -> EarlyBinder { + EarlyBinder(value) + } } impl EarlyBinder> { diff --git a/compiler/rustc_middle/src/ty/util.rs b/compiler/rustc_middle/src/ty/util.rs index 52da6c3a8c03b..4d2f69b23fa09 100644 --- a/compiler/rustc_middle/src/ty/util.rs +++ b/compiler/rustc_middle/src/ty/util.rs @@ -676,6 +676,10 @@ impl<'tcx> TyCtxt<'tcx> { ) -> ty::EarlyBinder<&'tcx ty::List>> { ty::EarlyBinder(self.item_bounds(def_id)) } + + pub fn bound_const_param_default(self, def_id: DefId) -> ty::EarlyBinder> { + ty::EarlyBinder(self.const_param_default(def_id)) + } } struct OpaqueTypeExpander<'tcx> { diff --git a/compiler/rustc_mir_transform/src/shim.rs b/compiler/rustc_mir_transform/src/shim.rs index 3be1783ae3389..f3153a6482048 100644 --- a/compiler/rustc_mir_transform/src/shim.rs +++ b/compiler/rustc_mir_transform/src/shim.rs @@ -537,13 +537,12 @@ fn build_call_shim<'tcx>( }; let def_id = instance.def_id(); - let sig = tcx.fn_sig(def_id); - let mut sig = tcx.erase_late_bound_regions(sig); + let sig = tcx.bound_fn_sig(def_id); + let sig = sig.map_bound(|sig| tcx.erase_late_bound_regions(sig)); assert_eq!(sig_substs.is_some(), !instance.has_polymorphic_mir_body()); - if let Some(sig_substs) = sig_substs { - sig = EarlyBinder(sig).subst(tcx, sig_substs); - } + let mut sig = + if let Some(sig_substs) = sig_substs { sig.subst(tcx, sig_substs) } else { sig.0 }; if let CallKind::Indirect(fnty) = call_kind { // `sig` determines our local decls, and thus the callee type in the `Call` terminator. This diff --git a/compiler/rustc_target/src/abi/mod.rs b/compiler/rustc_target/src/abi/mod.rs index d1eafd6ac5fb8..2c3340ba4448b 100644 --- a/compiler/rustc_target/src/abi/mod.rs +++ b/compiler/rustc_target/src/abi/mod.rs @@ -1372,7 +1372,7 @@ pub struct PointeeInfo { /// Used in `might_permit_raw_init` to indicate the kind of initialisation /// that is checked to be valid -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum InitKind { Zero, Uninit, @@ -1487,14 +1487,18 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { /// /// `init_kind` indicates if the memory is zero-initialized or left uninitialized. /// - /// `strict` is an opt-in debugging flag added in #97323 that enables more checks. + /// This code is intentionally conservative, and will not detect + /// * zero init of an enum whose 0 variant does not allow zero initialization + /// * making uninitialized types who have a full valid range (ints, floats, raw pointers) + /// * Any form of invalid value being made inside an array (unless the value is uninhabited) /// - /// This is conservative: in doubt, it will answer `true`. + /// A strict form of these checks that uses const evaluation exists in + /// [`rustc_const_eval::might_permit_raw_init`], and a tracking issue for making these checks + /// stricter is . /// - /// FIXME: Once we removed all the conservatism, we could alternatively - /// create an all-0/all-undef constant and run the const value validator to see if - /// this is a valid value for the given type. - pub fn might_permit_raw_init(self, cx: &C, init_kind: InitKind, strict: bool) -> bool + /// FIXME: Once all the conservatism is removed from here, and the checks are ran by default, + /// we can use the const evaluation checks always instead. + pub fn might_permit_raw_init(self, cx: &C, init_kind: InitKind) -> bool where Self: Copy, Ty: TyAbiInterface<'a, C>, @@ -1507,13 +1511,8 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { s.valid_range(cx).contains(0) } InitKind::Uninit => { - if strict { - // The type must be allowed to be uninit (which means "is a union"). - s.is_uninit_valid() - } else { - // The range must include all values. - s.is_always_valid(cx) - } + // The range must include all values. + s.is_always_valid(cx) } } }; @@ -1534,19 +1533,12 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { // If we have not found an error yet, we need to recursively descend into fields. match &self.fields { FieldsShape::Primitive | FieldsShape::Union { .. } => {} - FieldsShape::Array { count, .. } => { + FieldsShape::Array { .. } => { // FIXME(#66151): For now, we are conservative and do not check arrays by default. - if strict - && *count > 0 - && !self.field(cx, 0).might_permit_raw_init(cx, init_kind, strict) - { - // Found non empty array with a type that is unhappy about this kind of initialization - return false; - } } FieldsShape::Arbitrary { offsets, .. } => { for idx in 0..offsets.len() { - if !self.field(cx, idx).might_permit_raw_init(cx, init_kind, strict) { + if !self.field(cx, idx).might_permit_raw_init(cx, init_kind) { // We found a field that is unhappy with this kind of initialization. return false; } diff --git a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs index e1131140c39e8..4862631980e36 100644 --- a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs +++ b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs @@ -12,7 +12,7 @@ use rustc_index::bit_set::GrowableBitSet; use rustc_infer::infer::InferOk; use rustc_infer::infer::LateBoundRegionConversionTime::HigherRankedType; use rustc_middle::ty::subst::{GenericArg, GenericArgKind, InternalSubsts, Subst, SubstsRef}; -use rustc_middle::ty::{self, EarlyBinder, GenericParamDefKind, Ty, TyCtxt}; +use rustc_middle::ty::{self, GenericParamDefKind, Ty, TyCtxt}; use rustc_middle::ty::{ToPolyTraitRef, ToPredicate}; use rustc_span::def_id::DefId; @@ -555,7 +555,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { let bound_vars = tcx.mk_bound_variable_kinds(bound_vars.into_iter()); let bound = - EarlyBinder(bound.0.kind().skip_binder()).subst(tcx, assoc_ty_substs); + bound.map_bound(|b| b.kind().skip_binder()).subst(tcx, assoc_ty_substs); tcx.mk_predicate(ty::Binder::bind_with_vars(bound, bound_vars)) }; let normalized_bound = normalize_with_depth_to( diff --git a/compiler/rustc_typeck/src/astconv/mod.rs b/compiler/rustc_typeck/src/astconv/mod.rs index 0a2b54eec47cd..1d4e64b6bfc30 100644 --- a/compiler/rustc_typeck/src/astconv/mod.rs +++ b/compiler/rustc_typeck/src/astconv/mod.rs @@ -550,7 +550,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { GenericParamDefKind::Const { has_default } => { let ty = tcx.at(self.span).type_of(param.def_id); if !infer_args && has_default { - EarlyBinder(tcx.const_param_default(param.def_id)) + tcx.bound_const_param_default(param.def_id) .subst(tcx, substs.unwrap()) .into() } else { diff --git a/compiler/rustc_typeck/src/check/check.rs b/compiler/rustc_typeck/src/check/check.rs index e9709b64d930e..79edbeab9c72e 100644 --- a/compiler/rustc_typeck/src/check/check.rs +++ b/compiler/rustc_typeck/src/check/check.rs @@ -17,6 +17,7 @@ use rustc_infer::infer::outlives::env::OutlivesEnvironment; use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; use rustc_infer::infer::{RegionVariableOrigin, TyCtxtInferExt}; use rustc_infer::traits::Obligation; +use rustc_lint::builtin::REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS; use rustc_middle::hir::nested_filter; use rustc_middle::ty::layout::{LayoutError, MAX_SIMD_LANES}; use rustc_middle::ty::subst::GenericArgKind; @@ -1318,7 +1319,8 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, sp: Span, adt: ty::AdtD } } - // For each field, figure out if it's known to be a ZST and align(1) + // For each field, figure out if it's known to be a ZST and align(1), with "known" + // respecting #[non_exhaustive] attributes. let field_infos = adt.all_fields().map(|field| { let ty = field.ty(tcx, InternalSubsts::identity_for_item(tcx, field.did)); let param_env = tcx.param_env(field.did); @@ -1327,16 +1329,56 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, sp: Span, adt: ty::AdtD let span = tcx.hir().span_if_local(field.did).unwrap(); let zst = layout.map_or(false, |layout| layout.is_zst()); let align1 = layout.map_or(false, |layout| layout.align.abi.bytes() == 1); - (span, zst, align1) + if !zst { + return (span, zst, align1, None); + } + + fn check_non_exhaustive<'tcx>( + tcx: TyCtxt<'tcx>, + t: Ty<'tcx>, + ) -> ControlFlow<(&'static str, DefId, SubstsRef<'tcx>, bool)> { + match t.kind() { + ty::Tuple(list) => list.iter().try_for_each(|t| check_non_exhaustive(tcx, t)), + ty::Array(ty, _) => check_non_exhaustive(tcx, *ty), + ty::Adt(def, subst) => { + if !def.did().is_local() { + let non_exhaustive = def.is_variant_list_non_exhaustive() + || def + .variants() + .iter() + .any(ty::VariantDef::is_field_list_non_exhaustive); + let has_priv = def.all_fields().any(|f| !f.vis.is_public()); + if non_exhaustive || has_priv { + return ControlFlow::Break(( + def.descr(), + def.did(), + subst, + non_exhaustive, + )); + } + } + def.all_fields() + .map(|field| field.ty(tcx, subst)) + .try_for_each(|t| check_non_exhaustive(tcx, t)) + } + _ => ControlFlow::Continue(()), + } + } + + (span, zst, align1, check_non_exhaustive(tcx, ty).break_value()) }); - let non_zst_fields = - field_infos.clone().filter_map(|(span, zst, _align1)| if !zst { Some(span) } else { None }); + let non_zst_fields = field_infos + .clone() + .filter_map(|(span, zst, _align1, _non_exhaustive)| if !zst { Some(span) } else { None }); let non_zst_count = non_zst_fields.clone().count(); if non_zst_count >= 2 { bad_non_zero_sized_fields(tcx, adt, non_zst_count, non_zst_fields, sp); } - for (span, zst, align1) in field_infos { + let incompatible_zst_fields = + field_infos.clone().filter(|(_, _, _, opt)| opt.is_some()).count(); + let incompat = incompatible_zst_fields + non_zst_count >= 2 && non_zst_count < 2; + for (span, zst, align1, non_exhaustive) in field_infos { if zst && !align1 { struct_span_err!( tcx.sess, @@ -1348,6 +1390,25 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, sp: Span, adt: ty::AdtD .span_label(span, "has alignment larger than 1") .emit(); } + if incompat && let Some((descr, def_id, substs, non_exhaustive)) = non_exhaustive { + tcx.struct_span_lint_hir( + REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS, + tcx.hir().local_def_id_to_hir_id(adt.did().expect_local()), + span, + |lint| { + let note = if non_exhaustive { + "is marked with `#[non_exhaustive]`" + } else { + "contains private fields" + }; + let field_ty = tcx.def_path_str_with_substs(def_id, substs); + lint.build("zero-sized fields in repr(transparent) cannot contain external non-exhaustive types") + .note(format!("this {descr} contains `{field_ty}`, which {note}, \ + and makes it not a breaking change to become non-zero-sized in the future.")) + .emit(); + }, + ) + } } } diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs b/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs index cf7de1dc016c8..d15d40bc24756 100644 --- a/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs +++ b/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs @@ -1426,7 +1426,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } GenericParamDefKind::Const { has_default } => { if !infer_args && has_default { - EarlyBinder(tcx.const_param_default(param.def_id)) + tcx.bound_const_param_default(param.def_id) .subst(tcx, substs.unwrap()) .into() } else { diff --git a/compiler/rustc_typeck/src/check/intrinsicck.rs b/compiler/rustc_typeck/src/check/intrinsicck.rs index cc91f2431e076..a5add1e9a8acb 100644 --- a/compiler/rustc_typeck/src/check/intrinsicck.rs +++ b/compiler/rustc_typeck/src/check/intrinsicck.rs @@ -4,7 +4,7 @@ use rustc_errors::struct_span_err; use rustc_hir as hir; use rustc_index::vec::Idx; use rustc_middle::ty::layout::{LayoutError, SizeSkeleton}; -use rustc_middle::ty::{self, Article, FloatTy, InferTy, IntTy, Ty, TyCtxt, TypeVisitable, UintTy}; +use rustc_middle::ty::{self, Article, FloatTy, IntTy, Ty, TyCtxt, TypeVisitable, UintTy}; use rustc_session::lint; use rustc_span::{Span, Symbol, DUMMY_SP}; use rustc_target::abi::{Pointer, VariantIdx}; @@ -99,8 +99,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err.emit(); } + // FIXME(compiler-errors): This could use `<$ty as Pointee>::Metadata == ()` fn is_thin_ptr_ty(&self, ty: Ty<'tcx>) -> bool { - if ty.is_sized(self.tcx.at(DUMMY_SP), self.param_env) { + // Type still may have region variables, but `Sized` does not depend + // on those, so just erase them before querying. + if self.tcx.erase_regions(ty).is_sized(self.tcx.at(DUMMY_SP), self.param_env) { return true; } if let ty::Foreign(..) = ty.kind() { @@ -128,30 +131,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { 64 => InlineAsmType::I64, _ => unreachable!(), }; + + // Expect types to be fully resolved, no const or type variables. + if ty.has_infer_types_or_consts() { + assert!(self.is_tainted_by_errors()); + return None; + } + let asm_ty = match *ty.kind() { // `!` is allowed for input but not for output (issue #87802) ty::Never if is_input => return None, ty::Error(_) => return None, ty::Int(IntTy::I8) | ty::Uint(UintTy::U8) => Some(InlineAsmType::I8), ty::Int(IntTy::I16) | ty::Uint(UintTy::U16) => Some(InlineAsmType::I16), - // Somewhat of a hack: fallback in the presence of errors does not actually - // fall back to i32, but to ty::Error. For integer inference variables this - // means that they don't get any fallback and stay as `{integer}`. - // Since compilation can't succeed anyway, it's fine to use this to avoid printing - // "cannot use value of type `{integer}`", even though that would absolutely - // work due due i32 fallback if the current function had no other errors. - ty::Infer(InferTy::IntVar(_)) => { - assert!(self.is_tainted_by_errors()); - Some(InlineAsmType::I32) - } ty::Int(IntTy::I32) | ty::Uint(UintTy::U32) => Some(InlineAsmType::I32), ty::Int(IntTy::I64) | ty::Uint(UintTy::U64) => Some(InlineAsmType::I64), ty::Int(IntTy::I128) | ty::Uint(UintTy::U128) => Some(InlineAsmType::I128), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize) => Some(asm_ty_isize), - ty::Infer(InferTy::FloatVar(_)) => { - assert!(self.is_tainted_by_errors()); - Some(InlineAsmType::F32) - } ty::Float(FloatTy::F32) => Some(InlineAsmType::F32), ty::Float(FloatTy::F64) => Some(InlineAsmType::F64), ty::FnPtr(_) => Some(asm_ty_isize), @@ -191,6 +187,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { _ => None, } } + ty::Infer(_) => unreachable!(), _ => None, }; let Some(asm_ty) = asm_ty else { @@ -204,11 +201,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return None; }; - if ty.has_infer_types_or_consts() { - assert!(self.is_tainted_by_errors()); - return None; - } - // Check that the type implements Copy. The only case where this can // possibly fail is for SIMD types which don't #[derive(Copy)]. if !self.infcx.type_is_copy_modulo_regions(self.param_env, ty, DUMMY_SP) { diff --git a/compiler/rustc_typeck/src/check/method/probe.rs b/compiler/rustc_typeck/src/check/method/probe.rs index e9b91414a07ab..2de225303560c 100644 --- a/compiler/rustc_typeck/src/check/method/probe.rs +++ b/compiler/rustc_typeck/src/check/method/probe.rs @@ -21,9 +21,7 @@ use rustc_middle::middle::stability; use rustc_middle::ty::fast_reject::{simplify_type, TreatParams}; use rustc_middle::ty::subst::{InternalSubsts, Subst, SubstsRef}; use rustc_middle::ty::GenericParamDefKind; -use rustc_middle::ty::{ - self, EarlyBinder, ParamEnvAnd, ToPredicate, Ty, TyCtxt, TypeFoldable, TypeVisitable, -}; +use rustc_middle::ty::{self, ParamEnvAnd, ToPredicate, Ty, TyCtxt, TypeFoldable, TypeVisitable}; use rustc_session::lint; use rustc_span::def_id::LocalDefId; use rustc_span::lev_distance::{ @@ -713,7 +711,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { } let (impl_ty, impl_substs) = self.impl_ty_and_substs(impl_def_id); - let impl_ty = EarlyBinder(impl_ty).subst(self.tcx, impl_substs); + let impl_ty = impl_ty.subst(self.tcx, impl_substs); debug!("impl_ty: {:?}", impl_ty); @@ -1811,9 +1809,12 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { self.erase_late_bound_regions(xform_fn_sig) } - /// Gets the type of an impl and generate substitutions with placeholders. - fn impl_ty_and_substs(&self, impl_def_id: DefId) -> (Ty<'tcx>, SubstsRef<'tcx>) { - (self.tcx.type_of(impl_def_id), self.fresh_item_substs(impl_def_id)) + /// Gets the type of an impl and generate substitutions with inference vars. + fn impl_ty_and_substs( + &self, + impl_def_id: DefId, + ) -> (ty::EarlyBinder>, SubstsRef<'tcx>) { + (self.tcx.bound_type_of(impl_def_id), self.fresh_item_substs(impl_def_id)) } fn fresh_item_substs(&self, def_id: DefId) -> SubstsRef<'tcx> { diff --git a/compiler/rustc_typeck/src/outlives/explicit.rs b/compiler/rustc_typeck/src/outlives/explicit.rs index bbf31de527eb3..7534482cce9bb 100644 --- a/compiler/rustc_typeck/src/outlives/explicit.rs +++ b/compiler/rustc_typeck/src/outlives/explicit.rs @@ -6,7 +6,7 @@ use super::utils::*; #[derive(Debug)] pub struct ExplicitPredicatesMap<'tcx> { - map: FxHashMap>, + map: FxHashMap>>, } impl<'tcx> ExplicitPredicatesMap<'tcx> { @@ -14,11 +14,11 @@ impl<'tcx> ExplicitPredicatesMap<'tcx> { ExplicitPredicatesMap { map: FxHashMap::default() } } - pub fn explicit_predicates_of( + pub(crate) fn explicit_predicates_of( &mut self, tcx: TyCtxt<'tcx>, def_id: DefId, - ) -> &RequiredPredicates<'tcx> { + ) -> &ty::EarlyBinder> { self.map.entry(def_id).or_insert_with(|| { let predicates = if def_id.is_local() { tcx.explicit_predicates_of(def_id) @@ -63,7 +63,7 @@ impl<'tcx> ExplicitPredicatesMap<'tcx> { } } - required_predicates + ty::EarlyBinder(required_predicates) }) } } diff --git a/compiler/rustc_typeck/src/outlives/implicit_infer.rs b/compiler/rustc_typeck/src/outlives/implicit_infer.rs index 52f9e386441a4..257a9520eeb25 100644 --- a/compiler/rustc_typeck/src/outlives/implicit_infer.rs +++ b/compiler/rustc_typeck/src/outlives/implicit_infer.rs @@ -2,7 +2,7 @@ use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; use rustc_middle::ty::subst::{GenericArg, GenericArgKind, Subst}; -use rustc_middle::ty::{self, EarlyBinder, Ty, TyCtxt}; +use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_span::Span; use super::explicit::ExplicitPredicatesMap; @@ -13,20 +13,19 @@ use super::utils::*; /// `global_inferred_outlives`: this is initially the empty map that /// was generated by walking the items in the crate. This will /// now be filled with inferred predicates. -pub fn infer_predicates<'tcx>( +pub(super) fn infer_predicates<'tcx>( tcx: TyCtxt<'tcx>, - explicit_map: &mut ExplicitPredicatesMap<'tcx>, -) -> FxHashMap> { +) -> FxHashMap>> { debug!("infer_predicates"); - let mut predicates_added = true; + let mut explicit_map = ExplicitPredicatesMap::new(); let mut global_inferred_outlives = FxHashMap::default(); // If new predicates were added then we need to re-calculate // all crates since there could be new implied predicates. - while predicates_added { - predicates_added = false; + 'outer: loop { + let mut predicates_added = false; // Visit all the crates and infer predicates for id in tcx.hir().items() { @@ -53,9 +52,9 @@ pub fn infer_predicates<'tcx>( tcx, field_ty, field_span, - &mut global_inferred_outlives, + &global_inferred_outlives, &mut item_required_predicates, - explicit_map, + &mut explicit_map, ); } } @@ -70,12 +69,17 @@ pub fn infer_predicates<'tcx>( // we walk the crates again and re-calculate predicates for all // items. let item_predicates_len: usize = - global_inferred_outlives.get(&item_did.to_def_id()).map_or(0, |p| p.len()); + global_inferred_outlives.get(&item_did.to_def_id()).map_or(0, |p| p.0.len()); if item_required_predicates.len() > item_predicates_len { predicates_added = true; - global_inferred_outlives.insert(item_did.to_def_id(), item_required_predicates); + global_inferred_outlives + .insert(item_did.to_def_id(), ty::EarlyBinder(item_required_predicates)); } } + + if !predicates_added { + break 'outer; + } } global_inferred_outlives @@ -85,7 +89,7 @@ fn insert_required_predicates_to_be_wf<'tcx>( tcx: TyCtxt<'tcx>, field_ty: Ty<'tcx>, field_span: Span, - global_inferred_outlives: &FxHashMap>, + global_inferred_outlives: &FxHashMap>>, required_predicates: &mut RequiredPredicates<'tcx>, explicit_map: &mut ExplicitPredicatesMap<'tcx>, ) { @@ -133,11 +137,13 @@ fn insert_required_predicates_to_be_wf<'tcx>( // 'a` holds for `Foo`. debug!("Adt"); if let Some(unsubstituted_predicates) = global_inferred_outlives.get(&def.did()) { - for (unsubstituted_predicate, &span) in unsubstituted_predicates { + for (unsubstituted_predicate, &span) in &unsubstituted_predicates.0 { // `unsubstituted_predicate` is `U: 'b` in the // example above. So apply the substitution to // get `T: 'a` (or `predicate`): - let predicate = EarlyBinder(*unsubstituted_predicate).subst(tcx, substs); + let predicate = unsubstituted_predicates + .rebind(*unsubstituted_predicate) + .subst(tcx, substs); insert_outlives_predicate( tcx, predicate.0, @@ -224,7 +230,7 @@ fn insert_required_predicates_to_be_wf<'tcx>( /// will give us `U: 'static` and `U: Foo`. The latter we /// can ignore, but we will want to process `U: 'static`, /// applying the substitution as above. -pub fn check_explicit_predicates<'tcx>( +fn check_explicit_predicates<'tcx>( tcx: TyCtxt<'tcx>, def_id: DefId, substs: &[GenericArg<'tcx>], @@ -242,7 +248,7 @@ pub fn check_explicit_predicates<'tcx>( ); let explicit_predicates = explicit_map.explicit_predicates_of(tcx, def_id); - for (outlives_predicate, &span) in explicit_predicates { + for (outlives_predicate, &span) in &explicit_predicates.0 { debug!("outlives_predicate = {:?}", &outlives_predicate); // Careful: If we are inferring the effects of a `dyn Trait<..>` @@ -287,7 +293,7 @@ pub fn check_explicit_predicates<'tcx>( continue; } - let predicate = EarlyBinder(*outlives_predicate).subst(tcx, substs); + let predicate = explicit_predicates.rebind(*outlives_predicate).subst(tcx, substs); debug!("predicate = {:?}", &predicate); insert_outlives_predicate(tcx, predicate.0, predicate.1, span, required_predicates); } diff --git a/compiler/rustc_typeck/src/outlives/mod.rs b/compiler/rustc_typeck/src/outlives/mod.rs index dccfee19960c5..8fa65d51e3ba1 100644 --- a/compiler/rustc_typeck/src/outlives/mod.rs +++ b/compiler/rustc_typeck/src/outlives/mod.rs @@ -88,9 +88,7 @@ fn inferred_outlives_crate(tcx: TyCtxt<'_>, (): ()) -> CratePredicatesMap<'_> { // for the type. // Compute the inferred predicates - let mut exp_map = explicit::ExplicitPredicatesMap::new(); - - let global_inferred_outlives = implicit_infer::infer_predicates(tcx, &mut exp_map); + let global_inferred_outlives = implicit_infer::infer_predicates(tcx); // Convert the inferred predicates into the "collected" form the // global data structure expects. @@ -100,7 +98,7 @@ fn inferred_outlives_crate(tcx: TyCtxt<'_>, (): ()) -> CratePredicatesMap<'_> { let predicates = global_inferred_outlives .iter() .map(|(&def_id, set)| { - let predicates = &*tcx.arena.alloc_from_iter(set.iter().filter_map( + let predicates = &*tcx.arena.alloc_from_iter(set.0.iter().filter_map( |(ty::OutlivesPredicate(kind1, region2), &span)| { match kind1.unpack() { GenericArgKind::Type(ty1) => Some(( diff --git a/compiler/rustc_typeck/src/outlives/utils.rs b/compiler/rustc_typeck/src/outlives/utils.rs index 14e3048cadc62..b718ca9421336 100644 --- a/compiler/rustc_typeck/src/outlives/utils.rs +++ b/compiler/rustc_typeck/src/outlives/utils.rs @@ -7,12 +7,12 @@ use std::collections::BTreeMap; /// Tracks the `T: 'a` or `'a: 'a` predicates that we have inferred /// must be added to the struct header. -pub type RequiredPredicates<'tcx> = +pub(crate) type RequiredPredicates<'tcx> = BTreeMap, ty::Region<'tcx>>, Span>; /// Given a requirement `T: 'a` or `'b: 'a`, deduce the /// outlives_component and add it to `required_predicates` -pub fn insert_outlives_predicate<'tcx>( +pub(crate) fn insert_outlives_predicate<'tcx>( tcx: TyCtxt<'tcx>, kind: GenericArg<'tcx>, outlived_region: Region<'tcx>, diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 509c4253f0f76..39ec6a6085640 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -726,31 +726,58 @@ fn check_if_attr_is_complete(source: &str, edition: Edition) -> bool { // Empty content so nothing to check in here... return true; } - rustc_span::create_session_if_not_set_then(edition, |_| { - let filename = FileName::anon_source_code(source); - let sess = ParseSess::with_silent_emitter(None); - let mut parser = match maybe_new_parser_from_source_str(&sess, filename, source.to_owned()) - { - Ok(p) => p, - Err(_) => { - debug!("Cannot build a parser to check mod attr so skipping..."); - return true; + rustc_driver::catch_fatal_errors(|| { + rustc_span::create_session_if_not_set_then(edition, |_| { + use rustc_errors::emitter::EmitterWriter; + use rustc_errors::Handler; + use rustc_span::source_map::FilePathMapping; + + let filename = FileName::anon_source_code(source); + // Any errors in parsing should also appear when the doctest is compiled for real, so just + // send all the errors that librustc_ast emits directly into a `Sink` instead of stderr. + let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); + let fallback_bundle = + rustc_errors::fallback_fluent_bundle(rustc_errors::DEFAULT_LOCALE_RESOURCES, false); + + let emitter = EmitterWriter::new( + box io::sink(), + None, + None, + fallback_bundle, + false, + false, + false, + None, + false, + ); + + let handler = Handler::with_emitter(false, None, box emitter); + let sess = ParseSess::with_span_handler(handler, sm); + let mut parser = + match maybe_new_parser_from_source_str(&sess, filename, source.to_owned()) { + Ok(p) => p, + Err(_) => { + debug!("Cannot build a parser to check mod attr so skipping..."); + return true; + } + }; + // If a parsing error happened, it's very likely that the attribute is incomplete. + if let Err(e) = parser.parse_attribute(InnerAttrPolicy::Permitted) { + e.cancel(); + return false; } - }; - // If a parsing error happened, it's very likely that the attribute is incomplete. - if parser.parse_attribute(InnerAttrPolicy::Permitted).is_err() { - return false; - } - // We now check if there is an unclosed delimiter for the attribute. To do so, we look at - // the `unclosed_delims` and see if the opening square bracket was closed. - parser - .unclosed_delims() - .get(0) - .map(|unclosed| { - unclosed.unclosed_span.map(|s| s.lo()).unwrap_or(BytePos(0)) != BytePos(2) - }) - .unwrap_or(true) + // We now check if there is an unclosed delimiter for the attribute. To do so, we look at + // the `unclosed_delims` and see if the opening square bracket was closed. + parser + .unclosed_delims() + .get(0) + .map(|unclosed| { + unclosed.unclosed_span.map(|s| s.lo()).unwrap_or(BytePos(0)) != BytePos(2) + }) + .unwrap_or(true) + }) }) + .unwrap_or(false) } fn partition_source(s: &str, edition: Edition) -> (String, String, String) { diff --git a/src/test/rustdoc-ui/doctest-multiline-crate-attribute.rs b/src/test/rustdoc-ui/doctest-multiline-crate-attribute.rs new file mode 100644 index 0000000000000..a30472ac56b24 --- /dev/null +++ b/src/test/rustdoc-ui/doctest-multiline-crate-attribute.rs @@ -0,0 +1,10 @@ +// compile-flags:--test --test-args=--test-threads=1 +// normalize-stdout-test: "src/test/rustdoc-ui" -> "$$DIR" +// normalize-stdout-test "finished in \d+\.\d+s" -> "finished in $$TIME" +// check-pass + +/// ``` +/// #![deprecated(since = "5.2", note = "foo was rarely used. \ +/// Users should instead use bar")] +/// ``` +pub fn f() {} diff --git a/src/test/rustdoc-ui/doctest-multiline-crate-attribute.stdout b/src/test/rustdoc-ui/doctest-multiline-crate-attribute.stdout new file mode 100644 index 0000000000000..07a4f657dea6a --- /dev/null +++ b/src/test/rustdoc-ui/doctest-multiline-crate-attribute.stdout @@ -0,0 +1,6 @@ + +running 1 test +test $DIR/doctest-multiline-crate-attribute.rs - f (line 6) ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/src/test/ui/asm/issue-99122-2.rs b/src/test/ui/asm/issue-99122-2.rs new file mode 100644 index 0000000000000..87ae837a14864 --- /dev/null +++ b/src/test/ui/asm/issue-99122-2.rs @@ -0,0 +1,18 @@ +// check-pass +// This demonstrates why we need to erase regions before sized check in intrinsicck + +struct NoCopy; + +struct Wrap<'a, T, Tail: ?Sized>(&'a T, Tail); + +pub unsafe fn test() { + let i = NoCopy; + let j = Wrap(&i, ()); + let pointer = &j as *const _; + core::arch::asm!( + "nop", + in("eax") pointer, + ); +} + +fn main() {} diff --git a/src/test/ui/asm/issue-99122.rs b/src/test/ui/asm/issue-99122.rs new file mode 100644 index 0000000000000..588d63069570f --- /dev/null +++ b/src/test/ui/asm/issue-99122.rs @@ -0,0 +1,10 @@ +pub unsafe fn test() { + let pointer = 1u32 as *const _; + //~^ ERROR cannot cast to a pointer of an unknown kind + core::arch::asm!( + "nop", + in("eax") pointer, + ); +} + +fn main() {} diff --git a/src/test/ui/asm/issue-99122.stderr b/src/test/ui/asm/issue-99122.stderr new file mode 100644 index 0000000000000..bef8f34a52537 --- /dev/null +++ b/src/test/ui/asm/issue-99122.stderr @@ -0,0 +1,11 @@ +error[E0641]: cannot cast to a pointer of an unknown kind + --> $DIR/issue-99122.rs:2:27 + | +LL | let pointer = 1u32 as *const _; + | ^^^^^^^^ needs more type information + | + = note: the type information given here is insufficient to check whether the pointer cast is valid + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0641`. diff --git a/src/test/ui/intrinsics/panic-uninitialized-zeroed.rs b/src/test/ui/intrinsics/panic-uninitialized-zeroed.rs index 3ffd35ecdb8da..255151a96032c 100644 --- a/src/test/ui/intrinsics/panic-uninitialized-zeroed.rs +++ b/src/test/ui/intrinsics/panic-uninitialized-zeroed.rs @@ -57,6 +57,13 @@ enum LR_NonZero { struct ZeroSized; +#[allow(dead_code)] +#[repr(i32)] +enum ZeroIsValid { + Zero(u8) = 0, + One(NonNull<()>) = 1, +} + fn test_panic_msg(op: impl (FnOnce() -> T) + panic::UnwindSafe, msg: &str) { let err = panic::catch_unwind(op).err(); assert_eq!( @@ -152,33 +159,12 @@ fn main() { "attempted to zero-initialize type `*const dyn core::marker::Send`, which is invalid" ); - /* FIXME(#66151) we conservatively do not error here yet. - test_panic_msg( - || mem::uninitialized::(), - "attempted to leave type `LR_NonZero` uninitialized, which is invalid" - ); - test_panic_msg( - || mem::zeroed::(), - "attempted to zero-initialize type `LR_NonZero`, which is invalid" - ); - - test_panic_msg( - || mem::uninitialized::>(), - "attempted to leave type `std::mem::ManuallyDrop` uninitialized, \ - which is invalid" - ); - test_panic_msg( - || mem::zeroed::>(), - "attempted to zero-initialize type `std::mem::ManuallyDrop`, \ - which is invalid" - ); - */ - test_panic_msg( || mem::uninitialized::<(NonNull, u32, u32)>(), "attempted to leave type `(core::ptr::non_null::NonNull, u32, u32)` uninitialized, \ which is invalid" ); + test_panic_msg( || mem::zeroed::<(NonNull, u32, u32)>(), "attempted to zero-initialize type `(core::ptr::non_null::NonNull, u32, u32)`, \ @@ -196,11 +182,23 @@ fn main() { which is invalid" ); + test_panic_msg( + || mem::uninitialized::(), + "attempted to leave type `LR_NonZero` uninitialized, which is invalid" + ); + + test_panic_msg( + || mem::uninitialized::>(), + "attempted to leave type `core::mem::manually_drop::ManuallyDrop` uninitialized, \ + which is invalid" + ); + test_panic_msg( || mem::uninitialized::(), "attempted to leave type `NoNullVariant` uninitialized, \ which is invalid" ); + test_panic_msg( || mem::zeroed::(), "attempted to zero-initialize type `NoNullVariant`, \ @@ -212,10 +210,12 @@ fn main() { || mem::uninitialized::(), "attempted to leave type `bool` uninitialized, which is invalid" ); + test_panic_msg( || mem::uninitialized::(), "attempted to leave type `LR` uninitialized, which is invalid" ); + test_panic_msg( || mem::uninitialized::>(), "attempted to leave type `core::mem::manually_drop::ManuallyDrop` uninitialized, which is invalid" @@ -229,6 +229,7 @@ fn main() { let _val = mem::zeroed::>(); let _val = mem::zeroed::>>(); let _val = mem::zeroed::<[!; 0]>(); + let _val = mem::zeroed::(); let _val = mem::uninitialized::>(); let _val = mem::uninitialized::<[!; 0]>(); let _val = mem::uninitialized::<()>(); @@ -259,12 +260,33 @@ fn main() { || mem::zeroed::<[NonNull<()>; 1]>(), "attempted to zero-initialize type `[core::ptr::non_null::NonNull<()>; 1]`, which is invalid" ); + + // FIXME(#66151) we conservatively do not error here yet (by default). + test_panic_msg( + || mem::zeroed::(), + "attempted to zero-initialize type `LR_NonZero`, which is invalid" + ); + + test_panic_msg( + || mem::zeroed::>(), + "attempted to zero-initialize type `core::mem::manually_drop::ManuallyDrop`, \ + which is invalid" + ); } else { // These are UB because they have not been officially blessed, but we await the resolution // of before doing // anything about that. let _val = mem::uninitialized::(); let _val = mem::uninitialized::<*const ()>(); + + // These are UB, but best to test them to ensure we don't become unintentionally + // stricter. + + // It's currently unchecked to create invalid enums and values inside arrays. + let _val = mem::zeroed::(); + let _val = mem::zeroed::<[LR_NonZero; 1]>(); + let _val = mem::zeroed::<[NonNull<()>; 1]>(); + let _val = mem::uninitialized::<[NonNull<()>; 1]>(); } } } diff --git a/src/test/ui/repr/auxiliary/repr-transparent-non-exhaustive.rs b/src/test/ui/repr/auxiliary/repr-transparent-non-exhaustive.rs new file mode 100644 index 0000000000000..4bf6b54fe0787 --- /dev/null +++ b/src/test/ui/repr/auxiliary/repr-transparent-non-exhaustive.rs @@ -0,0 +1,18 @@ +#![crate_type = "lib"] + +pub struct Private { _priv: () } + +#[non_exhaustive] +pub struct NonExhaustive {} + +#[non_exhaustive] +pub enum NonExhaustiveEnum {} + +pub enum NonExhaustiveVariant { + #[non_exhaustive] + A, +} + +pub struct ExternalIndirection { + pub x: T, +} diff --git a/src/test/ui/repr/repr-transparent-non-exhaustive.rs b/src/test/ui/repr/repr-transparent-non-exhaustive.rs new file mode 100644 index 0000000000000..9ccd8610dad47 --- /dev/null +++ b/src/test/ui/repr/repr-transparent-non-exhaustive.rs @@ -0,0 +1,96 @@ +#![deny(repr_transparent_external_private_fields)] + +// aux-build: repr-transparent-non-exhaustive.rs +extern crate repr_transparent_non_exhaustive; + +use repr_transparent_non_exhaustive::{ + Private, + NonExhaustive, + NonExhaustiveEnum, + NonExhaustiveVariant, + ExternalIndirection, +}; + +pub struct InternalPrivate { + _priv: (), +} + +#[non_exhaustive] +pub struct InternalNonExhaustive; + +pub struct InternalIndirection { + x: T, +} + +pub type Sized = i32; + +#[repr(transparent)] +pub struct T1(Sized, InternalPrivate); +#[repr(transparent)] +pub struct T2(Sized, InternalNonExhaustive); +#[repr(transparent)] +pub struct T3(Sized, InternalIndirection<(InternalPrivate, InternalNonExhaustive)>); +#[repr(transparent)] +pub struct T4(Sized, ExternalIndirection<(InternalPrivate, InternalNonExhaustive)>); + +#[repr(transparent)] +pub struct T5(Sized, Private); +//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types +//~| WARN this was previously accepted by the compiler + +#[repr(transparent)] +pub struct T6(Sized, NonExhaustive); +//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types +//~| WARN this was previously accepted by the compiler + +#[repr(transparent)] +pub struct T7(Sized, NonExhaustiveEnum); +//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types +//~| WARN this was previously accepted by the compiler + +#[repr(transparent)] +pub struct T8(Sized, NonExhaustiveVariant); +//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types +//~| WARN this was previously accepted by the compiler + +#[repr(transparent)] +pub struct T9(Sized, InternalIndirection); +//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types +//~| WARN this was previously accepted by the compiler + +#[repr(transparent)] +pub struct T10(Sized, InternalIndirection); +//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types +//~| WARN this was previously accepted by the compiler + +#[repr(transparent)] +pub struct T11(Sized, InternalIndirection); +//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types +//~| WARN this was previously accepted by the compiler + +#[repr(transparent)] +pub struct T12(Sized, InternalIndirection); +//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types +//~| WARN this was previously accepted by the compiler + +#[repr(transparent)] +pub struct T13(Sized, ExternalIndirection); +//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types +//~| WARN this was previously accepted by the compiler + +#[repr(transparent)] +pub struct T14(Sized, ExternalIndirection); +//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types +//~| WARN this was previously accepted by the compiler + +#[repr(transparent)] +pub struct T15(Sized, ExternalIndirection); +//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types +//~| WARN this was previously accepted by the compiler + +#[repr(transparent)] +pub struct T16(Sized, ExternalIndirection); +//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types +//~| WARN this was previously accepted by the compiler + +fn main() {} diff --git a/src/test/ui/repr/repr-transparent-non-exhaustive.stderr b/src/test/ui/repr/repr-transparent-non-exhaustive.stderr new file mode 100644 index 0000000000000..3b1e334a0cbe2 --- /dev/null +++ b/src/test/ui/repr/repr-transparent-non-exhaustive.stderr @@ -0,0 +1,127 @@ +error: zero-sized fields in repr(transparent) cannot contain external non-exhaustive types + --> $DIR/repr-transparent-non-exhaustive.rs:37:22 + | +LL | pub struct T5(Sized, Private); + | ^^^^^^^ + | +note: the lint level is defined here + --> $DIR/repr-transparent-non-exhaustive.rs:1:9 + | +LL | #![deny(repr_transparent_external_private_fields)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #78586 + = note: this struct contains `Private`, which contains private fields, and makes it not a breaking change to become non-zero-sized in the future. + +error: zero-sized fields in repr(transparent) cannot contain external non-exhaustive types + --> $DIR/repr-transparent-non-exhaustive.rs:42:22 + | +LL | pub struct T6(Sized, NonExhaustive); + | ^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #78586 + = note: this struct contains `NonExhaustive`, which is marked with `#[non_exhaustive]`, and makes it not a breaking change to become non-zero-sized in the future. + +error: zero-sized fields in repr(transparent) cannot contain external non-exhaustive types + --> $DIR/repr-transparent-non-exhaustive.rs:47:22 + | +LL | pub struct T7(Sized, NonExhaustiveEnum); + | ^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #78586 + = note: this enum contains `NonExhaustiveEnum`, which is marked with `#[non_exhaustive]`, and makes it not a breaking change to become non-zero-sized in the future. + +error: zero-sized fields in repr(transparent) cannot contain external non-exhaustive types + --> $DIR/repr-transparent-non-exhaustive.rs:52:22 + | +LL | pub struct T8(Sized, NonExhaustiveVariant); + | ^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #78586 + = note: this enum contains `NonExhaustiveVariant`, which is marked with `#[non_exhaustive]`, and makes it not a breaking change to become non-zero-sized in the future. + +error: zero-sized fields in repr(transparent) cannot contain external non-exhaustive types + --> $DIR/repr-transparent-non-exhaustive.rs:57:22 + | +LL | pub struct T9(Sized, InternalIndirection); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #78586 + = note: this struct contains `Private`, which contains private fields, and makes it not a breaking change to become non-zero-sized in the future. + +error: zero-sized fields in repr(transparent) cannot contain external non-exhaustive types + --> $DIR/repr-transparent-non-exhaustive.rs:62:23 + | +LL | pub struct T10(Sized, InternalIndirection); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #78586 + = note: this struct contains `NonExhaustive`, which is marked with `#[non_exhaustive]`, and makes it not a breaking change to become non-zero-sized in the future. + +error: zero-sized fields in repr(transparent) cannot contain external non-exhaustive types + --> $DIR/repr-transparent-non-exhaustive.rs:67:23 + | +LL | pub struct T11(Sized, InternalIndirection); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #78586 + = note: this enum contains `NonExhaustiveEnum`, which is marked with `#[non_exhaustive]`, and makes it not a breaking change to become non-zero-sized in the future. + +error: zero-sized fields in repr(transparent) cannot contain external non-exhaustive types + --> $DIR/repr-transparent-non-exhaustive.rs:72:23 + | +LL | pub struct T12(Sized, InternalIndirection); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #78586 + = note: this enum contains `NonExhaustiveVariant`, which is marked with `#[non_exhaustive]`, and makes it not a breaking change to become non-zero-sized in the future. + +error: zero-sized fields in repr(transparent) cannot contain external non-exhaustive types + --> $DIR/repr-transparent-non-exhaustive.rs:77:23 + | +LL | pub struct T13(Sized, ExternalIndirection); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #78586 + = note: this struct contains `Private`, which contains private fields, and makes it not a breaking change to become non-zero-sized in the future. + +error: zero-sized fields in repr(transparent) cannot contain external non-exhaustive types + --> $DIR/repr-transparent-non-exhaustive.rs:82:23 + | +LL | pub struct T14(Sized, ExternalIndirection); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #78586 + = note: this struct contains `NonExhaustive`, which is marked with `#[non_exhaustive]`, and makes it not a breaking change to become non-zero-sized in the future. + +error: zero-sized fields in repr(transparent) cannot contain external non-exhaustive types + --> $DIR/repr-transparent-non-exhaustive.rs:87:23 + | +LL | pub struct T15(Sized, ExternalIndirection); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #78586 + = note: this enum contains `NonExhaustiveEnum`, which is marked with `#[non_exhaustive]`, and makes it not a breaking change to become non-zero-sized in the future. + +error: zero-sized fields in repr(transparent) cannot contain external non-exhaustive types + --> $DIR/repr-transparent-non-exhaustive.rs:92:23 + | +LL | pub struct T16(Sized, ExternalIndirection); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #78586 + = note: this enum contains `NonExhaustiveVariant`, which is marked with `#[non_exhaustive]`, and makes it not a breaking change to become non-zero-sized in the future. + +error: aborting due to 12 previous errors +