test [large_stack_arrays] with proc-macro and make sure not to offer false help messages if in one.

This commit is contained in:
J-ZhengLi 2024-04-17 18:10:50 +08:00
parent 666e2f2868
commit 2861729dad
4 changed files with 102 additions and 28 deletions

View File

@ -1,7 +1,8 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_from_proc_macro;
use clippy_utils::macros::macro_backtrace; use clippy_utils::macros::macro_backtrace;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node}; use rustc_hir::{ArrayLen, Expr, ExprKind, Item, ItemKind, Node};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::layout::LayoutOf;
use rustc_middle::ty::{self, ConstKind}; use rustc_middle::ty::{self, ConstKind};
@ -27,21 +28,41 @@
pub struct LargeStackArrays { pub struct LargeStackArrays {
maximum_allowed_size: u128, maximum_allowed_size: u128,
prev_vec_macro_callsite: Option<Span>,
} }
impl LargeStackArrays { impl LargeStackArrays {
#[must_use] #[must_use]
pub fn new(maximum_allowed_size: u128) -> Self { pub fn new(maximum_allowed_size: u128) -> Self {
Self { maximum_allowed_size } Self {
maximum_allowed_size,
prev_vec_macro_callsite: None,
}
}
/// Check if the given span of an expr is already in a `vec!` call.
fn is_from_vec_macro(&mut self, cx: &LateContext<'_>, span: Span) -> bool {
// First, we check if this is span is within the last encountered `vec!` macro's root callsite.
self.prev_vec_macro_callsite
.is_some_and(|vec_mac| vec_mac.contains(span))
|| {
// Then, we try backtracking the macro expansions, to see if there's a `vec!` macro,
// and update the `prev_vec_macro_callsite`.
let res = macro_backtrace(span).any(|mac| cx.tcx.is_diagnostic_item(sym::vec_macro, mac.def_id));
if res {
self.prev_vec_macro_callsite = Some(span.source_callsite());
}
res
}
} }
} }
impl_lint_pass!(LargeStackArrays => [LARGE_STACK_ARRAYS]); impl_lint_pass!(LargeStackArrays => [LARGE_STACK_ARRAYS]);
impl<'tcx> LateLintPass<'tcx> for LargeStackArrays { impl<'tcx> LateLintPass<'tcx> for LargeStackArrays {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if let ExprKind::Repeat(_, _) | ExprKind::Array(_) = expr.kind if let ExprKind::Repeat(_, _) | ExprKind::Array(_) = expr.kind
&& !is_from_vec_macro(cx, expr.span) && !self.is_from_vec_macro(cx, expr.span)
&& let ty::Array(element_type, cst) = cx.typeck_results().expr_ty(expr).kind() && let ty::Array(element_type, cst) = cx.typeck_results().expr_ty(expr).kind()
&& let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind() && let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind()
&& let Ok(element_count) = element_count.try_to_target_usize(cx.tcx) && let Ok(element_count) = element_count.try_to_target_usize(cx.tcx)
@ -66,7 +87,7 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
self.maximum_allowed_size self.maximum_allowed_size
), ),
|diag| { |diag| {
if !expr.span.from_expansion() { if !might_be_expanded(cx, expr) {
diag.help(format!( diag.help(format!(
"consider allocating on the heap with `vec!{}.into_boxed_slice()`", "consider allocating on the heap with `vec!{}.into_boxed_slice()`",
snippet(cx, expr.span, "[...]") snippet(cx, expr.span, "[...]")
@ -78,7 +99,20 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
} }
} }
/// We shouldn't lint messages if the expr is already in a `vec!` call /// Only giving help messages if the expr does not contains macro expanded codes.
fn is_from_vec_macro(cx: &LateContext<'_>, expr_span: Span) -> bool { fn might_be_expanded<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> bool {
macro_backtrace(expr_span).any(|mac| cx.tcx.is_diagnostic_item(sym::vec_macro, mac.def_id)) /// Check if the span of `ArrayLen` of a repeat expression is within the expr's span,
/// if not, meaning this repeat expr is definitely from some proc-macro.
///
/// This is a fail-safe to a case where even the `is_from_proc_macro` is unable to determain the
/// correct result.
fn repeat_expr_might_be_expanded<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> bool {
let ExprKind::Repeat(_, ArrayLen::Body(anon_const)) = expr.kind else {
return false;
};
let len_span = cx.tcx.def_span(anon_const.def_id);
!expr.span.contains(len_span)
}
expr.span.from_expansion() || is_from_proc_macro(cx, expr) || repeat_expr_might_be_expanded(cx, expr)
} }

View File

@ -9,6 +9,7 @@
use proc_macro::Delimiter::{self, Brace, Parenthesis}; use proc_macro::Delimiter::{self, Brace, Parenthesis};
use proc_macro::Spacing::{self, Alone, Joint}; use proc_macro::Spacing::{self, Alone, Joint};
use proc_macro::{Group, Ident, Literal, Punct, Span, TokenStream, TokenTree as TT}; use proc_macro::{Group, Ident, Literal, Punct, Span, TokenStream, TokenTree as TT};
use syn::spanned::Spanned;
type Result<T> = core::result::Result<T, TokenStream>; type Result<T> = core::result::Result<T, TokenStream>;
@ -124,6 +125,22 @@ fn write_with_span(s: Span, mut input: IntoIter, out: &mut TokenStream) -> Resul
Ok(()) Ok(())
} }
/// Takes an array repeat expression such as `[0_u32; 2]`, and return the tokens with 10 times the
/// original size, which turns to `[0_u32; 20]`.
#[proc_macro]
pub fn make_it_big(input: TokenStream) -> TokenStream {
let mut expr_repeat = syn::parse_macro_input!(input as syn::ExprRepeat);
let len_span = expr_repeat.len.span();
if let syn::Expr::Lit(expr_lit) = &mut *expr_repeat.len {
if let syn::Lit::Int(lit_int) = &expr_lit.lit {
let orig_val = lit_int.base10_parse::<usize>().expect("not a valid length parameter");
let new_val = orig_val.saturating_mul(10);
expr_lit.lit = syn::parse_quote_spanned!( len_span => #new_val);
}
}
quote::quote!(#expr_repeat).into()
}
/// Within the item this attribute is attached to, an `inline!` macro is available which expands the /// Within the item this attribute is attached to, an `inline!` macro is available which expands the
/// contained tokens as though they came from a macro expansion. /// contained tokens as though they came from a macro expansion.
/// ///

View File

@ -1,6 +1,9 @@
//@aux-build:proc_macros.rs
#![warn(clippy::large_stack_arrays)] #![warn(clippy::large_stack_arrays)]
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant)]
extern crate proc_macros;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
struct S { struct S {
pub data: [u64; 32], pub data: [u64; 32],
@ -74,12 +77,18 @@ macro_rules! dummy {
::std::vec![$($id),*] ::std::vec![$($id),*]
} }
} }
macro_rules! create_then_move {
($id:ident; $n:literal) => {{
let _x_ = [$id; $n];
//~^ ERROR: allocating a local array larger than 512000 bytes
_x_
}};
}
let x = [0u32; 50_000]; let x = [0u32; 50_000];
let y = vec![x, x, x, x, x]; let y = vec![x, x, x, x, x];
let y = vec![dummy![x, x, x, x, x]]; let y = vec![dummy![x, x, x, x, x]];
let y = vec![dummy![[x, x, x, x, x]]]; let y = vec![dummy![[x, x, x, x, x]]];
//~^ ERROR: allocating a local array larger than 512000 bytes
let y = dummy![x, x, x, x, x]; let y = dummy![x, x, x, x, x];
let y = [x, x, dummy!(x), x, x]; let y = [x, x, dummy!(x), x, x];
//~^ ERROR: allocating a local array larger than 512000 bytes //~^ ERROR: allocating a local array larger than 512000 bytes
@ -88,4 +97,9 @@ macro_rules! dummy {
let y = dummy!(vec![dummy![x, x, x, x, x]]); let y = dummy!(vec![dummy![x, x, x, x, x]]);
let y = dummy![[x, x, x, x, x]]; let y = dummy![[x, x, x, x, x]];
//~^ ERROR: allocating a local array larger than 512000 bytes //~^ ERROR: allocating a local array larger than 512000 bytes
let y = proc_macros::make_it_big!([x; 1]);
//~^ ERROR: allocating a local array larger than 512000 bytes
let y = vec![proc_macros::make_it_big!([x; 10])];
let y = vec![create_then_move![x; 5]; 5];
} }

View File

@ -1,5 +1,5 @@
error: allocating a local array larger than 512000 bytes error: allocating a local array larger than 512000 bytes
--> tests/ui/large_stack_arrays.rs:29:14 --> tests/ui/large_stack_arrays.rs:32:14
| |
LL | let _x = [build(); 3]; LL | let _x = [build(); 3];
| ^^^^^^^^^^^^ | ^^^^^^^^^^^^
@ -9,7 +9,7 @@ LL | let _x = [build(); 3];
= help: to override `-D warnings` add `#[allow(clippy::large_stack_arrays)]` = help: to override `-D warnings` add `#[allow(clippy::large_stack_arrays)]`
error: allocating a local array larger than 512000 bytes error: allocating a local array larger than 512000 bytes
--> tests/ui/large_stack_arrays.rs:32:14 --> tests/ui/large_stack_arrays.rs:35:14
| |
LL | let _y = [build(), build(), build()]; LL | let _y = [build(), build(), build()];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -17,7 +17,7 @@ LL | let _y = [build(), build(), build()];
= help: consider allocating on the heap with `vec![build(), build(), build()].into_boxed_slice()` = help: consider allocating on the heap with `vec![build(), build(), build()].into_boxed_slice()`
error: allocating a local array larger than 512000 bytes error: allocating a local array larger than 512000 bytes
--> tests/ui/large_stack_arrays.rs:38:9 --> tests/ui/large_stack_arrays.rs:41:9
| |
LL | [0u32; 20_000_000], LL | [0u32; 20_000_000],
| ^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^
@ -25,7 +25,7 @@ LL | [0u32; 20_000_000],
= help: consider allocating on the heap with `vec![0u32; 20_000_000].into_boxed_slice()` = help: consider allocating on the heap with `vec![0u32; 20_000_000].into_boxed_slice()`
error: allocating a local array larger than 512000 bytes error: allocating a local array larger than 512000 bytes
--> tests/ui/large_stack_arrays.rs:40:9 --> tests/ui/large_stack_arrays.rs:43:9
| |
LL | [S { data: [0; 32] }; 5000], LL | [S { data: [0; 32] }; 5000],
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -33,7 +33,7 @@ LL | [S { data: [0; 32] }; 5000],
= help: consider allocating on the heap with `vec![S { data: [0; 32] }; 5000].into_boxed_slice()` = help: consider allocating on the heap with `vec![S { data: [0; 32] }; 5000].into_boxed_slice()`
error: allocating a local array larger than 512000 bytes error: allocating a local array larger than 512000 bytes
--> tests/ui/large_stack_arrays.rs:42:9 --> tests/ui/large_stack_arrays.rs:45:9
| |
LL | [Some(""); 20_000_000], LL | [Some(""); 20_000_000],
| ^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^
@ -41,7 +41,7 @@ LL | [Some(""); 20_000_000],
= help: consider allocating on the heap with `vec![Some(""); 20_000_000].into_boxed_slice()` = help: consider allocating on the heap with `vec![Some(""); 20_000_000].into_boxed_slice()`
error: allocating a local array larger than 512000 bytes error: allocating a local array larger than 512000 bytes
--> tests/ui/large_stack_arrays.rs:44:9 --> tests/ui/large_stack_arrays.rs:47:9
| |
LL | [E::T(0); 5000], LL | [E::T(0); 5000],
| ^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^
@ -49,7 +49,7 @@ LL | [E::T(0); 5000],
= help: consider allocating on the heap with `vec![E::T(0); 5000].into_boxed_slice()` = help: consider allocating on the heap with `vec![E::T(0); 5000].into_boxed_slice()`
error: allocating a local array larger than 512000 bytes error: allocating a local array larger than 512000 bytes
--> tests/ui/large_stack_arrays.rs:46:9 --> tests/ui/large_stack_arrays.rs:49:9
| |
LL | [0u8; usize::MAX], LL | [0u8; usize::MAX],
| ^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^
@ -57,15 +57,7 @@ LL | [0u8; usize::MAX],
= help: consider allocating on the heap with `vec![0u8; usize::MAX].into_boxed_slice()` = help: consider allocating on the heap with `vec![0u8; usize::MAX].into_boxed_slice()`
error: allocating a local array larger than 512000 bytes error: allocating a local array larger than 512000 bytes
--> tests/ui/large_stack_arrays.rs:81:25 --> tests/ui/large_stack_arrays.rs:93:13
|
LL | let y = vec![dummy![[x, x, x, x, x]]];
| ^^^^^^^^^^^^^^^
|
= help: consider allocating on the heap with `vec![x, x, x, x, x].into_boxed_slice()`
error: allocating a local array larger than 512000 bytes
--> tests/ui/large_stack_arrays.rs:84:13
| |
LL | let y = [x, x, dummy!(x), x, x]; LL | let y = [x, x, dummy!(x), x, x];
| ^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^
@ -73,7 +65,7 @@ LL | let y = [x, x, dummy!(x), x, x];
= help: consider allocating on the heap with `vec![x, x, dummy!(x), x, x].into_boxed_slice()` = help: consider allocating on the heap with `vec![x, x, dummy!(x), x, x].into_boxed_slice()`
error: allocating a local array larger than 512000 bytes error: allocating a local array larger than 512000 bytes
--> tests/ui/large_stack_arrays.rs:67:13 --> tests/ui/large_stack_arrays.rs:70:13
| |
LL | [$a, $b, $a, $b] LL | [$a, $b, $a, $b]
| ^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^
@ -84,12 +76,29 @@ LL | let y = dummy![x => x];
= note: this error originates in the macro `dummy` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the macro `dummy` (in Nightly builds, run with -Z macro-backtrace for more info)
error: allocating a local array larger than 512000 bytes error: allocating a local array larger than 512000 bytes
--> tests/ui/large_stack_arrays.rs:89:20 --> tests/ui/large_stack_arrays.rs:98:20
| |
LL | let y = dummy![[x, x, x, x, x]]; LL | let y = dummy![[x, x, x, x, x]];
| ^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^
| |
= help: consider allocating on the heap with `vec![x, x, x, x, x].into_boxed_slice()` = help: consider allocating on the heap with `vec![x, x, x, x, x].into_boxed_slice()`
error: aborting due to 11 previous errors error: allocating a local array larger than 512000 bytes
--> tests/ui/large_stack_arrays.rs:101:39
|
LL | let y = proc_macros::make_it_big!([x; 1]);
| ^^^^^^
error: allocating a local array larger than 512000 bytes
--> tests/ui/large_stack_arrays.rs:82:23
|
LL | let _x_ = [$id; $n];
| ^^^^^^^^^
...
LL | let y = vec![create_then_move![x; 5]; 5];
| ----------------------- in this macro invocation
|
= note: this error originates in the macro `create_then_move` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 12 previous errors