diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index 15772d874507f..8fbe3b57bf9cb 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -7,6 +7,7 @@ use rustc_hir::attrs::{ DivergingFallbackBehavior, RustcCleanAttribute, RustcCleanQueries, RustcLayoutType, RustcMirKind, }; +use rustc_hir::target::GenericParamKind; use rustc_session::errors; use rustc_span::Symbol; @@ -84,6 +85,20 @@ impl NoArgsAttributeParser for RustcNeverReturnsNullPtrParser { ]); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcNeverReturnsNullPtr; } + +pub(crate) struct RustcPanicsWhenZeroParser; + +impl NoArgsAttributeParser for RustcPanicsWhenZeroParser { + const PATH: &[Symbol] = &[sym::rustc_panics_when_zero]; + const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::GenericParam { kind: GenericParamKind::Const, has_default: true }), + Allow(Target::GenericParam { kind: GenericParamKind::Const, has_default: false }), + ]); + + const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcPanicsWhenZero; +} + pub(crate) struct RustcNoImplicitAutorefsParser; impl NoArgsAttributeParser for RustcNoImplicitAutorefsParser { diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 259a73de59853..b2edee70773a7 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -313,6 +313,7 @@ attribute_parsers!( Single>, Single>, Single>, + Single>, Single>, Single>, Single>, diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index acbcba90fbcc0..03312ae8a070a 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -1274,6 +1274,10 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ EncodeCrossCrate::Yes, "`#[rustc_never_returns_null_ptr]` is used to mark functions returning non-null pointers" ), + rustc_attr!( + rustc_panics_when_zero, Normal, template!(Word), ErrorFollowing, EncodeCrossCrate::Yes, + "`#[rustc_panics_when_zero]` is used to mark a const generic argument who makes the function panics when it is zero" + ), rustc_attr!( rustc_no_implicit_autorefs, AttributeType::Normal, template!(Word), ErrorFollowing, EncodeCrossCrate::Yes, "`#[rustc_no_implicit_autorefs]` is used to mark functions for which an autoref to the dereference of a raw pointer should not be used as an argument" diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index a18ddff947099..b29c9e7c69487 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1513,6 +1513,9 @@ pub enum AttributeKind { /// Represents `#[rustc_offload_kernel]` RustcOffloadKernel, + /// Represents `#[rustc_panics_when_n_is_zero]` (used for linting). + RustcPanicsWhenZero, + /// Represents `#[rustc_paren_sugar]`. RustcParenSugar(Span), diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index c19fc6976c6e6..e5e8812c63f80 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -167,6 +167,7 @@ impl AttributeKind { RustcObjcClass { .. } => No, RustcObjcSelector { .. } => No, RustcOffloadKernel => Yes, + RustcPanicsWhenZero => Yes, RustcParenSugar(..) => No, RustcPassByValue(..) => Yes, RustcPassIndirectlyInNonRusticAbis(..) => No, diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 8bf919dab8e79..c6fa02676a275 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -942,6 +942,7 @@ fn should_encode_attrs(def_kind: DefKind) -> bool { | DefKind::AssocConst { .. } | DefKind::Macro(_) | DefKind::Field + | DefKind::ConstParam | DefKind::Impl { .. } => true, // Tools may want to be able to detect their tool lints on // closures from upstream crates, too. This is used by @@ -950,7 +951,6 @@ fn should_encode_attrs(def_kind: DefKind) -> bool { DefKind::Closure => true, DefKind::SyntheticCoroutineBody => false, DefKind::TyParam - | DefKind::ConstParam | DefKind::Ctor(..) | DefKind::ExternCrate | DefKind::Use diff --git a/compiler/rustc_mir_transform/src/errors.rs b/compiler/rustc_mir_transform/src/errors.rs index daee8de965135..48813255d0a59 100644 --- a/compiler/rustc_mir_transform/src/errors.rs +++ b/compiler/rustc_mir_transform/src/errors.rs @@ -159,6 +159,13 @@ impl AssertLintKind { } } +#[derive(Diagnostic)] +#[diag("this operation will panic at runtime")] +pub(crate) struct ConstNIsZero { + #[label("const parameter `N` is zero")] + pub const_param: Span, +} + #[derive(Diagnostic)] #[diag("call to inline assembly that may unwind")] pub(crate) struct AsmUnwindCall { diff --git a/compiler/rustc_mir_transform/src/known_panics_lint.rs b/compiler/rustc_mir_transform/src/known_panics_lint.rs index 8d0922db8f40e..7a8ab88ab9226 100644 --- a/compiler/rustc_mir_transform/src/known_panics_lint.rs +++ b/compiler/rustc_mir_transform/src/known_panics_lint.rs @@ -10,19 +10,22 @@ use rustc_const_eval::interpret::{ ImmTy, InterpCx, InterpResult, Projectable, Scalar, format_interp_error, interp_ok, }; use rustc_data_structures::fx::FxHashSet; -use rustc_hir::HirId; use rustc_hir::def::DefKind; +use rustc_hir::{HirId, find_attr}; use rustc_index::IndexVec; use rustc_index::bit_set::DenseBitSet; use rustc_middle::bug; use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::*; use rustc_middle::ty::layout::{LayoutError, LayoutOf, LayoutOfHelpers, TyAndLayout}; -use rustc_middle::ty::{self, ConstInt, ScalarInt, Ty, TyCtxt, TypeVisitableExt}; +use rustc_middle::ty::{ + self, ConstInt, GenericArgKind, GenericParamDefKind, ScalarInt, Ty, TyCtxt, TypeVisitableExt, +}; +use rustc_session::lint::builtin::UNCONDITIONAL_PANIC; use rustc_span::Span; use tracing::{debug, instrument, trace}; -use crate::errors::{AssertLint, AssertLintKind}; +use crate::errors::{AssertLint, AssertLintKind, ConstNIsZero}; pub(super) struct KnownPanicsLint; @@ -765,6 +768,35 @@ impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> { } // We failed to evaluate the discriminant, fallback to visiting all successors. } + TerminatorKind::Call { func, args: _, .. } => { + if let Some((def_id, generic_args)) = func.const_fn_def() { + for (index, arg) in generic_args.iter().enumerate() { + if let GenericArgKind::Const(ct) = arg.kind() { + let generics = self.tcx.generics_of(def_id); + let param_def = generics.param_at(index, self.tcx); + + if let GenericParamDefKind::Const { .. } = param_def.kind + && find_attr!(self.tcx, param_def.def_id, RustcPanicsWhenZero) + && let Some(0) = ct.try_to_target_usize(self.tcx) + { + // We managed to figure-out that the value of a + // `#[rustc_panics_when_zero]` const-generic parameter is zero. + // + // Let's report it as an unconditional panic. + let source_info = self.body.source_info(location); + if let Some(lint_root) = self.lint_root(*source_info) { + self.tcx.emit_node_span_lint( + UNCONDITIONAL_PANIC, + lint_root, + source_info.span, + ConstNIsZero { const_param: source_info.span }, + ); + } + } + } + } + } + } // None of these have Operands to const-propagate. TerminatorKind::Goto { .. } | TerminatorKind::UnwindResume @@ -777,7 +809,6 @@ impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> { | TerminatorKind::CoroutineDrop | TerminatorKind::FalseEdge { .. } | TerminatorKind::FalseUnwind { .. } - | TerminatorKind::Call { .. } | TerminatorKind::InlineAsm { .. } => {} } diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 26ba2b0e8f42d..097d2e900fadc 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -350,6 +350,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::RustcObjcClass { .. } | AttributeKind::RustcObjcSelector { .. } | AttributeKind::RustcOffloadKernel + | AttributeKind::RustcPanicsWhenZero | AttributeKind::RustcParenSugar(..) | AttributeKind::RustcPassByValue (..) | AttributeKind::RustcPassIndirectlyInNonRusticAbis(..) diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index ccd4516372d26..e3f21a55eb6d7 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1759,6 +1759,7 @@ symbols! { rustc_objc_selector, rustc_offload_kernel, rustc_on_unimplemented, + rustc_panics_when_zero, rustc_paren_sugar, rustc_partition_codegened, rustc_partition_reused, diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index 36dd4d6782ac1..9cb81f0a0780c 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -1335,7 +1335,9 @@ impl [T] { #[inline] #[must_use] #[track_caller] - pub const unsafe fn as_chunks_unchecked(&self) -> &[[T; N]] { + pub const unsafe fn as_chunks_unchecked<#[rustc_panics_when_zero] const N: usize>( + &self, + ) -> &[[T; N]] { assert_unsafe_precondition!( check_language_ub, "slice::as_chunks_unchecked requires `N != 0` and the slice to split exactly into `N`-element chunks", @@ -1393,7 +1395,7 @@ impl [T] { #[inline] #[track_caller] #[must_use] - pub const fn as_chunks(&self) -> (&[[T; N]], &[T]) { + pub const fn as_chunks<#[rustc_panics_when_zero] const N: usize>(&self) -> (&[[T; N]], &[T]) { assert!(N != 0, "chunk size must be non-zero"); let len_rounded_down = self.len() / N * N; // SAFETY: The rounded-down value is always the same or smaller than the @@ -1440,7 +1442,7 @@ impl [T] { #[inline] #[track_caller] #[must_use] - pub const fn as_rchunks(&self) -> (&[T], &[[T; N]]) { + pub const fn as_rchunks<#[rustc_panics_when_zero] const N: usize>(&self) -> (&[T], &[[T; N]]) { assert!(N != 0, "chunk size must be non-zero"); let len = self.len() / N; let (remainder, multiple_of_n) = self.split_at(self.len() - len * N); @@ -1495,7 +1497,9 @@ impl [T] { #[inline] #[must_use] #[track_caller] - pub const unsafe fn as_chunks_unchecked_mut(&mut self) -> &mut [[T; N]] { + pub const unsafe fn as_chunks_unchecked_mut<#[rustc_panics_when_zero] const N: usize>( + &mut self, + ) -> &mut [[T; N]] { assert_unsafe_precondition!( check_language_ub, "slice::as_chunks_unchecked requires `N != 0` and the slice to split exactly into `N`-element chunks", @@ -1549,7 +1553,9 @@ impl [T] { #[inline] #[track_caller] #[must_use] - pub const fn as_chunks_mut(&mut self) -> (&mut [[T; N]], &mut [T]) { + pub const fn as_chunks_mut<#[rustc_panics_when_zero] const N: usize>( + &mut self, + ) -> (&mut [[T; N]], &mut [T]) { assert!(N != 0, "chunk size must be non-zero"); let len_rounded_down = self.len() / N * N; // SAFETY: The rounded-down value is always the same or smaller than the @@ -1602,7 +1608,9 @@ impl [T] { #[inline] #[track_caller] #[must_use] - pub const fn as_rchunks_mut(&mut self) -> (&mut [T], &mut [[T; N]]) { + pub const fn as_rchunks_mut<#[rustc_panics_when_zero] const N: usize>( + &mut self, + ) -> (&mut [T], &mut [[T; N]]) { assert!(N != 0, "chunk size must be non-zero"); let len = self.len() / N; let (remainder, multiple_of_n) = self.split_at_mut(self.len() - len * N); @@ -1643,7 +1651,9 @@ impl [T] { #[rustc_const_unstable(feature = "const_slice_make_iter", issue = "137737")] #[inline] #[track_caller] - pub const fn array_windows(&self) -> ArrayWindows<'_, T, N> { + pub const fn array_windows<#[rustc_panics_when_zero] const N: usize>( + &self, + ) -> ArrayWindows<'_, T, N> { assert!(N != 0, "window size must be non-zero"); ArrayWindows::new(self) } diff --git a/tests/ui/lint/const-n-is-zero.rs b/tests/ui/lint/const-n-is-zero.rs new file mode 100644 index 0000000000000..9efc6f861ff52 --- /dev/null +++ b/tests/ui/lint/const-n-is-zero.rs @@ -0,0 +1,44 @@ +// Test that core functions annotated with `#[rustc_panics_when_n_is_zero]` lint when `N` is zero + +//@ build-fail + +const ZERO: usize = 0; +const ONE: usize = 1; + +fn main() { + let s = [1, 2, 3, 4]; + + let _ = s.array_windows::<0>(); + //~^ ERROR this operation will panic at runtime + //~| NOTE `#[deny(unconditional_panic)]` on by default + //~| NOTE const parameter `N` is zero + + let _ = s.as_chunks::<{ 0 }>(); + //~^ ERROR this operation will panic at runtime + //~| NOTE const parameter `N` is zero + // + let _ = s.as_rchunks::<{ 1 - 1 }>(); + //~^ ERROR this operation will panic at runtime + //~| NOTE const parameter `N` is zero + + let mut m = [1, 2, 3, 4]; + + let _ = m.as_chunks_mut::(); + //~^ ERROR this operation will panic at runtime + //~| NOTE const parameter `N` is zero + + let _ = m.as_rchunks_mut::<{ if ZERO == 0 { 0 } else { 1 } }>(); + //~^ ERROR this operation will panic at runtime + //~| NOTE const parameter `N` is zero + + let _ = s.array_windows().any(|[]| true); + //~^ ERROR this operation will panic at runtime + //~| NOTE const parameter `N` is zero + + // Shouldn't lint + let _ = s.array_windows::<2>(); + let _ = s.as_chunks::<1>(); + let _ = m.as_chunks_mut::(); + let _ = m.as_rchunks::<{ 1 + 1 }>(); + let _ = m.as_rchunks_mut::<{ if ZERO == 1 { 0 } else { 5 } }>(); +} diff --git a/tests/ui/lint/const-n-is-zero.stderr b/tests/ui/lint/const-n-is-zero.stderr new file mode 100644 index 0000000000000..3e477784d0454 --- /dev/null +++ b/tests/ui/lint/const-n-is-zero.stderr @@ -0,0 +1,40 @@ +error: this operation will panic at runtime + --> $DIR/const-n-is-zero.rs:11:13 + | +LL | let _ = s.array_windows::<0>(); + | ^^^^^^^^^^^^^^^^^^^^^^ const parameter `N` is zero + | + = note: `#[deny(unconditional_panic)]` on by default + +error: this operation will panic at runtime + --> $DIR/const-n-is-zero.rs:16:13 + | +LL | let _ = s.as_chunks::<{ 0 }>(); + | ^^^^^^^^^^^^^^^^^^^^^^ const parameter `N` is zero + +error: this operation will panic at runtime + --> $DIR/const-n-is-zero.rs:20:13 + | +LL | let _ = s.as_rchunks::<{ 1 - 1 }>(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ const parameter `N` is zero + +error: this operation will panic at runtime + --> $DIR/const-n-is-zero.rs:26:13 + | +LL | let _ = m.as_chunks_mut::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ const parameter `N` is zero + +error: this operation will panic at runtime + --> $DIR/const-n-is-zero.rs:30:13 + | +LL | let _ = m.as_rchunks_mut::<{ if ZERO == 0 { 0 } else { 1 } }>(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ const parameter `N` is zero + +error: this operation will panic at runtime + --> $DIR/const-n-is-zero.rs:34:13 + | +LL | let _ = s.array_windows().any(|[]| true); + | ^^^^^^^^^^^^^^^^^ const parameter `N` is zero + +error: aborting due to 6 previous errors +