diff --git a/compiler/rustc_codegen_ssa/src/mir/mod.rs b/compiler/rustc_codegen_ssa/src/mir/mod.rs index 62f69af3f2f75..7d139050288e1 100644 --- a/compiler/rustc_codegen_ssa/src/mir/mod.rs +++ b/compiler/rustc_codegen_ssa/src/mir/mod.rs @@ -1,5 +1,6 @@ use std::iter; +use rustc_hir::CRATE_HIR_ID; use rustc_index::IndexVec; use rustc_index::bit_set::BitSet; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; @@ -7,6 +8,7 @@ use rustc_middle::mir::{UnwindTerminateReason, traversal}; use rustc_middle::ty::layout::{FnAbiOf, HasTyCtxt, HasTypingEnv, TyAndLayout}; use rustc_middle::ty::{self, Instance, Ty, TyCtxt, TypeFoldable, TypeVisitableExt}; use rustc_middle::{bug, mir, span_bug}; +use rustc_session::lint; use rustc_target::callconv::{FnAbi, PassMode}; use tracing::{debug, instrument}; @@ -30,6 +32,8 @@ use self::debuginfo::{FunctionDebugContext, PerLocalVarDebugInfo}; use self::operand::{OperandRef, OperandValue}; use self::place::PlaceRef; +const MIN_DANGEROUS_ALLOC_SIZE: u64 = 1024 * 1024 * 1024 * 1; // 1 GB + // Used for tracking the state of generated basic blocks. enum CachedLlbb { /// Nothing created yet. @@ -245,6 +249,21 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( let layout = start_bx.layout_of(fx.monomorphize(decl.ty)); assert!(!layout.ty.has_erasable_regions()); + if layout.size.bytes() >= MIN_DANGEROUS_ALLOC_SIZE { + let (size_quantity, size_unit) = human_readable_bytes(layout.size.bytes()); + cx.tcx().node_span_lint( + lint::builtin::DANGEROUS_STACK_ALLOCATION, + CRATE_HIR_ID, + decl.source_info.span, + |lint| { + lint.primary_message(format!( + "allocation of size: {:.2} {} exceeds most system architecture limits", + size_quantity, size_unit + )); + }, + ); + } + if local == mir::RETURN_PLACE { match fx.fn_abi.ret.mode { PassMode::Indirect { .. } => { @@ -310,6 +329,14 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( } } +/// Formats a number of bytes into a human readable SI-prefixed size. +/// Returns a tuple of `(quantity, units)`. +pub fn human_readable_bytes(bytes: u64) -> (u64, &'static str) { + static UNITS: [&str; 7] = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"]; + let i = ((bytes.checked_ilog2().unwrap_or(0) / 10) as usize).min(UNITS.len() - 1); + (bytes >> (10 * i), UNITS[i]) +} + /// Produces, for each argument, a `Value` pointing at the /// argument's value. As arguments are places, these are always /// indirect. diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 54e927df3c42b..08fea927cc22c 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -32,6 +32,7 @@ declare_lint_pass! { CONFLICTING_REPR_HINTS, CONST_EVALUATABLE_UNCHECKED, CONST_ITEM_MUTATION, + DANGEROUS_STACK_ALLOCATION, DEAD_CODE, DEPENDENCY_ON_UNIT_NEVER_TYPE_FALLBACK, DEPRECATED, @@ -711,6 +712,50 @@ declare_lint! { "detect assignments that will never be read" } +declare_lint! { + /// The `dangerous_stack_allocation` lint detects stack allocations that + /// are 1 GB or more. + /// + /// ### Example + /// + /// ``` rust + /// fn func() { + /// const CAP: usize = std::u32::MAX as usize; + /// let mut x: [u8; CAP>>1] = [0; CAP>>1]; + /// x[2] = 123; + /// println!("{}", x[2]); + /// } + /// + /// fn main() { + /// std::thread::Builder::new() + /// .stack_size(3 * 1024 * 1024 * 1024) + /// .spawn(func) + /// .unwrap() + /// .join() + /// .unwrap(); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ```text + /// warning: allocation of size: 1 GiB exceeds most system architecture limits + /// --> $DIR/large-stack-size-issue-83060.rs:7:9 + /// | + /// LL | let mut x: [u8; CAP>>1] = [0; CAP>>1]; + /// | ^^^^^ + /// | + /// = note: `#[warn(dangerous_stack_allocation)]` on by default + /// ``` + /// ### Explanation + /// + /// Large arrays may cause stack overflow due to the limited size of the + /// stack on most platforms. + pub DANGEROUS_STACK_ALLOCATION, + Warn, + "detects dangerously large stack allocations at the limit of most architectures" +} + declare_lint! { /// The `dead_code` lint detects unused, unexported items. /// diff --git a/tests/ui/sanitizer/large-stack-size-issue-83060.rs b/tests/ui/sanitizer/large-stack-size-issue-83060.rs new file mode 100644 index 0000000000000..297b4bff29a21 --- /dev/null +++ b/tests/ui/sanitizer/large-stack-size-issue-83060.rs @@ -0,0 +1,21 @@ +// This test checks that allocating a stack size of 1GB or more results in a warning +//@build-pass +//@ only-64bit + +fn func() { + const CAP: usize = std::u32::MAX as usize; + let mut x: [u8; CAP>>1] = [0; CAP>>1]; + //~^ warning: allocation of size: 1 GiB exceeds most system architecture limits + //~| NOTE on by default + x[2] = 123; + println!("{}", x[2]); +} + +fn main() { + std::thread::Builder::new() + .stack_size(3 * 1024 * 1024 * 1024) + .spawn(func) + .unwrap() + .join() + .unwrap(); +} diff --git a/tests/ui/sanitizer/large-stack-size-issue-83060.stderr b/tests/ui/sanitizer/large-stack-size-issue-83060.stderr new file mode 100644 index 0000000000000..da28cc7021827 --- /dev/null +++ b/tests/ui/sanitizer/large-stack-size-issue-83060.stderr @@ -0,0 +1,10 @@ +warning: allocation of size: 1 GiB exceeds most system architecture limits + --> $DIR/large-stack-size-issue-83060.rs:7:9 + | +LL | let mut x: [u8; CAP>>1] = [0; CAP>>1]; + | ^^^^^ + | + = note: `#[warn(dangerous_stack_allocation)]` on by default + +warning: 1 warning emitted +