Skip to main content

wowlab_tidy/languages/rust/rules/performance/
large_async_local.rs

1use ra_ap_syntax::ast;
2
3use super::support;
4use crate::{AstCtx, Example, Violation};
5
6#[rustfmt::skip]
7const EXAMPLES: &[Example] = &[
8    Example {
9        label: "large repeat-literal array in async fn",
10        code: "async fn f() { let buf = [0u8; 2048]; consume(&buf).await; }",
11        pass: false,
12    },
13    Example {
14        label: "large annotated array local in async fn",
15        code: "async fn f() { let buf: [u64; 512] = [0; 512]; consume(&buf).await; }",
16        pass: false,
17    },
18    Example {
19        label: "large by-value array parameter on async fn",
20        code: "async fn f(buf: [u8; 4096]) { consume(&buf).await; }",
21        pass: false,
22    },
23    Example {
24        label: "large array local in async block",
25        code: "fn g() { let _fut = async { let buf = [0u8; 2048]; consume(&buf).await; }; }",
26        pass: false,
27    },
28    Example {
29        label: "small array local in async fn",
30        code: "async fn f() { let buf = [0u8; 128]; consume(&buf).await; }",
31        pass: true,
32    },
33    Example {
34        label: "sync fn is rust_large_stack_array territory",
35        code: "fn f() { let _buf = [0u8; 4096]; }",
36        pass: true,
37    },
38    Example {
39        label: "array behind a reference parameter",
40        code: "async fn f(buf: &[u8; 4096]) { consume(buf).await; }",
41        pass: true,
42    },
43    Example {
44        label: "large array inside sync closure in async fn",
45        code: "async fn f() { let g = || { let _buf = [0u8; 4096]; }; g(); }",
46        pass: true,
47    },
48    Example {
49        label: "large async local in test module",
50        code: "#[cfg(test)]\nmod tests {\n    async fn t() { let _buf = [0u8; 2048]; }\n}",
51        pass: true,
52    },
53];
54
55crate::ast_rule!(
56    large_async_local,
57    "Flag by-value `[T; N]` locals and parameters over threshold bytes inside async fns and blocks.",
58    "Async locals and parameters embed in the future's state machine, inflating every task and spawn memcpy.",
59    Medium,
60    params {
61        threshold: i64 = 1024
62    },
63);
64
65fn check_large_async_local(ctx: &AstCtx<'_>) -> Vec<Violation> {
66    let threshold = ctx
67        .file
68        .config
69        .get_u64("rust_large_async_local", &PARAMS[0]);
70    let locals = ctx
71        .nodes::<ast::LetStmt>()
72        .filter(|local| !ctx.is_in_test(local) && support::is_in_async_context(local))
73        .filter_map(|local| {
74            let size = local_array_size(&local)?;
75
76            (size > threshold).then(|| {
77                ctx.violation(
78                    &local,
79                    format!(
80                        "large array local ({size} bytes) in async context — lives in the future state machine; Box it or shrink it"
81                    ),
82                )
83            })
84        });
85    let parameters = ctx
86        .nodes::<ast::Fn>()
87        .filter(|function| {
88            !ctx.is_in_test(function)
89                && function.body().is_some()
90                && function.async_token().is_some()
91        })
92        .flat_map(|function| {
93            function
94                .param_list()
95                .into_iter()
96                .flat_map(|parameters| parameters.params())
97                .filter_map(move |parameter| param_violation(ctx, &parameter, threshold))
98        });
99
100    locals.chain(parameters).collect()
101}
102
103fn local_array_size(local: &ast::LetStmt) -> Option<u64> {
104    if let Some(ty @ ast::Type::ArrayType(_)) = local.ty() {
105        return support::estimate_type_size(&ty);
106    }
107
108    let ast::Expr::ArrayExpr(array) = local.initializer()? else {
109        return None;
110    };
111
112    array.semicolon_token()?;
113
114    let mut expressions = array.exprs();
115    let element = expressions.next()?;
116    let length = expressions.next()?;
117
118    Some(support::literal_elem_size(&element)?.saturating_mul(support::parse_int_expr(&length)?))
119}
120
121fn param_violation(ctx: &AstCtx<'_>, parameter: &ast::Param, threshold: u64) -> Option<Violation> {
122    let ty @ ast::Type::ArrayType(_) = parameter.ty()? else {
123        return None;
124    };
125    let size = support::estimate_type_size(&ty)?;
126
127    if size <= threshold {
128        return None;
129    }
130
131    Some(ctx.violation(
132        parameter,
133        format!(
134            "by-value array parameter ({size} bytes) on async fn — embedded in every future instance; pass a reference or Box"
135        ),
136    ))
137}
138
139crate::tidy_ast_test!(check_large_async_local, {
140    crate::example_tests!(EXAMPLES, check_large_async_local);
141});