diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl index e7e8b2b36006d..9155bae263106 100644 --- a/compiler/rustc_const_eval/messages.ftl +++ b/compiler/rustc_const_eval/messages.ftl @@ -170,6 +170,10 @@ const_eval_invalid_meta = invalid metadata in wide pointer: total size is bigger than largest supported object const_eval_invalid_meta_slice = invalid metadata in wide pointer: slice is bigger than largest supported object + +const_eval_invalid_niched_enum_variant_written = + trying to set discriminant of a {$ty} to the niched variant, but the value does not match + const_eval_invalid_str = this string is not valid UTF-8: {$err} const_eval_invalid_tag = diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index 4d2b1ba3eec89..c8fdb06d29158 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -509,6 +509,9 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { ScalarSizeMismatch(_) => const_eval_scalar_size_mismatch, UninhabitedEnumVariantWritten(_) => const_eval_uninhabited_enum_variant_written, UninhabitedEnumVariantRead(_) => const_eval_uninhabited_enum_variant_read, + InvalidNichedEnumVariantWritten { .. } => { + const_eval_invalid_niched_enum_variant_written + } AbiMismatchArgument { .. } => const_eval_incompatible_types, AbiMismatchReturn { .. } => const_eval_incompatible_return_types, } @@ -597,6 +600,9 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { builder.arg("target_size", info.target_size); builder.arg("data_size", info.data_size); } + InvalidNichedEnumVariantWritten { enum_ty } => { + builder.arg("ty", enum_ty.to_string()); + } AbiMismatchArgument { caller_ty, callee_ty } | AbiMismatchReturn { caller_ty, callee_ty } => { builder.arg("caller_ty", caller_ty.to_string()); diff --git a/compiler/rustc_const_eval/src/interpret/discriminant.rs b/compiler/rustc_const_eval/src/interpret/discriminant.rs index bb8c17cf7791d..e951a77611c21 100644 --- a/compiler/rustc_const_eval/src/interpret/discriminant.rs +++ b/compiler/rustc_const_eval/src/interpret/discriminant.rs @@ -85,6 +85,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // Write result. let niche_dest = self.project_field(dest, tag_field)?; self.write_immediate(*tag_val, &niche_dest)?; + } else { + // The untagged variant is implicitly encoded simply by having a value that is + // outside the niche variants. But what if the data stored here does not + // actually encode this variant? That would be bad! So let's double-check... + let actual_variant = self.read_discriminant(&dest.to_op(self)?)?; + if actual_variant != variant_index { + throw_ub!(InvalidNichedEnumVariantWritten { enum_ty: dest.layout().ty }); + } } } } diff --git a/compiler/rustc_middle/src/mir/interpret/error.rs b/compiler/rustc_middle/src/mir/interpret/error.rs index 0f69ab93452f7..4f915951163e3 100644 --- a/compiler/rustc_middle/src/mir/interpret/error.rs +++ b/compiler/rustc_middle/src/mir/interpret/error.rs @@ -356,6 +356,8 @@ pub enum UndefinedBehaviorInfo<'tcx> { UninhabitedEnumVariantWritten(VariantIdx), /// An uninhabited enum variant is projected. UninhabitedEnumVariantRead(VariantIdx), + /// Trying to set discriminant to the niched variant, but the value does not match. + InvalidNichedEnumVariantWritten { enum_ty: Ty<'tcx> }, /// ABI-incompatible argument types. AbiMismatchArgument { caller_ty: Ty<'tcx>, callee_ty: Ty<'tcx> }, /// ABI-incompatible return types. diff --git a/src/tools/miri/tests/fail/enum-set-discriminant-niche-variant-wrong.rs b/src/tools/miri/tests/fail/enum-set-discriminant-niche-variant-wrong.rs new file mode 100644 index 0000000000000..7097aa0c43ae3 --- /dev/null +++ b/src/tools/miri/tests/fail/enum-set-discriminant-niche-variant-wrong.rs @@ -0,0 +1,31 @@ +#![feature(core_intrinsics)] +#![feature(custom_mir)] + +use std::intrinsics::mir::*; +use std::num::NonZeroI32; + +// We define our own option type so that we can control the varian indices. +#[allow(unused)] +enum Option { + None, + Some(T), +} +use Option::*; + +#[custom_mir(dialect = "runtime", phase = "optimized")] +fn set_discriminant(ptr: &mut Option) { + mir! { + { + // We set the discriminant to `Some`, which is a NOP since this is the niched variant. + // However, the enum is actually encoding `None` currently! That's not good... + SetDiscriminant(*ptr, 1); + //~^ ERROR: trying to set discriminant of a Option> to the niched variant, but the value does not match + Return() + } + } +} + +pub fn main() { + let mut v = None; + set_discriminant(&mut v); +} diff --git a/src/tools/miri/tests/fail/enum-set-discriminant-niche-variant-wrong.stderr b/src/tools/miri/tests/fail/enum-set-discriminant-niche-variant-wrong.stderr new file mode 100644 index 0000000000000..a48a0a993daa1 --- /dev/null +++ b/src/tools/miri/tests/fail/enum-set-discriminant-niche-variant-wrong.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: trying to set discriminant of a Option> to the niched variant, but the value does not match + --> $DIR/enum-set-discriminant-niche-variant-wrong.rs:LL:CC + | +LL | SetDiscriminant(*ptr, 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^ trying to set discriminant of a Option> to the niched variant, but the value does not match + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `set_discriminant` at $DIR/enum-set-discriminant-niche-variant-wrong.rs:LL:CC +note: inside `main` + --> $DIR/enum-set-discriminant-niche-variant-wrong.rs:LL:CC + | +LL | set_discriminant(&mut v); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error +