wowlab_tidy/languages/rust/rules/performance/
large_async_local.rs1use 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, ¶meter, 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});