diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index e8b4cb343794c..010bd5372ab1e 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -85,6 +85,23 @@ impl NoArgsAttributeParser for RustcNeverReturnsNullPtrParser { ]); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcNeverReturnsNullPtr; } + +pub(crate) struct RustcPanicsWhenNIsZeroParser; + +impl NoArgsAttributeParser for RustcPanicsWhenNIsZeroParser { + const PATH: &[Symbol] = &[sym::rustc_panics_when_n_is_zero]; + const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Fn), + Allow(Target::Method(MethodKind::Inherent)), + Allow(Target::Method(MethodKind::Trait { body: false })), + Allow(Target::Method(MethodKind::Trait { body: true })), + Allow(Target::Method(MethodKind::TraitImpl)), + ]); + + const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcPanicsWhenNIsZero; +} + 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 190568bed508d..ba6cf6cfe84d5 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -311,6 +311,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 3a2f548902d11..fabc2065aec01 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_n_is_zero, Normal, template!(Word), ErrorFollowing, EncodeCrossCrate::Yes, + "`#[rustc_panics_when_n_is_zero]` is used to mark functions with 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 e8476c3d8c73b..d25289a9f34c6 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1508,6 +1508,9 @@ pub enum AttributeKind { /// Represents `#[rustc_offload_kernel]` RustcOffloadKernel, + /// Represents `#[rustc_panics_when_n_is_zero]` (used for linting). + RustcPanicsWhenNIsZero, + /// 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 27128f6996370..64f33572709bb 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -166,6 +166,7 @@ impl AttributeKind { RustcObjcClass { .. } => No, RustcObjcSelector { .. } => No, RustcOffloadKernel => Yes, + RustcPanicsWhenNIsZero => Yes, RustcParenSugar(..) => No, RustcPassByValue(..) => Yes, RustcPassIndirectlyInNonRusticAbis(..) => No, diff --git a/compiler/rustc_mir_transform/src/errors.rs b/compiler/rustc_mir_transform/src/errors.rs index daee8de965135..e47b2a571bbf9 100644 --- a/compiler/rustc_mir_transform/src/errors.rs +++ b/compiler/rustc_mir_transform/src/errors.rs @@ -122,10 +122,15 @@ pub(crate) struct UnknownPassName<'a> { pub(crate) struct AssertLint

{ pub span: Span, - pub assert_kind: AssertKind

, + pub message: AssertLintMessage

, pub lint_kind: AssertLintKind, } +pub(crate) enum AssertLintMessage

{ + AssertKind(AssertKind

), + Message(&'static str), +} + pub(crate) enum AssertLintKind { ArithmeticOverflow, UnconditionalPanic, @@ -145,7 +150,14 @@ impl<'a, P: std::fmt::Debug> Diagnostic<'a, ()> for AssertLint

{ } }, ); - diag.span_label(self.span, self.assert_kind.to_string()); + match self.message { + AssertLintMessage::AssertKind(assert_kind) => { + diag.span_label(self.span, assert_kind.to_string()); + } + AssertLintMessage::Message(msg) => { + diag.span_label(self.span, msg); + } + } diag } } diff --git a/compiler/rustc_mir_transform/src/known_panics_lint.rs b/compiler/rustc_mir_transform/src/known_panics_lint.rs index 8d0922db8f40e..caa6b90931a8f 100644 --- a/compiler/rustc_mir_transform/src/known_panics_lint.rs +++ b/compiler/rustc_mir_transform/src/known_panics_lint.rs @@ -10,19 +10,19 @@ 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, ScalarInt, Ty, TyCtxt, TypeVisitableExt}; use rustc_span::Span; use tracing::{debug, instrument, trace}; -use crate::errors::{AssertLint, AssertLintKind}; +use crate::errors::{AssertLint, AssertLintKind, AssertLintMessage}; pub(super) struct KnownPanicsLint; @@ -288,6 +288,25 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { } } + fn report_assert_message( + &self, + location: Location, + lint_kind: AssertLintKind, + msg: &'static str, + ) { + let source_info = self.body.source_info(location); + if let Some(lint_root) = self.lint_root(*source_info) { + let span = source_info.span; + let message = AssertLintMessage::<()>::Message(msg); + self.tcx.emit_node_span_lint( + lint_kind.lint(), + lint_root, + span, + AssertLint { span, message, lint_kind }, + ); + } + } + fn report_assert_as_lint( &self, location: Location, @@ -297,11 +316,12 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { let source_info = self.body.source_info(location); if let Some(lint_root) = self.lint_root(*source_info) { let span = source_info.span; + let message = AssertLintMessage::AssertKind(assert_kind); self.tcx.emit_node_span_lint( lint_kind.lint(), lint_root, span, - AssertLint { span, assert_kind, lint_kind }, + AssertLint { span, message, lint_kind }, ); } } @@ -765,6 +785,22 @@ 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 arg in generic_args { + if let GenericArgKind::Const(ct) = arg.kind() + && find_attr!(self.tcx, def_id, RustcPanicsWhenNIsZero) + && let Some(0) = ct.try_to_target_usize(self.tcx) + { + self.report_assert_message( + location, + AssertLintKind::UnconditionalPanic, + "const parameter `N` is zero", + ); + } + } + } + } // None of these have Operands to const-propagate. TerminatorKind::Goto { .. } | TerminatorKind::UnwindResume @@ -777,7 +813,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 bec6ab7e83551..d24a8afbc1ac9 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -358,6 +358,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::RustcObjcClass { .. } | AttributeKind::RustcObjcSelector { .. } | AttributeKind::RustcOffloadKernel + | AttributeKind::RustcPanicsWhenNIsZero | AttributeKind::RustcParenSugar(..) | AttributeKind::RustcPassByValue (..) | AttributeKind::RustcPassIndirectlyInNonRusticAbis(..) diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 48cdf46b821f9..2e115606250aa 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1756,6 +1756,7 @@ symbols! { rustc_objc_selector, rustc_offload_kernel, rustc_on_unimplemented, + rustc_panics_when_n_is_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..893f50d4794d0 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -1332,6 +1332,7 @@ impl [T] { /// ``` #[stable(feature = "slice_as_chunks", since = "1.88.0")] #[rustc_const_stable(feature = "slice_as_chunks", since = "1.88.0")] + #[rustc_panics_when_n_is_zero] #[inline] #[must_use] #[track_caller] @@ -1390,6 +1391,7 @@ impl [T] { /// ``` #[stable(feature = "slice_as_chunks", since = "1.88.0")] #[rustc_const_stable(feature = "slice_as_chunks", since = "1.88.0")] + #[rustc_panics_when_n_is_zero] #[inline] #[track_caller] #[must_use] @@ -1437,6 +1439,7 @@ impl [T] { /// ``` #[stable(feature = "slice_as_chunks", since = "1.88.0")] #[rustc_const_stable(feature = "slice_as_chunks", since = "1.88.0")] + #[rustc_panics_when_n_is_zero] #[inline] #[track_caller] #[must_use] @@ -1492,6 +1495,7 @@ impl [T] { /// ``` #[stable(feature = "slice_as_chunks", since = "1.88.0")] #[rustc_const_stable(feature = "slice_as_chunks", since = "1.88.0")] + #[rustc_panics_when_n_is_zero] #[inline] #[must_use] #[track_caller] @@ -1546,6 +1550,7 @@ impl [T] { /// ``` #[stable(feature = "slice_as_chunks", since = "1.88.0")] #[rustc_const_stable(feature = "slice_as_chunks", since = "1.88.0")] + #[rustc_panics_when_n_is_zero] #[inline] #[track_caller] #[must_use] @@ -1599,6 +1604,7 @@ impl [T] { /// ``` #[stable(feature = "slice_as_chunks", since = "1.88.0")] #[rustc_const_stable(feature = "slice_as_chunks", since = "1.88.0")] + #[rustc_panics_when_n_is_zero] #[inline] #[track_caller] #[must_use] @@ -1641,6 +1647,7 @@ impl [T] { /// [`windows`]: slice::windows #[stable(feature = "array_windows", since = "1.94.0")] #[rustc_const_unstable(feature = "const_slice_make_iter", issue = "137737")] + #[rustc_panics_when_n_is_zero] #[inline] #[track_caller] pub const fn array_windows(&self) -> ArrayWindows<'_, T, N> { 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 +