diff --git a/compiler/rustc_const_eval/src/const_eval/mod.rs b/compiler/rustc_const_eval/src/const_eval/mod.rs index eaa333fd8a965..ba1d5f45bbb10 100644 --- a/compiler/rustc_const_eval/src/const_eval/mod.rs +++ b/compiler/rustc_const_eval/src/const_eval/mod.rs @@ -11,7 +11,8 @@ use rustc_middle::{ use rustc_span::{source_map::DUMMY_SP, symbol::Symbol}; use crate::interpret::{ - intern_const_alloc_recursive, ConstValue, InternKind, InterpCx, MPlaceTy, MemPlaceMeta, Scalar, + intern_const_alloc_recursive, ConstValue, InternKind, InterpCx, InterpResult, MPlaceTy, + MemPlaceMeta, Scalar, }; mod error; @@ -132,42 +133,39 @@ fn const_to_valtree_inner<'tcx>( } } -/// This function uses `unwrap` copiously, because an already validated constant -/// must have valid fields and can thus never fail outside of compiler bugs. However, it is -/// invoked from the pretty printer, where it can receive enums with no variants and e.g. -/// `read_discriminant` needs to be able to handle that. -pub(crate) fn destructure_const<'tcx>( +/// This function should never fail for validated constants. However, it is also invoked from the +/// pretty printer which might attempt to format invalid constants and in that case it might fail. +pub(crate) fn try_destructure_const<'tcx>( tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, val: ty::Const<'tcx>, -) -> mir::DestructuredConst<'tcx> { +) -> InterpResult<'tcx, mir::DestructuredConst<'tcx>> { trace!("destructure_const: {:?}", val); let ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, false); - let op = ecx.const_to_op(val, None).unwrap(); + let op = ecx.const_to_op(val, None)?; // We go to `usize` as we cannot allocate anything bigger anyway. let (field_count, variant, down) = match val.ty().kind() { ty::Array(_, len) => (usize::try_from(len.eval_usize(tcx, param_env)).unwrap(), None, op), - ty::Adt(def, _) if def.variants.is_empty() => { - return mir::DestructuredConst { variant: None, fields: &[] }; - } ty::Adt(def, _) => { - let variant = ecx.read_discriminant(&op).unwrap().1; - let down = ecx.operand_downcast(&op, variant).unwrap(); + let variant = ecx.read_discriminant(&op)?.1; + let down = ecx.operand_downcast(&op, variant)?; (def.variants[variant].fields.len(), Some(variant), down) } ty::Tuple(substs) => (substs.len(), None, op), _ => bug!("cannot destructure constant {:?}", val), }; - let fields_iter = (0..field_count).map(|i| { - let field_op = ecx.operand_field(&down, i).unwrap(); - let val = op_to_const(&ecx, &field_op); - ty::Const::from_value(tcx, val, field_op.layout.ty) - }); - let fields = tcx.arena.alloc_from_iter(fields_iter); + let fields = (0..field_count) + .map(|i| { + let field_op = ecx.operand_field(&down, i)?; + let val = op_to_const(&ecx, &field_op); + Ok(ty::Const::from_value(tcx, val, field_op.layout.ty)) + }) + .collect::>>()?; + let fields = tcx.arena.alloc_from_iter(fields); - mir::DestructuredConst { variant, fields } + Ok(mir::DestructuredConst { variant, fields }) } pub(crate) fn deref_const<'tcx>( diff --git a/compiler/rustc_const_eval/src/lib.rs b/compiler/rustc_const_eval/src/lib.rs index 838484876c72a..77d312f585138 100644 --- a/compiler/rustc_const_eval/src/lib.rs +++ b/compiler/rustc_const_eval/src/lib.rs @@ -41,9 +41,9 @@ pub fn provide(providers: &mut Providers) { providers.eval_to_const_value_raw = const_eval::eval_to_const_value_raw_provider; providers.eval_to_allocation_raw = const_eval::eval_to_allocation_raw_provider; providers.const_caller_location = const_eval::const_caller_location; - providers.destructure_const = |tcx, param_env_and_value| { + providers.try_destructure_const = |tcx, param_env_and_value| { let (param_env, value) = param_env_and_value.into_parts(); - const_eval::destructure_const(tcx, param_env, value) + const_eval::try_destructure_const(tcx, param_env, value).ok() }; providers.const_to_valtree = |tcx, param_env_and_value| { let (param_env, raw) = param_env_and_value.into_parts(); diff --git a/compiler/rustc_middle/src/mir/interpret/queries.rs b/compiler/rustc_middle/src/mir/interpret/queries.rs index ec0f5b7d0595c..4a57f483c70e2 100644 --- a/compiler/rustc_middle/src/mir/interpret/queries.rs +++ b/compiler/rustc_middle/src/mir/interpret/queries.rs @@ -98,4 +98,12 @@ impl<'tcx> TyCtxt<'tcx> { let raw_const = self.eval_to_allocation_raw(param_env.and(gid))?; Ok(self.global_alloc(raw_const.alloc_id).unwrap_memory()) } + + /// Destructure a constant ADT or array into its variant index and its field values. + pub fn destructure_const( + self, + param_env_and_val: ty::ParamEnvAnd<'tcx, ty::Const<'tcx>>, + ) -> mir::DestructuredConst<'tcx> { + self.try_destructure_const(param_env_and_val).unwrap() + } } diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 77eda70bcd151..43cfe6f3b8a7a 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -924,10 +924,12 @@ rustc_queries! { } /// Destructure a constant ADT or array into its variant index and its - /// field values. - query destructure_const( + /// field values or return `None` if constant is invalid. + /// + /// Use infallible `TyCtxt::destructure_const` when you know that constant is valid. + query try_destructure_const( key: ty::ParamEnvAnd<'tcx, ty::Const<'tcx>> - ) -> mir::DestructuredConst<'tcx> { + ) -> Option> { desc { "destructure constant" } remap_env_constness } diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs index 893df1a009cfc..ae838a461574b 100644 --- a/compiler/rustc_middle/src/ty/print/pretty.rs +++ b/compiler/rustc_middle/src/ty/print/pretty.rs @@ -1459,10 +1459,18 @@ pub trait PrettyPrinter<'tcx>: // FIXME(eddyb) for `--emit=mir`/`-Z dump-mir`, we should provide the // correct `ty::ParamEnv` to allow printing *all* constant values. (_, ty::Array(..) | ty::Tuple(..) | ty::Adt(..)) if !ty.has_param_types_or_consts() => { - let contents = - self.tcx().destructure_const(ty::ParamEnv::reveal_all().and( - self.tcx().mk_const(ty::ConstS { val: ty::ConstKind::Value(ct), ty }), - )); + let Some(contents) = self.tcx().try_destructure_const( + ty::ParamEnv::reveal_all() + .and(self.tcx().mk_const(ty::ConstS { val: ty::ConstKind::Value(ct), ty })), + ) else { + // Fall back to debug pretty printing for invalid constants. + p!(write("{:?}", ct)); + if print_ty { + p!(": ", print(ty)); + } + return Ok(self); + }; + let fields = contents.fields.iter().copied(); match *ty.kind() { diff --git a/src/test/mir-opt/const_prop/invalid_constant.main.ConstProp.diff b/src/test/mir-opt/const_prop/invalid_constant.main.ConstProp.diff new file mode 100644 index 0000000000000..ee6c3b5f36fd4 --- /dev/null +++ b/src/test/mir-opt/const_prop/invalid_constant.main.ConstProp.diff @@ -0,0 +1,55 @@ +- // MIR for `main` before ConstProp ++ // MIR for `main` after ConstProp + + fn main() -> () { + let mut _0: (); // return place in scope 0 at $DIR/invalid_constant.rs:15:11: 15:11 + let _1: std::option::Option<()>; // in scope 0 at $DIR/invalid_constant.rs:16:5: 16:12 + let mut _2: std::option::Option>; // in scope 0 at $DIR/invalid_constant.rs:16:7: 16:11 + scope 1 (inlined f) { // at $DIR/invalid_constant.rs:16:5: 16:12 + debug x => _2; // in scope 1 at $DIR/invalid_constant.rs:16:5: 16:12 + let mut _3: isize; // in scope 1 at $DIR/invalid_constant.rs:16:5: 16:12 + let _4: std::option::Option<()>; // in scope 1 at $DIR/invalid_constant.rs:16:5: 16:12 + scope 2 { + debug y => _4; // in scope 2 at $DIR/invalid_constant.rs:16:5: 16:12 + } + } + + bb0: { + discriminant(_2) = 0; // scope 0 at $DIR/invalid_constant.rs:16:7: 16:11 +- _3 = discriminant(_2); // scope 1 at $DIR/invalid_constant.rs:16:5: 16:12 +- switchInt(move _3) -> [0_isize: bb3, otherwise: bb2]; // scope 1 at $DIR/invalid_constant.rs:16:5: 16:12 ++ _3 = const 0_isize; // scope 1 at $DIR/invalid_constant.rs:16:5: 16:12 ++ switchInt(const 0_isize) -> [0_isize: bb3, otherwise: bb2]; // scope 1 at $DIR/invalid_constant.rs:16:5: 16:12 + } + + bb1: { + nop; // scope 0 at $DIR/invalid_constant.rs:15:11: 17:2 + return; // scope 0 at $DIR/invalid_constant.rs:17:2: 17:2 + } + + bb2: { +- _4 = ((_2 as Some).0: std::option::Option<()>); // scope 1 at $DIR/invalid_constant.rs:16:5: 16:12 +- _1 = _4; // scope 2 at $DIR/invalid_constant.rs:16:5: 16:12 ++ _4 = const Scalar(0x02): Option::<()>; // scope 1 at $DIR/invalid_constant.rs:16:5: 16:12 ++ // ty::Const ++ // + ty: std::option::Option<()> ++ // + val: Value(Scalar(0x02)) ++ // mir::Constant ++ // + span: $DIR/invalid_constant.rs:16:5: 16:12 ++ // + literal: Const { ty: std::option::Option<()>, val: Value(Scalar(0x02)) } ++ _1 = const Scalar(0x02): Option::<()>; // scope 2 at $DIR/invalid_constant.rs:16:5: 16:12 ++ // ty::Const ++ // + ty: std::option::Option<()> ++ // + val: Value(Scalar(0x02)) ++ // mir::Constant ++ // + span: $DIR/invalid_constant.rs:16:5: 16:12 ++ // + literal: Const { ty: std::option::Option<()>, val: Value(Scalar(0x02)) } + goto -> bb1; // scope 0 at $DIR/invalid_constant.rs:10:20: 10:21 + } + + bb3: { + discriminant(_1) = 0; // scope 1 at $DIR/invalid_constant.rs:16:5: 16:12 + goto -> bb1; // scope 0 at $DIR/invalid_constant.rs:9:17: 9:21 + } + } + diff --git a/src/test/mir-opt/const_prop/invalid_constant.rs b/src/test/mir-opt/const_prop/invalid_constant.rs new file mode 100644 index 0000000000000..1eb6f37df5968 --- /dev/null +++ b/src/test/mir-opt/const_prop/invalid_constant.rs @@ -0,0 +1,17 @@ +// Verify that we can pretty print invalid constant introduced +// by constant propagation. Regression test for issue #93688. +// +// compile-flags: -Copt-level=0 -Zinline-mir + +#[inline(always)] +pub fn f(x: Option>) -> Option<()> { + match x { + None => None, + Some(y) => y, + } +} + +// EMIT_MIR invalid_constant.main.ConstProp.diff +fn main() { + f(None); +}