]>
Commit | Line | Data |
---|---|---|
31ef2f64 FG |
1 | use clippy_utils::diagnostics::span_lint_and_then; |
2 | use clippy_utils::is_from_proc_macro; | |
3 | use clippy_utils::macros::macro_backtrace; | |
cdc7bbd5 | 4 | use clippy_utils::source::snippet; |
31ef2f64 | 5 | use rustc_hir::{ArrayLen, Expr, ExprKind, Item, ItemKind, Node}; |
f20569fa | 6 | use rustc_lint::{LateContext, LateLintPass}; |
c295e0f8 | 7 | use rustc_middle::ty::layout::LayoutOf; |
f20569fa | 8 | use rustc_middle::ty::{self, ConstKind}; |
4b012472 | 9 | use rustc_session::impl_lint_pass; |
31ef2f64 | 10 | use rustc_span::{sym, Span}; |
f20569fa | 11 | |
f20569fa | 12 | declare_clippy_lint! { |
94222f64 XL |
13 | /// ### What it does |
14 | /// Checks for local arrays that may be too large. | |
f20569fa | 15 | /// |
94222f64 XL |
16 | /// ### Why is this bad? |
17 | /// Large local arrays may cause stack overflow. | |
f20569fa | 18 | /// |
94222f64 | 19 | /// ### Example |
f20569fa XL |
20 | /// ```rust,ignore |
21 | /// let a = [0u32; 1_000_000]; | |
22 | /// ``` | |
a2a8927a | 23 | #[clippy::version = "1.41.0"] |
f20569fa XL |
24 | pub LARGE_STACK_ARRAYS, |
25 | pedantic, | |
26 | "allocating large arrays on stack may cause stack overflow" | |
27 | } | |
28 | ||
29 | pub struct LargeStackArrays { | |
9c376795 | 30 | maximum_allowed_size: u128, |
31ef2f64 | 31 | prev_vec_macro_callsite: Option<Span>, |
f20569fa XL |
32 | } |
33 | ||
34 | impl LargeStackArrays { | |
35 | #[must_use] | |
9c376795 | 36 | pub fn new(maximum_allowed_size: u128) -> Self { |
31ef2f64 FG |
37 | Self { |
38 | maximum_allowed_size, | |
39 | prev_vec_macro_callsite: None, | |
40 | } | |
41 | } | |
42 | ||
43 | /// Check if the given span of an expr is already in a `vec!` call. | |
44 | fn is_from_vec_macro(&mut self, cx: &LateContext<'_>, span: Span) -> bool { | |
45 | // First, we check if this is span is within the last encountered `vec!` macro's root callsite. | |
46 | self.prev_vec_macro_callsite | |
47 | .is_some_and(|vec_mac| vec_mac.contains(span)) | |
48 | || { | |
49 | // Then, we try backtracking the macro expansions, to see if there's a `vec!` macro, | |
50 | // and update the `prev_vec_macro_callsite`. | |
51 | let res = macro_backtrace(span).any(|mac| cx.tcx.is_diagnostic_item(sym::vec_macro, mac.def_id)); | |
52 | if res { | |
53 | self.prev_vec_macro_callsite = Some(span.source_callsite()); | |
54 | } | |
55 | res | |
56 | } | |
f20569fa XL |
57 | } |
58 | } | |
59 | ||
60 | impl_lint_pass!(LargeStackArrays => [LARGE_STACK_ARRAYS]); | |
61 | ||
62 | impl<'tcx> LateLintPass<'tcx> for LargeStackArrays { | |
31ef2f64 | 63 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { |
fe692bf9 | 64 | if let ExprKind::Repeat(_, _) | ExprKind::Array(_) = expr.kind |
31ef2f64 | 65 | && !self.is_from_vec_macro(cx, expr.span) |
ed00b5ec | 66 | && let ty::Array(element_type, cst) = cx.typeck_results().expr_ty(expr).kind() |
31ef2f64 | 67 | && let ConstKind::Value(_, ty::ValTree::Leaf(element_count)) = cst.kind() |
ed00b5ec FG |
68 | && let Ok(element_count) = element_count.try_to_target_usize(cx.tcx) |
69 | && let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes()) | |
70 | && !cx.tcx.hir().parent_iter(expr.hir_id).any(|(_, node)| { | |
71 | matches!( | |
72 | node, | |
73 | Node::Item(Item { | |
74 | kind: ItemKind::Static(..), | |
75 | .. | |
76 | }) | |
77 | ) | |
78 | }) | |
79 | && self.maximum_allowed_size < u128::from(element_count) * u128::from(element_size) | |
80 | { | |
31ef2f64 | 81 | span_lint_and_then( |
ed00b5ec FG |
82 | cx, |
83 | LARGE_STACK_ARRAYS, | |
84 | expr.span, | |
e8be2606 | 85 | format!( |
ed00b5ec FG |
86 | "allocating a local array larger than {} bytes", |
87 | self.maximum_allowed_size | |
88 | ), | |
31ef2f64 FG |
89 | |diag| { |
90 | if !might_be_expanded(cx, expr) { | |
91 | diag.help(format!( | |
92 | "consider allocating on the heap with `vec!{}.into_boxed_slice()`", | |
93 | snippet(cx, expr.span, "[...]") | |
94 | )); | |
95 | } | |
96 | }, | |
ed00b5ec FG |
97 | ); |
98 | } | |
f20569fa XL |
99 | } |
100 | } | |
31ef2f64 FG |
101 | |
102 | /// Only giving help messages if the expr does not contains macro expanded codes. | |
103 | fn might_be_expanded<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> bool { | |
104 | /// Check if the span of `ArrayLen` of a repeat expression is within the expr's span, | |
105 | /// if not, meaning this repeat expr is definitely from some proc-macro. | |
106 | /// | |
107 | /// This is a fail-safe to a case where even the `is_from_proc_macro` is unable to determain the | |
108 | /// correct result. | |
109 | fn repeat_expr_might_be_expanded<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> bool { | |
110 | let ExprKind::Repeat(_, ArrayLen::Body(anon_const)) = expr.kind else { | |
111 | return false; | |
112 | }; | |
113 | let len_span = cx.tcx.def_span(anon_const.def_id); | |
114 | !expr.span.contains(len_span) | |
115 | } | |
116 | ||
117 | expr.span.from_expansion() || is_from_proc_macro(cx, expr) || repeat_expr_might_be_expanded(cx, expr) | |
118 | } |