Skip to content

Commit 799732c

Browse files
committed
Auto merge of rust-lang#10414 - csmoe:large-future, r=xFrednet
add large future lint Closes rust-lang#5263 --- changelog: new lint: [`large_futures`] [rust-lang#10414](rust-lang#10414) <!-- changelog_checked -->
2 parents ef3867f + 6e87ae0 commit 799732c

File tree

12 files changed

+287
-0
lines changed

12 files changed

+287
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4633,6 +4633,7 @@ Released 2018-09-13
46334633
[`large_const_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays
46344634
[`large_digit_groups`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_digit_groups
46354635
[`large_enum_variant`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant
4636+
[`large_futures`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_futures
46364637
[`large_include_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_include_file
46374638
[`large_stack_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_arrays
46384639
[`large_types_passed_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value

book/src/lint_configuration.md

+9
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ Please use that command to update the file and do not edit it by hand.
5454
| [allow-mixed-uninlined-format-args](#allow-mixed-uninlined-format-args) | `true` |
5555
| [suppress-restriction-lint-in-const](#suppress-restriction-lint-in-const) | `false` |
5656
| [missing-docs-in-crate-items](#missing-docs-in-crate-items) | `false` |
57+
| [future-size-threshold](#future-size-threshold) | `16384` |
5758

5859
### arithmetic-side-effects-allowed
5960
Suppress checking of the passed type names in all types of operations.
@@ -552,4 +553,12 @@ crate. For example, `pub(crate)` items.
552553
* [missing_docs_in_private_items](https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items)
553554

554555

556+
### future-size-threshold
557+
The maximum byte size a `Future` can have, before it triggers the `clippy::large_futures` lint
558+
559+
**Default Value:** `16384` (`u64`)
560+
561+
* [large_futures](https://rust-lang.github.io/rust-clippy/master/index.html#large_futures)
562+
563+
555564

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
218218
crate::iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR_INFO,
219219
crate::large_const_arrays::LARGE_CONST_ARRAYS_INFO,
220220
crate::large_enum_variant::LARGE_ENUM_VARIANT_INFO,
221+
crate::large_futures::LARGE_FUTURES_INFO,
221222
crate::large_include_file::LARGE_INCLUDE_FILE_INFO,
222223
crate::large_stack_arrays::LARGE_STACK_ARRAYS_INFO,
223224
crate::len_zero::COMPARISON_TO_EMPTY_INFO,

clippy_lints/src/large_futures.rs

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use clippy_utils::source::snippet;
2+
use clippy_utils::{diagnostics::span_lint_and_sugg, ty::implements_trait};
3+
use rustc_errors::Applicability;
4+
use rustc_hir::{Expr, ExprKind, LangItem, MatchSource, QPath};
5+
use rustc_lint::{LateContext, LateLintPass};
6+
use rustc_session::{declare_tool_lint, impl_lint_pass};
7+
use rustc_target::abi::Size;
8+
9+
declare_clippy_lint! {
10+
/// ### What it does
11+
/// It checks for the size of a `Future` created by `async fn` or `async {}`.
12+
///
13+
/// ### Why is this bad?
14+
/// Due to the current [unideal implemention](https://github.com/rust-lang/rust/issues/69826) of `Generator`,
15+
/// large size of a `Future` may cause stack overflows.
16+
///
17+
/// ### Example
18+
/// ```rust
19+
/// async fn wait(f: impl std::future::Future<Output = ()>) {}
20+
///
21+
/// async fn big_fut(arg: [u8; 1024]) {}
22+
///
23+
/// pub async fn test() {
24+
/// let fut = big_fut([0u8; 1024]);
25+
/// wait(fut).await;
26+
/// }
27+
/// ```
28+
///
29+
/// `Box::pin` the big future instead.
30+
///
31+
/// ```rust
32+
/// async fn wait(f: impl std::future::Future<Output = ()>) {}
33+
///
34+
/// async fn big_fut(arg: [u8; 1024]) {}
35+
///
36+
/// pub async fn test() {
37+
/// let fut = Box::pin(big_fut([0u8; 1024]));
38+
/// wait(fut).await;
39+
/// }
40+
/// ```
41+
#[clippy::version = "1.68.0"]
42+
pub LARGE_FUTURES,
43+
pedantic,
44+
"large future may lead to unexpected stack overflows"
45+
}
46+
47+
#[derive(Copy, Clone)]
48+
pub struct LargeFuture {
49+
future_size_threshold: u64,
50+
}
51+
52+
impl LargeFuture {
53+
pub fn new(future_size_threshold: u64) -> Self {
54+
Self { future_size_threshold }
55+
}
56+
}
57+
58+
impl_lint_pass!(LargeFuture => [LARGE_FUTURES]);
59+
60+
impl<'tcx> LateLintPass<'tcx> for LargeFuture {
61+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
62+
if matches!(expr.span.ctxt().outer_expn_data().kind, rustc_span::ExpnKind::Macro(..)) {
63+
return;
64+
}
65+
if let ExprKind::Match(expr, _, MatchSource::AwaitDesugar) = expr.kind {
66+
if let ExprKind::Call(func, [expr, ..]) = expr.kind
67+
&& let ExprKind::Path(QPath::LangItem(LangItem::IntoFutureIntoFuture, ..)) = func.kind
68+
&& let ty = cx.typeck_results().expr_ty(expr)
69+
&& let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait()
70+
&& implements_trait(cx, ty, future_trait_def_id, &[])
71+
&& let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty))
72+
&& let size = layout.layout.size()
73+
&& size >= Size::from_bytes(self.future_size_threshold)
74+
{
75+
span_lint_and_sugg(
76+
cx,
77+
LARGE_FUTURES,
78+
expr.span,
79+
&format!("large future with a size of {} bytes", size.bytes()),
80+
"consider `Box::pin` on it",
81+
format!("Box::pin({})", snippet(cx, expr.span, "..")),
82+
Applicability::Unspecified,
83+
);
84+
}
85+
}
86+
}
87+
}

clippy_lints/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ mod items_after_statements;
163163
mod iter_not_returning_iterator;
164164
mod large_const_arrays;
165165
mod large_enum_variant;
166+
mod large_futures;
166167
mod large_include_file;
167168
mod large_stack_arrays;
168169
mod len_zero;
@@ -810,6 +811,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
810811
store.register_late_pass(move |_| Box::new(dereference::Dereferencing::new(msrv())));
811812
store.register_late_pass(|_| Box::new(option_if_let_else::OptionIfLetElse));
812813
store.register_late_pass(|_| Box::new(future_not_send::FutureNotSend));
814+
let future_size_threshold = conf.future_size_threshold;
815+
store.register_late_pass(move |_| Box::new(large_futures::LargeFuture::new(future_size_threshold)));
813816
store.register_late_pass(|_| Box::new(if_let_mutex::IfLetMutex));
814817
store.register_late_pass(|_| Box::new(if_not_else::IfNotElse));
815818
store.register_late_pass(|_| Box::new(equatable_if_let::PatternEquality));

clippy_lints/src/utils/conf.rs

+4
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,10 @@ define_Conf! {
459459
/// Whether to **only** check for missing documentation in items visible within the current
460460
/// crate. For example, `pub(crate)` items.
461461
(missing_docs_in_crate_items: bool = false),
462+
/// Lint: LARGE_FUTURES.
463+
///
464+
/// The maximum byte size a `Future` can have, before it triggers the `clippy::large_futures` lint
465+
(future_size_threshold: u64 = 16 * 1024),
462466
}
463467

464468
/// Search for the configuration file.
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
future-size-threshold = 1024
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#![warn(clippy::large_futures)]
2+
3+
fn main() {}
4+
5+
pub async fn should_warn() {
6+
let x = [0u8; 1024];
7+
async {}.await;
8+
dbg!(x);
9+
}
10+
11+
pub async fn should_not_warn() {
12+
let x = [0u8; 1020];
13+
async {}.await;
14+
dbg!(x);
15+
}
16+
17+
pub async fn bar() {
18+
should_warn().await;
19+
20+
async {
21+
let x = [0u8; 1024];
22+
dbg!(x);
23+
}
24+
.await;
25+
26+
should_not_warn().await;
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: large future with a size of 1026 bytes
2+
--> $DIR/large_futures.rs:18:5
3+
|
4+
LL | should_warn().await;
5+
| ^^^^^^^^^^^^^ help: consider `Box::pin` on it: `Box::pin(should_warn())`
6+
|
7+
= note: `-D clippy::large-futures` implied by `-D warnings`
8+
9+
error: aborting due to previous error
10+

tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown fie
2424
enforced-import-renames
2525
enum-variant-name-threshold
2626
enum-variant-size-threshold
27+
future-size-threshold
2728
ignore-interior-mutability
2829
large-error-threshold
2930
literal-representation-threshold

tests/ui/large_futures.rs

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#![feature(generators)]
2+
#![warn(clippy::large_futures)]
3+
#![allow(clippy::future_not_send)]
4+
#![allow(clippy::manual_async_fn)]
5+
6+
async fn big_fut(_arg: [u8; 1024 * 16]) {}
7+
8+
async fn wait() {
9+
let f = async {
10+
big_fut([0u8; 1024 * 16]).await;
11+
};
12+
f.await
13+
}
14+
async fn calls_fut(fut: impl std::future::Future<Output = ()>) {
15+
loop {
16+
wait().await;
17+
if true {
18+
return fut.await;
19+
} else {
20+
wait().await;
21+
}
22+
}
23+
}
24+
25+
pub async fn test() {
26+
let fut = big_fut([0u8; 1024 * 16]);
27+
foo().await;
28+
calls_fut(fut).await;
29+
}
30+
31+
pub fn foo() -> impl std::future::Future<Output = ()> {
32+
async {
33+
let x = [0i32; 1024 * 16];
34+
async {}.await;
35+
dbg!(x);
36+
}
37+
}
38+
39+
pub async fn lines() {
40+
async {
41+
let x = [0i32; 1024 * 16];
42+
async {}.await;
43+
println!("{:?}", x);
44+
}
45+
.await;
46+
}
47+
48+
pub async fn macro_expn() {
49+
macro_rules! macro_ {
50+
() => {
51+
async {
52+
let x = [0i32; 1024 * 16];
53+
async {}.await;
54+
println!("macro: {:?}", x);
55+
}
56+
};
57+
}
58+
macro_!().await
59+
}
60+
61+
fn main() {}

tests/ui/large_futures.stderr

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
error: large future with a size of 16385 bytes
2+
--> $DIR/large_futures.rs:10:9
3+
|
4+
LL | big_fut([0u8; 1024 * 16]).await;
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider `Box::pin` on it: `Box::pin(big_fut([0u8; 1024 * 16]))`
6+
|
7+
= note: `-D clippy::large-futures` implied by `-D warnings`
8+
9+
error: large future with a size of 16386 bytes
10+
--> $DIR/large_futures.rs:12:5
11+
|
12+
LL | f.await
13+
| ^ help: consider `Box::pin` on it: `Box::pin(f)`
14+
15+
error: large future with a size of 16387 bytes
16+
--> $DIR/large_futures.rs:16:9
17+
|
18+
LL | wait().await;
19+
| ^^^^^^ help: consider `Box::pin` on it: `Box::pin(wait())`
20+
21+
error: large future with a size of 16387 bytes
22+
--> $DIR/large_futures.rs:20:13
23+
|
24+
LL | wait().await;
25+
| ^^^^^^ help: consider `Box::pin` on it: `Box::pin(wait())`
26+
27+
error: large future with a size of 65540 bytes
28+
--> $DIR/large_futures.rs:27:5
29+
|
30+
LL | foo().await;
31+
| ^^^^^ help: consider `Box::pin` on it: `Box::pin(foo())`
32+
33+
error: large future with a size of 49159 bytes
34+
--> $DIR/large_futures.rs:28:5
35+
|
36+
LL | calls_fut(fut).await;
37+
| ^^^^^^^^^^^^^^ help: consider `Box::pin` on it: `Box::pin(calls_fut(fut))`
38+
39+
error: large future with a size of 65540 bytes
40+
--> $DIR/large_futures.rs:40:5
41+
|
42+
LL | / async {
43+
LL | | let x = [0i32; 1024 * 16];
44+
LL | | async {}.await;
45+
LL | | println!("{:?}", x);
46+
LL | | }
47+
| |_____^
48+
|
49+
help: consider `Box::pin` on it
50+
|
51+
LL ~ Box::pin(async {
52+
LL + let x = [0i32; 1024 * 16];
53+
LL + async {}.await;
54+
LL + println!("{:?}", x);
55+
LL + })
56+
|
57+
58+
error: large future with a size of 65540 bytes
59+
--> $DIR/large_futures.rs:51:13
60+
|
61+
LL | / async {
62+
LL | | let x = [0i32; 1024 * 16];
63+
LL | | async {}.await;
64+
LL | | println!("macro: {:?}", x);
65+
LL | | }
66+
| |_____________^
67+
...
68+
LL | macro_!().await
69+
| --------- in this macro invocation
70+
|
71+
= note: this error originates in the macro `macro_` (in Nightly builds, run with -Z macro-backtrace for more info)
72+
help: consider `Box::pin` on it
73+
|
74+
LL ~ Box::pin(async {
75+
LL + let x = [0i32; 1024 * 16];
76+
LL + async {}.await;
77+
LL + println!("macro: {:?}", x);
78+
LL + })
79+
|
80+
81+
error: aborting due to 8 previous errors
82+

0 commit comments

Comments
 (0)