Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -84,6 +85,20 @@ impl<S: Stage> NoArgsAttributeParser<S> for RustcNeverReturnsNullPtrParser {
]);
const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcNeverReturnsNullPtr;
}

pub(crate) struct RustcPanicsWhenZeroParser;

impl<S: Stage> NoArgsAttributeParser<S> for RustcPanicsWhenZeroParser {
const PATH: &[Symbol] = &[sym::rustc_panics_when_zero];
const ON_DUPLICATE: OnDuplicate<S> = 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<S: Stage> NoArgsAttributeParser<S> for RustcNoImplicitAutorefsParser {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_attr_parsing/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ attribute_parsers!(
Single<WithoutArgs<RustcNonnullOptimizationGuaranteedParser>>,
Single<WithoutArgs<RustcNounwindParser>>,
Single<WithoutArgs<RustcOffloadKernelParser>>,
Single<WithoutArgs<RustcPanicsWhenZeroParser>>,
Single<WithoutArgs<RustcParenSugarParser>>,
Single<WithoutArgs<RustcPassByValueParser>>,
Single<WithoutArgs<RustcPassIndirectlyInNonRusticAbisParser>>,
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_hir/src/attrs/data_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir/src/attrs/encode_cross_crate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ impl AttributeKind {
RustcObjcClass { .. } => No,
RustcObjcSelector { .. } => No,
RustcOffloadKernel => Yes,
RustcPanicsWhenZero => Yes,
RustcParenSugar(..) => No,
RustcPassByValue(..) => Yes,
RustcPassIndirectlyInNonRusticAbis(..) => No,
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_metadata/src/rmeta/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_mir_transform/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
39 changes: 35 additions & 4 deletions compiler/rustc_mir_transform/src/known_panics_lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand All @@ -777,7 +809,6 @@ impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> {
| TerminatorKind::CoroutineDrop
| TerminatorKind::FalseEdge { .. }
| TerminatorKind::FalseUnwind { .. }
| TerminatorKind::Call { .. }
| TerminatorKind::InlineAsm { .. } => {}
}

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| AttributeKind::RustcObjcClass { .. }
| AttributeKind::RustcObjcSelector { .. }
| AttributeKind::RustcOffloadKernel
| AttributeKind::RustcPanicsWhenZero
| AttributeKind::RustcParenSugar(..)
| AttributeKind::RustcPassByValue (..)
| AttributeKind::RustcPassIndirectlyInNonRusticAbis(..)
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
24 changes: 17 additions & 7 deletions library/core/src/slice/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1335,7 +1335,9 @@ impl<T> [T] {
#[inline]
#[must_use]
#[track_caller]
pub const unsafe fn as_chunks_unchecked<const N: usize>(&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",
Expand Down Expand Up @@ -1393,7 +1395,7 @@ impl<T> [T] {
#[inline]
#[track_caller]
#[must_use]
pub const fn as_chunks<const N: usize>(&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
Expand Down Expand Up @@ -1440,7 +1442,7 @@ impl<T> [T] {
#[inline]
#[track_caller]
#[must_use]
pub const fn as_rchunks<const N: usize>(&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);
Expand Down Expand Up @@ -1495,7 +1497,9 @@ impl<T> [T] {
#[inline]
#[must_use]
#[track_caller]
pub const unsafe fn as_chunks_unchecked_mut<const N: usize>(&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",
Expand Down Expand Up @@ -1549,7 +1553,9 @@ impl<T> [T] {
#[inline]
#[track_caller]
#[must_use]
pub const fn as_chunks_mut<const N: usize>(&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
Expand Down Expand Up @@ -1602,7 +1608,9 @@ impl<T> [T] {
#[inline]
#[track_caller]
#[must_use]
pub const fn as_rchunks_mut<const N: usize>(&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);
Expand Down Expand Up @@ -1643,7 +1651,9 @@ impl<T> [T] {
#[rustc_const_unstable(feature = "const_slice_make_iter", issue = "137737")]
#[inline]
#[track_caller]
pub const fn array_windows<const N: usize>(&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)
}
Expand Down
44 changes: 44 additions & 0 deletions tests/ui/lint/const-n-is-zero.rs
Original file line number Diff line number Diff line change
@@ -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::<ZERO>();
//~^ 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::<ONE>();
let _ = m.as_rchunks::<{ 1 + 1 }>();
let _ = m.as_rchunks_mut::<{ if ZERO == 1 { 0 } else { 5 } }>();
}
40 changes: 40 additions & 0 deletions tests/ui/lint/const-n-is-zero.stderr
Original file line number Diff line number Diff line change
@@ -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::<ZERO>();
| ^^^^^^^^^^^^^^^^^^^^^^^^^ 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

Loading