From 5f593da4e6976fe27f7cd31ce0fb9a9293b3a00b Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Sun, 26 Feb 2023 21:50:19 +0000 Subject: Unify all validity check intrinsics Also merges the inhabitedness check into the query to further unify the code paths. --- .../rustc_const_eval/src/interpret/intrinsics.rs | 74 ++++------ compiler/rustc_const_eval/src/lib.rs | 4 +- .../src/util/check_validity_requirement.rs | 161 +++++++++++++++++++++ .../src/util/might_permit_raw_init.rs | 151 ------------------- compiler/rustc_const_eval/src/util/mod.rs | 4 +- 5 files changed, 191 insertions(+), 203 deletions(-) create mode 100644 compiler/rustc_const_eval/src/util/check_validity_requirement.rs delete mode 100644 compiler/rustc_const_eval/src/util/might_permit_raw_init.rs (limited to 'compiler/rustc_const_eval') diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 26c84b4ce61..c65d677e8ea 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -11,7 +11,7 @@ use rustc_middle::mir::{ BinOp, NonDivergingIntrinsic, }; use rustc_middle::ty; -use rustc_middle::ty::layout::{InitKind, LayoutOf as _}; +use rustc_middle::ty::layout::{LayoutOf as _, ValidityRequirement}; use rustc_middle::ty::subst::SubstsRef; use rustc_middle::ty::{Ty, TyCtxt}; use rustc_span::symbol::{sym, Symbol}; @@ -418,57 +418,35 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { | sym::assert_zero_valid | sym::assert_mem_uninitialized_valid => { let ty = instance.substs.type_at(0); - let layout = self.layout_of(ty)?; - - // For *all* intrinsics we first check `is_uninhabited` to give a more specific - // error message. - if layout.abi.is_uninhabited() { - // The run-time intrinsic panics just to get a good backtrace; here we abort - // since there is no problem showing a backtrace even for aborts. - M::abort( - self, - format!( + let requirement = ValidityRequirement::from_intrinsic(intrinsic_name).unwrap(); + + let should_panic = !self + .tcx + .check_validity_requirement((requirement, self.param_env.and(ty))) + .map_err(|_| err_inval!(TooGeneric))?; + + if should_panic { + let layout = self.layout_of(ty)?; + + let msg = match requirement { + // For *all* intrinsics we first check `is_uninhabited` to give a more specific + // error message. + _ if layout.abi.is_uninhabited() => format!( "aborted execution: attempted to instantiate uninhabited type `{}`", ty ), - )?; - } - - if intrinsic_name == sym::assert_zero_valid { - let should_panic = !self - .tcx - .check_validity_of_init((InitKind::Zero, self.param_env.and(ty))) - .map_err(|_| err_inval!(TooGeneric))?; - - if should_panic { - M::abort( - self, - format!( - "aborted execution: attempted to zero-initialize type `{}`, which is invalid", - ty - ), - )?; - } - } + ValidityRequirement::Inhabited => bug!("handled earlier"), + ValidityRequirement::Zero => format!( + "aborted execution: attempted to zero-initialize type `{}`, which is invalid", + ty + ), + ValidityRequirement::UninitMitigated0x01Fill => format!( + "aborted execution: attempted to leave type `{}` uninitialized, which is invalid", + ty + ), + }; - if intrinsic_name == sym::assert_mem_uninitialized_valid { - let should_panic = !self - .tcx - .check_validity_of_init(( - InitKind::UninitMitigated0x01Fill, - self.param_env.and(ty), - )) - .map_err(|_| err_inval!(TooGeneric))?; - - if should_panic { - M::abort( - self, - format!( - "aborted execution: attempted to leave type `{}` uninitialized, which is invalid", - ty - ), - )?; - } + M::abort(self, msg)?; } } sym::simd_insert => { diff --git a/compiler/rustc_const_eval/src/lib.rs b/compiler/rustc_const_eval/src/lib.rs index 092a7dc3d3b..ed9efe568fb 100644 --- a/compiler/rustc_const_eval/src/lib.rs +++ b/compiler/rustc_const_eval/src/lib.rs @@ -61,7 +61,7 @@ pub fn provide(providers: &mut Providers) { let (param_env, value) = param_env_and_value.into_parts(); const_eval::deref_mir_constant(tcx, param_env, value) }; - providers.check_validity_of_init = |tcx, (init_kind, param_env_and_ty)| { - util::might_permit_raw_init(tcx, init_kind, param_env_and_ty) + providers.check_validity_requirement = |tcx, (init_kind, param_env_and_ty)| { + util::check_validity_requirement(tcx, init_kind, param_env_and_ty) }; } diff --git a/compiler/rustc_const_eval/src/util/check_validity_requirement.rs b/compiler/rustc_const_eval/src/util/check_validity_requirement.rs new file mode 100644 index 00000000000..dcd15b919f4 --- /dev/null +++ b/compiler/rustc_const_eval/src/util/check_validity_requirement.rs @@ -0,0 +1,161 @@ +use rustc_middle::ty::layout::{LayoutCx, LayoutError, LayoutOf, TyAndLayout, ValidityRequirement}; +use rustc_middle::ty::{ParamEnv, ParamEnvAnd, Ty, TyCtxt}; +use rustc_session::Limit; +use rustc_target::abi::{Abi, FieldsShape, Scalar, Variants}; + +use crate::const_eval::{CheckAlignment, CompileTimeInterpreter}; +use crate::interpret::{InterpCx, MemoryKind, OpTy}; + +/// Determines if this type permits "raw" initialization by just transmuting some memory into an +/// instance of `T`. +/// +/// `init_kind` indicates if the memory is zero-initialized or left uninitialized. We assume +/// uninitialized memory is mitigated by filling it with 0x01, which reduces the chance of causing +/// LLVM UB. +/// +/// By default we check whether that operation would cause *LLVM UB*, i.e., whether the LLVM IR we +/// generate has UB or not. This is a mitigation strategy, which is why we are okay with accepting +/// Rust UB as long as there is no risk of miscompilations. The `strict_init_checks` can be set to +/// do a full check against Rust UB instead (in which case we will also ignore the 0x01-filling and +/// to the full uninit check). +pub fn check_validity_requirement<'tcx>( + tcx: TyCtxt<'tcx>, + kind: ValidityRequirement, + param_env_and_ty: ParamEnvAnd<'tcx, Ty<'tcx>>, +) -> Result> { + let layout = tcx.layout_of(param_env_and_ty)?; + + // There is nothing strict or lax about inhabitedness. + if kind == ValidityRequirement::Inhabited { + return Ok(!layout.abi.is_uninhabited()); + } + + if tcx.sess.opts.unstable_opts.strict_init_checks { + might_permit_raw_init_strict(layout, tcx, kind) + } else { + let layout_cx = LayoutCx { tcx, param_env: param_env_and_ty.param_env }; + might_permit_raw_init_lax(layout, &layout_cx, kind) + } +} + +/// Implements the 'strict' version of the `might_permit_raw_init` checks; see that function for +/// details. +fn might_permit_raw_init_strict<'tcx>( + ty: TyAndLayout<'tcx>, + tcx: TyCtxt<'tcx>, + kind: ValidityRequirement, +) -> Result> { + let machine = CompileTimeInterpreter::new( + Limit::new(0), + /*can_access_statics:*/ false, + CheckAlignment::Error, + ); + + let mut cx = InterpCx::new(tcx, rustc_span::DUMMY_SP, ParamEnv::reveal_all(), machine); + + let allocated = cx + .allocate(ty, MemoryKind::Machine(crate::const_eval::MemoryKind::Heap)) + .expect("OOM: failed to allocate for uninit check"); + + if kind == ValidityRequirement::Zero { + 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. + // This does *not* actually check that references are dereferenceable, but since all types that + // require dereferenceability also require non-null, we don't actually get any false negatives + // due to this. + Ok(cx.validate_operand(&ot).is_ok()) +} + +/// Implements the 'lax' (default) version of the `might_permit_raw_init` checks; see that function for +/// details. +fn might_permit_raw_init_lax<'tcx>( + this: TyAndLayout<'tcx>, + cx: &LayoutCx<'tcx, TyCtxt<'tcx>>, + init_kind: ValidityRequirement, +) -> Result> { + let scalar_allows_raw_init = move |s: Scalar| -> bool { + match init_kind { + ValidityRequirement::Inhabited => { + bug!("ValidityRequirement::Inhabited should have been handled above") + } + ValidityRequirement::Zero => { + // The range must contain 0. + s.valid_range(cx).contains(0) + } + ValidityRequirement::UninitMitigated0x01Fill => { + // The range must include an 0x01-filled buffer. + let mut val: u128 = 0x01; + for _ in 1..s.size(cx).bytes() { + // For sizes >1, repeat the 0x01. + val = (val << 8) | 0x01; + } + s.valid_range(cx).contains(val) + } + } + }; + + // Check the ABI. + let valid = match this.abi { + Abi::Uninhabited => false, // definitely UB + Abi::Scalar(s) => scalar_allows_raw_init(s), + Abi::ScalarPair(s1, s2) => scalar_allows_raw_init(s1) && scalar_allows_raw_init(s2), + Abi::Vector { element: s, count } => count == 0 || scalar_allows_raw_init(s), + Abi::Aggregate { .. } => true, // Fields are checked below. + }; + if !valid { + // This is definitely not okay. + return Ok(false); + } + + // Special magic check for references and boxes (i.e., special pointer types). + if let Some(pointee) = this.ty.builtin_deref(false) { + let pointee = cx.layout_of(pointee.ty)?; + // We need to ensure that the LLVM attributes `aligned` and `dereferenceable(size)` are satisfied. + if pointee.align.abi.bytes() > 1 { + // 0x01-filling is not aligned. + return Ok(false); + } + if pointee.size.bytes() > 0 { + // A 'fake' integer pointer is not sufficiently dereferenceable. + return Ok(false); + } + } + + // If we have not found an error yet, we need to recursively descend into fields. + match &this.fields { + FieldsShape::Primitive | FieldsShape::Union { .. } => {} + FieldsShape::Array { .. } => { + // Arrays never have scalar layout in LLVM, so if the array is not actually + // accessed, there is no LLVM UB -- therefore we can skip this. + } + FieldsShape::Arbitrary { offsets, .. } => { + for idx in 0..offsets.len() { + if !might_permit_raw_init_lax(this.field(cx, idx), cx, init_kind)? { + // We found a field that is unhappy with this kind of initialization. + return Ok(false); + } + } + } + } + + match &this.variants { + Variants::Single { .. } => { + // All fields of this single variant have already been checked above, there is nothing + // else to do. + } + Variants::Multiple { .. } => { + // We cannot tell LLVM anything about the details of this multi-variant layout, so + // invalid values "hidden" inside the variant cannot cause LLVM trouble. + } + } + + Ok(true) +} diff --git a/compiler/rustc_const_eval/src/util/might_permit_raw_init.rs b/compiler/rustc_const_eval/src/util/might_permit_raw_init.rs deleted file mode 100644 index a78bf927ca1..00000000000 --- a/compiler/rustc_const_eval/src/util/might_permit_raw_init.rs +++ /dev/null @@ -1,151 +0,0 @@ -use rustc_middle::ty::layout::{InitKind, LayoutCx, LayoutError, LayoutOf, TyAndLayout}; -use rustc_middle::ty::{ParamEnv, ParamEnvAnd, Ty, TyCtxt}; -use rustc_session::Limit; -use rustc_target::abi::{Abi, FieldsShape, Scalar, Variants}; - -use crate::const_eval::{CheckAlignment, CompileTimeInterpreter}; -use crate::interpret::{InterpCx, MemoryKind, OpTy}; - -/// Determines if this type permits "raw" initialization by just transmuting some memory into an -/// instance of `T`. -/// -/// `init_kind` indicates if the memory is zero-initialized or left uninitialized. We assume -/// uninitialized memory is mitigated by filling it with 0x01, which reduces the chance of causing -/// LLVM UB. -/// -/// By default we check whether that operation would cause *LLVM UB*, i.e., whether the LLVM IR we -/// generate has UB or not. This is a mitigation strategy, which is why we are okay with accepting -/// Rust UB as long as there is no risk of miscompilations. The `strict_init_checks` can be set to -/// do a full check against Rust UB instead (in which case we will also ignore the 0x01-filling and -/// to the full uninit check). -pub fn might_permit_raw_init<'tcx>( - tcx: TyCtxt<'tcx>, - kind: InitKind, - param_env_and_ty: ParamEnvAnd<'tcx, Ty<'tcx>>, -) -> Result> { - if tcx.sess.opts.unstable_opts.strict_init_checks { - might_permit_raw_init_strict(tcx.layout_of(param_env_and_ty)?, tcx, kind) - } else { - let layout_cx = LayoutCx { tcx, param_env: param_env_and_ty.param_env }; - might_permit_raw_init_lax(tcx.layout_of(param_env_and_ty)?, &layout_cx, kind) - } -} - -/// Implements the 'strict' version of the `might_permit_raw_init` checks; see that function for -/// details. -fn might_permit_raw_init_strict<'tcx>( - ty: TyAndLayout<'tcx>, - tcx: TyCtxt<'tcx>, - kind: InitKind, -) -> Result> { - let machine = CompileTimeInterpreter::new( - Limit::new(0), - /*can_access_statics:*/ false, - CheckAlignment::Error, - ); - - let mut cx = InterpCx::new(tcx, rustc_span::DUMMY_SP, ParamEnv::reveal_all(), machine); - - let allocated = cx - .allocate(ty, MemoryKind::Machine(crate::const_eval::MemoryKind::Heap)) - .expect("OOM: failed to allocate for uninit check"); - - if kind == InitKind::Zero { - 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. - // This does *not* actually check that references are dereferenceable, but since all types that - // require dereferenceability also require non-null, we don't actually get any false negatives - // due to this. - Ok(cx.validate_operand(&ot).is_ok()) -} - -/// Implements the 'lax' (default) version of the `might_permit_raw_init` checks; see that function for -/// details. -fn might_permit_raw_init_lax<'tcx>( - this: TyAndLayout<'tcx>, - cx: &LayoutCx<'tcx, TyCtxt<'tcx>>, - init_kind: InitKind, -) -> Result> { - let scalar_allows_raw_init = move |s: Scalar| -> bool { - match init_kind { - InitKind::Zero => { - // The range must contain 0. - s.valid_range(cx).contains(0) - } - InitKind::UninitMitigated0x01Fill => { - // The range must include an 0x01-filled buffer. - let mut val: u128 = 0x01; - for _ in 1..s.size(cx).bytes() { - // For sizes >1, repeat the 0x01. - val = (val << 8) | 0x01; - } - s.valid_range(cx).contains(val) - } - } - }; - - // Check the ABI. - let valid = match this.abi { - Abi::Uninhabited => false, // definitely UB - Abi::Scalar(s) => scalar_allows_raw_init(s), - Abi::ScalarPair(s1, s2) => scalar_allows_raw_init(s1) && scalar_allows_raw_init(s2), - Abi::Vector { element: s, count } => count == 0 || scalar_allows_raw_init(s), - Abi::Aggregate { .. } => true, // Fields are checked below. - }; - if !valid { - // This is definitely not okay. - return Ok(false); - } - - // Special magic check for references and boxes (i.e., special pointer types). - if let Some(pointee) = this.ty.builtin_deref(false) { - let pointee = cx.layout_of(pointee.ty)?; - // We need to ensure that the LLVM attributes `aligned` and `dereferenceable(size)` are satisfied. - if pointee.align.abi.bytes() > 1 { - // 0x01-filling is not aligned. - return Ok(false); - } - if pointee.size.bytes() > 0 { - // A 'fake' integer pointer is not sufficiently dereferenceable. - return Ok(false); - } - } - - // If we have not found an error yet, we need to recursively descend into fields. - match &this.fields { - FieldsShape::Primitive | FieldsShape::Union { .. } => {} - FieldsShape::Array { .. } => { - // Arrays never have scalar layout in LLVM, so if the array is not actually - // accessed, there is no LLVM UB -- therefore we can skip this. - } - FieldsShape::Arbitrary { offsets, .. } => { - for idx in 0..offsets.len() { - if !might_permit_raw_init_lax(this.field(cx, idx), cx, init_kind)? { - // We found a field that is unhappy with this kind of initialization. - return Ok(false); - } - } - } - } - - match &this.variants { - Variants::Single { .. } => { - // All fields of this single variant have already been checked above, there is nothing - // else to do. - } - Variants::Multiple { .. } => { - // We cannot tell LLVM anything about the details of this multi-variant layout, so - // invalid values "hidden" inside the variant cannot cause LLVM trouble. - } - } - - Ok(true) -} diff --git a/compiler/rustc_const_eval/src/util/mod.rs b/compiler/rustc_const_eval/src/util/mod.rs index 51735e33e0f..c0aabd77cee 100644 --- a/compiler/rustc_const_eval/src/util/mod.rs +++ b/compiler/rustc_const_eval/src/util/mod.rs @@ -1,14 +1,14 @@ mod alignment; mod call_kind; +mod check_validity_requirement; pub mod collect_writes; mod compare_types; mod find_self_call; -mod might_permit_raw_init; mod type_name; pub use self::alignment::is_disaligned; pub use self::call_kind::{call_kind, CallDesugaringKind, CallKind}; +pub use self::check_validity_requirement::check_validity_requirement; pub use self::compare_types::{is_equal_up_to_subtyping, is_subtype}; pub use self::find_self_call::find_self_call; -pub use self::might_permit_raw_init::might_permit_raw_init; pub use self::type_name::type_name; -- cgit 1.4.1-3-g733a5