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::is_from_proc_macro;
use clippy_utils::macros::macro_backtrace;
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_middle::ty::layout::LayoutOf;
use rustc_middle::ty::{self, ConstKind};
@ -27,21 +28,41 @@
pub struct LargeStackArrays {
maximum_allowed_size: u128,
prev_vec_macro_callsite: Option<Span>,
}
impl LargeStackArrays {
#[must_use]
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<'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
&& !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 ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind()
&& 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
),
|diag| {
if !expr.span.from_expansion() {
if !might_be_expanded(cx, expr) {
diag.help(format!(
"consider allocating on the heap with `vec!{}.into_boxed_slice()`",
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
fn is_from_vec_macro(cx: &LateContext<'_>, expr_span: Span) -> bool {
macro_backtrace(expr_span).any(|mac| cx.tcx.is_diagnostic_item(sym::vec_macro, mac.def_id))
/// Only giving help messages if the expr does not contains macro expanded codes.
fn might_be_expanded<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> bool {
/// 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::Spacing::{self, Alone, Joint};
use proc_macro::{Group, Ident, Literal, Punct, Span, TokenStream, TokenTree as TT};
use syn::spanned::Spanned;
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(())
}
/// 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
/// 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)]
#![allow(clippy::large_enum_variant)]
extern crate proc_macros;
#[derive(Clone, Copy)]
struct S {
pub data: [u64; 32],
@ -74,12 +77,18 @@ macro_rules! dummy {
::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 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]]];
//~^ ERROR: allocating a local array larger than 512000 bytes
let y = dummy![x, x, x, x, x];
let y = [x, x, dummy!(x), x, x];
//~^ 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![[x, x, x, x, x]];
//~^ 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
--> tests/ui/large_stack_arrays.rs:29:14
--> tests/ui/large_stack_arrays.rs:32:14
|
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)]`
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()];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -17,7 +17,7 @@ LL | let _y = [build(), build(), build()];
= help: consider allocating on the heap with `vec![build(), build(), build()].into_boxed_slice()`
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],
| ^^^^^^^^^^^^^^^^^^
@ -25,7 +25,7 @@ LL | [0u32; 20_000_000],
= help: consider allocating on the heap with `vec![0u32; 20_000_000].into_boxed_slice()`
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],
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -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()`
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],
| ^^^^^^^^^^^^^^^^^^^^^^
@ -41,7 +41,7 @@ LL | [Some(""); 20_000_000],
= help: consider allocating on the heap with `vec![Some(""); 20_000_000].into_boxed_slice()`
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],
| ^^^^^^^^^^^^^^^
@ -49,7 +49,7 @@ LL | [E::T(0); 5000],
= help: consider allocating on the heap with `vec![E::T(0); 5000].into_boxed_slice()`
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],
| ^^^^^^^^^^^^^^^^^
@ -57,15 +57,7 @@ LL | [0u8; usize::MAX],
= help: consider allocating on the heap with `vec![0u8; usize::MAX].into_boxed_slice()`
error: allocating a local array larger than 512000 bytes
--> tests/ui/large_stack_arrays.rs:81:25
|
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
--> tests/ui/large_stack_arrays.rs:93:13
|
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()`
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]
| ^^^^^^^^^^^^^^^^
@ -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)
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]];
| ^^^^^^^^^^^^^^^
|
= 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