From c930cbbcf85bf443706a8cfcd3b8ea4e124b3ccd Mon Sep 17 00:00:00 2001 From: waffle Date: Tue, 3 Nov 2026 15:20:10 +0100 Subject: [PATCH 1/2] fix unsoundness when compiling `[diverging_const; 0]` Previusly we had a disagreement between hir_typeck (assuming that anything following the diverging constant is unreachable) and mir_build (assuming we don't need to evaluate constants in `[...; 0]`). This (temporarily) fixes this by making hir_typeck aware that such constants should not affect divergence / reachibility analysis --- compiler/rustc_hir/src/hir.rs | 87 +++++++++++++++++++ compiler/rustc_hir_typeck/src/expr.rs | 24 +++++ .../src/builder/expr/category.rs | 2 + tests/ui/repeat-expr/zero_diverging_consts.rs | 12 +++ .../repeat-expr/zero_diverging_consts.stderr | 11 +++ .../zero_diverging_consts_runnable.rs | 19 ++++ .../zero_diverging_consts_runnable.run.stderr | 4 + ...o_diverging_type_relative_consts_consts.rs | 21 +++++ ...verging_type_relative_consts_consts.stderr | 11 +++ 9 files changed, 191 insertions(+) create mode 100644 tests/ui/repeat-expr/zero_diverging_consts.rs create mode 100644 tests/ui/repeat-expr/zero_diverging_consts.stderr create mode 100644 tests/ui/repeat-expr/zero_diverging_consts_runnable.rs create mode 100644 tests/ui/repeat-expr/zero_diverging_consts_runnable.run.stderr create mode 100644 tests/ui/repeat-expr/zero_diverging_type_relative_consts_consts.rs create mode 100644 tests/ui/repeat-expr/zero_diverging_type_relative_consts_consts.stderr diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 5cfce366bbb23..e1ec05f3f7471 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -2354,6 +2354,93 @@ impl Expr<'_> { } } + /// Returns true if mir building would consider this expression a constant for the purposes of + /// lowering `[CONST; 0]` as `[]`. + pub fn is_constant_for_zero_repeat(&self) -> bool { + // NB: This needs to be kept in sync with `rustc_mir_build::builder::expr::category::Category::of` + + match self.kind { + ExprKind::ConstBlock(_) | ExprKind::Lit(_) => true, + ExprKind::Cast(inner, _) | ExprKind::Type(inner, _) => { + inner.is_constant_for_zero_repeat() + } + + ExprKind::Path(qpath) => { + match qpath { + QPath::Resolved(_, Path { res: Res::Def(def_kind, _), .. }) => match def_kind { + DefKind::Const | DefKind::ConstParam | DefKind::AssocConst => true, + + DefKind::Mod + | DefKind::Struct + | DefKind::Union + | DefKind::Enum + | DefKind::Variant + | DefKind::Trait + | DefKind::TyAlias + | DefKind::ForeignTy + | DefKind::TraitAlias + | DefKind::AssocTy + | DefKind::TyParam + | DefKind::Fn + | DefKind::Static { .. } + | DefKind::Ctor(..) + | DefKind::AssocFn + | DefKind::Macro(_) + | DefKind::ExternCrate + | DefKind::Use + | DefKind::ForeignMod + | DefKind::AnonConst + | DefKind::OpaqueTy + | DefKind::Field + | DefKind::LifetimeParam + | DefKind::GlobalAsm + | DefKind::Impl { .. } + | DefKind::Closure + | DefKind::SyntheticCoroutineBody + | DefKind::TestBinderConstraints => false, + }, + + // A path that resolved to something other than a definition cannot be a const + QPath::Resolved(..) => false, + + // A type relative expression can only be a const + QPath::TypeRelative(..) => true, + } + } + + ExprKind::Array(_) + | ExprKind::Call(..) + | ExprKind::MethodCall(..) + | ExprKind::Use(..) + | ExprKind::Tup(_) + | ExprKind::Binary(..) + | ExprKind::Unary(..) + | ExprKind::DropTemps(_) + | ExprKind::Let(_) + | ExprKind::If(..) + | ExprKind::Loop(..) + | ExprKind::Match(..) + | ExprKind::Closure(_) + | ExprKind::Block(..) + | ExprKind::Assign(..) + | ExprKind::AssignOp(..) + | ExprKind::Field(..) + | ExprKind::Index(..) + | ExprKind::AddrOf(..) + | ExprKind::Break(..) + | ExprKind::Continue(_) + | ExprKind::Ret(_) + | ExprKind::Become(_) + | ExprKind::InlineAsm(_) + | ExprKind::OffsetOf(..) + | ExprKind::Struct(..) + | ExprKind::Repeat(..) + | ExprKind::Yield(..) + | ExprKind::UnsafeBinderCast(..) + | ExprKind::Err(_) => false, + } + } + /// If this is a desugared range expression, /// returns the span of the range without desugaring context. pub fn range_span(&self) -> Option { diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 8944d6a47488a..bf5fc2562e4c9 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -1739,11 +1739,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ), ); + let count_is_zero = { + if let Ok(count) = tcx.try_normalize_erasing_regions( + self.typing_env(self.param_env), + Unnormalized::new_wip(count), + ) { + count.try_to_value().and_then(|c| c.try_to_target_usize(tcx)) == Some(0) + } else { + false + } + }; + let uty = match expected { ExpectHasType(uty) => uty.builtin_index(), _ => None, }; + let old_diverges = self.diverges.get(); + let (element_ty, t) = match uty { Some(uty) => { self.check_expr_coercible_to_type(element, uty, None); @@ -1756,6 +1769,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } }; + // FIXME: remove this workaround for an unsoundness (#165323) once it's no longer needed. + // + // MIR building lowers `[CONST; 0]` to `[]`, making `CONST` not evaluated. + // + // This is a problem, because it means if `CONST` is `const { panic!() }`, the side effect + // of panic / failing compilation never happens. In such cases we need to ignore divergence + // from the `CONST`, as otherwise we can end up executing code that was assumed unreachable. + if count_is_zero && element.is_constant_for_zero_repeat() { + self.diverges.set(old_diverges); + } + if let Err(guar) = element_ty.error_reported() { return Ty::new_error(tcx, guar); } diff --git a/compiler/rustc_mir_build/src/builder/expr/category.rs b/compiler/rustc_mir_build/src/builder/expr/category.rs index 9e5e4c16d9d96..f5b2f7ef338bf 100644 --- a/compiler/rustc_mir_build/src/builder/expr/category.rs +++ b/compiler/rustc_mir_build/src/builder/expr/category.rs @@ -32,6 +32,8 @@ impl Category { /// Determines the category for a given expression. Note that scope /// and paren expressions have no category. pub(crate) fn of(ek: &ExprKind<'_>) -> Option { + // NB: this needs to be kept in sync with `hir::Expr::is_constant_for_zero_repeat` + match *ek { ExprKind::Scope { .. } => None, diff --git a/tests/ui/repeat-expr/zero_diverging_consts.rs b/tests/ui/repeat-expr/zero_diverging_consts.rs new file mode 100644 index 0000000000000..91881a3b94d67 --- /dev/null +++ b/tests/ui/repeat-expr/zero_diverging_consts.rs @@ -0,0 +1,12 @@ +// Check that repeating a diverging constant 0 times doesn't lead to unsoundness. +// +// Issue: + +fn ex_nihilo() -> u32 { + //~^ error: mismatched types + [const { panic!() } as !; 0]; +} + +fn main() { + dbg!(ex_nihilo()); +} diff --git a/tests/ui/repeat-expr/zero_diverging_consts.stderr b/tests/ui/repeat-expr/zero_diverging_consts.stderr new file mode 100644 index 0000000000000..27e48e5d656b2 --- /dev/null +++ b/tests/ui/repeat-expr/zero_diverging_consts.stderr @@ -0,0 +1,11 @@ +error[E0308]: mismatched types + --> $DIR/zero_diverging_consts.rs:5:19 + | +LL | fn ex_nihilo() -> u32 { + | --------- ^^^ expected `u32`, found `()` + | | + | implicitly returns `()` as its body has no tail or `return` expression + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/repeat-expr/zero_diverging_consts_runnable.rs b/tests/ui/repeat-expr/zero_diverging_consts_runnable.rs new file mode 100644 index 0000000000000..f4f47f9071fd6 --- /dev/null +++ b/tests/ui/repeat-expr/zero_diverging_consts_runnable.rs @@ -0,0 +1,19 @@ +// Check that repeating a diverging constant 0 times doesn't lead to unsoundness. +// +// Issue: +// +//@ run-fail +//@ check-run-results + +fn get(v: &[u32], i: usize) -> u32 { + if i >= v.len() { + let _: [!; 0] = [const { panic!() }; 0]; + } + v[i] +} + +fn main() { + let v = vec![1, 2, 3]; + let i = std::hint::black_box(4); // 4, hidden from the optimizer + println!("v[{i}] = {}", get(&v, i)); +} diff --git a/tests/ui/repeat-expr/zero_diverging_consts_runnable.run.stderr b/tests/ui/repeat-expr/zero_diverging_consts_runnable.run.stderr new file mode 100644 index 0000000000000..b4d33e11415b7 --- /dev/null +++ b/tests/ui/repeat-expr/zero_diverging_consts_runnable.run.stderr @@ -0,0 +1,4 @@ + +thread 'main' ($TID) panicked at $DIR/zero_diverging_consts_runnable.rs:12:5: +index out of bounds: the len is 3 but the index is 4 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/tests/ui/repeat-expr/zero_diverging_type_relative_consts_consts.rs b/tests/ui/repeat-expr/zero_diverging_type_relative_consts_consts.rs new file mode 100644 index 0000000000000..0b23e291cfacf --- /dev/null +++ b/tests/ui/repeat-expr/zero_diverging_type_relative_consts_consts.rs @@ -0,0 +1,21 @@ +// Check that repeating a diverging constant 0 times doesn't lead to unsoundness. +// +// Issue: + +trait T { + const V: !; +} + +struct Panic; +impl T for Panic { + const V: ! = panic!(); +} + +fn ex_nihilo() -> u32 { + //~^ error: mismatched types + [Panic::V; 0]; +} + +fn main() { + dbg!(ex_nihilo()); +} diff --git a/tests/ui/repeat-expr/zero_diverging_type_relative_consts_consts.stderr b/tests/ui/repeat-expr/zero_diverging_type_relative_consts_consts.stderr new file mode 100644 index 0000000000000..cddb6fb897ef4 --- /dev/null +++ b/tests/ui/repeat-expr/zero_diverging_type_relative_consts_consts.stderr @@ -0,0 +1,11 @@ +error[E0308]: mismatched types + --> $DIR/zero_diverging_type_relative_consts_consts.rs:14:19 + | +LL | fn ex_nihilo() -> u32 { + | --------- ^^^ expected `u32`, found `()` + | | + | implicitly returns `()` as its body has no tail or `return` expression + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. From 766a8ef29ea555dfcd9a7b21bb091fa95e8e9b47 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Tue, 3 Nov 2026 20:22:35 +0100 Subject: [PATCH 2/2] Always disable backtrace in `zero_diverging_consts_runnable.rs` --- tests/ui/repeat-expr/zero_diverging_consts_runnable.rs | 1 + tests/ui/repeat-expr/zero_diverging_consts_runnable.run.stderr | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ui/repeat-expr/zero_diverging_consts_runnable.rs b/tests/ui/repeat-expr/zero_diverging_consts_runnable.rs index f4f47f9071fd6..9fa26608b58a0 100644 --- a/tests/ui/repeat-expr/zero_diverging_consts_runnable.rs +++ b/tests/ui/repeat-expr/zero_diverging_consts_runnable.rs @@ -4,6 +4,7 @@ // //@ run-fail //@ check-run-results +//@ exec-env:RUST_BACKTRACE=0 fn get(v: &[u32], i: usize) -> u32 { if i >= v.len() { diff --git a/tests/ui/repeat-expr/zero_diverging_consts_runnable.run.stderr b/tests/ui/repeat-expr/zero_diverging_consts_runnable.run.stderr index b4d33e11415b7..b2de85342f2c6 100644 --- a/tests/ui/repeat-expr/zero_diverging_consts_runnable.run.stderr +++ b/tests/ui/repeat-expr/zero_diverging_consts_runnable.run.stderr @@ -1,4 +1,4 @@ -thread 'main' ($TID) panicked at $DIR/zero_diverging_consts_runnable.rs:12:5: +thread 'main' ($TID) panicked at $DIR/zero_diverging_consts_runnable.rs:13:5: index out of bounds: the len is 3 but the index is 4 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace