Skip to content

Commit

Permalink
Auto merge of rust-lang#10414 - csmoe:large-future, r=xFrednet
Browse files Browse the repository at this point in the history
add large future lint

Closes rust-lang#5263

---

changelog: new lint: [`large_futures`]
[rust-lang#10414](rust-lang/rust-clippy#10414)
<!-- changelog_checked -->
  • Loading branch information
bors committed Mar 30, 2023
2 parents ef3867f + 6e87ae0 commit 799732c
Show file tree
Hide file tree
Showing 12 changed files with 287 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4633,6 +4633,7 @@ Released 2018-09-13
[`large_const_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays
[`large_digit_groups`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_digit_groups
[`large_enum_variant`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant
[`large_futures`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_futures
[`large_include_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_include_file
[`large_stack_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_arrays
[`large_types_passed_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value
Expand Down
9 changes: 9 additions & 0 deletions book/src/lint_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Please use that command to update the file and do not edit it by hand.
| [allow-mixed-uninlined-format-args](#allow-mixed-uninlined-format-args) | `true` |
| [suppress-restriction-lint-in-const](#suppress-restriction-lint-in-const) | `false` |
| [missing-docs-in-crate-items](#missing-docs-in-crate-items) | `false` |
| [future-size-threshold](#future-size-threshold) | `16384` |

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


### future-size-threshold
The maximum byte size a `Future` can have, before it triggers the `clippy::large_futures` lint

**Default Value:** `16384` (`u64`)

* [large_futures](https://rust-lang.github.io/rust-clippy/master/index.html#large_futures)



1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR_INFO,
crate::large_const_arrays::LARGE_CONST_ARRAYS_INFO,
crate::large_enum_variant::LARGE_ENUM_VARIANT_INFO,
crate::large_futures::LARGE_FUTURES_INFO,
crate::large_include_file::LARGE_INCLUDE_FILE_INFO,
crate::large_stack_arrays::LARGE_STACK_ARRAYS_INFO,
crate::len_zero::COMPARISON_TO_EMPTY_INFO,
Expand Down
87 changes: 87 additions & 0 deletions clippy_lints/src/large_futures.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use clippy_utils::source::snippet;
use clippy_utils::{diagnostics::span_lint_and_sugg, ty::implements_trait};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem, MatchSource, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_target::abi::Size;

declare_clippy_lint! {
/// ### What it does
/// It checks for the size of a `Future` created by `async fn` or `async {}`.
///
/// ### Why is this bad?
/// Due to the current [unideal implemention](https://github.com/rust-lang/rust/issues/69826) of `Generator`,
/// large size of a `Future` may cause stack overflows.
///
/// ### Example
/// ```rust
/// async fn wait(f: impl std::future::Future<Output = ()>) {}
///
/// async fn big_fut(arg: [u8; 1024]) {}
///
/// pub async fn test() {
/// let fut = big_fut([0u8; 1024]);
/// wait(fut).await;
/// }
/// ```
///
/// `Box::pin` the big future instead.
///
/// ```rust
/// async fn wait(f: impl std::future::Future<Output = ()>) {}
///
/// async fn big_fut(arg: [u8; 1024]) {}
///
/// pub async fn test() {
/// let fut = Box::pin(big_fut([0u8; 1024]));
/// wait(fut).await;
/// }
/// ```
#[clippy::version = "1.68.0"]
pub LARGE_FUTURES,
pedantic,
"large future may lead to unexpected stack overflows"
}

#[derive(Copy, Clone)]
pub struct LargeFuture {
future_size_threshold: u64,
}

impl LargeFuture {
pub fn new(future_size_threshold: u64) -> Self {
Self { future_size_threshold }
}
}

impl_lint_pass!(LargeFuture => [LARGE_FUTURES]);

impl<'tcx> LateLintPass<'tcx> for LargeFuture {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if matches!(expr.span.ctxt().outer_expn_data().kind, rustc_span::ExpnKind::Macro(..)) {
return;
}
if let ExprKind::Match(expr, _, MatchSource::AwaitDesugar) = expr.kind {
if let ExprKind::Call(func, [expr, ..]) = expr.kind
&& let ExprKind::Path(QPath::LangItem(LangItem::IntoFutureIntoFuture, ..)) = func.kind
&& let ty = cx.typeck_results().expr_ty(expr)
&& let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait()
&& implements_trait(cx, ty, future_trait_def_id, &[])
&& let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty))
&& let size = layout.layout.size()
&& size >= Size::from_bytes(self.future_size_threshold)
{
span_lint_and_sugg(
cx,
LARGE_FUTURES,
expr.span,
&format!("large future with a size of {} bytes", size.bytes()),
"consider `Box::pin` on it",
format!("Box::pin({})", snippet(cx, expr.span, "..")),
Applicability::Unspecified,
);
}
}
}
}
3 changes: 3 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ mod items_after_statements;
mod iter_not_returning_iterator;
mod large_const_arrays;
mod large_enum_variant;
mod large_futures;
mod large_include_file;
mod large_stack_arrays;
mod len_zero;
Expand Down Expand Up @@ -810,6 +811,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move |_| Box::new(dereference::Dereferencing::new(msrv())));
store.register_late_pass(|_| Box::new(option_if_let_else::OptionIfLetElse));
store.register_late_pass(|_| Box::new(future_not_send::FutureNotSend));
let future_size_threshold = conf.future_size_threshold;
store.register_late_pass(move |_| Box::new(large_futures::LargeFuture::new(future_size_threshold)));
store.register_late_pass(|_| Box::new(if_let_mutex::IfLetMutex));
store.register_late_pass(|_| Box::new(if_not_else::IfNotElse));
store.register_late_pass(|_| Box::new(equatable_if_let::PatternEquality));
Expand Down
4 changes: 4 additions & 0 deletions clippy_lints/src/utils/conf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,10 @@ define_Conf! {
/// Whether to **only** check for missing documentation in items visible within the current
/// crate. For example, `pub(crate)` items.
(missing_docs_in_crate_items: bool = false),
/// Lint: LARGE_FUTURES.
///
/// The maximum byte size a `Future` can have, before it triggers the `clippy::large_futures` lint
(future_size_threshold: u64 = 16 * 1024),
}

/// Search for the configuration file.
Expand Down
1 change: 1 addition & 0 deletions tests/ui-toml/large_futures/clippy.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
future-size-threshold = 1024
27 changes: 27 additions & 0 deletions tests/ui-toml/large_futures/large_futures.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#![warn(clippy::large_futures)]

fn main() {}

pub async fn should_warn() {
let x = [0u8; 1024];
async {}.await;
dbg!(x);
}

pub async fn should_not_warn() {
let x = [0u8; 1020];
async {}.await;
dbg!(x);
}

pub async fn bar() {
should_warn().await;

async {
let x = [0u8; 1024];
dbg!(x);
}
.await;

should_not_warn().await;
}
10 changes: 10 additions & 0 deletions tests/ui-toml/large_futures/large_futures.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
error: large future with a size of 1026 bytes
--> $DIR/large_futures.rs:18:5
|
LL | should_warn().await;
| ^^^^^^^^^^^^^ help: consider `Box::pin` on it: `Box::pin(should_warn())`
|
= note: `-D clippy::large-futures` implied by `-D warnings`

error: aborting due to previous error

1 change: 1 addition & 0 deletions tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown fie
enforced-import-renames
enum-variant-name-threshold
enum-variant-size-threshold
future-size-threshold
ignore-interior-mutability
large-error-threshold
literal-representation-threshold
Expand Down
61 changes: 61 additions & 0 deletions tests/ui/large_futures.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#![feature(generators)]
#![warn(clippy::large_futures)]
#![allow(clippy::future_not_send)]
#![allow(clippy::manual_async_fn)]

async fn big_fut(_arg: [u8; 1024 * 16]) {}

async fn wait() {
let f = async {
big_fut([0u8; 1024 * 16]).await;
};
f.await
}
async fn calls_fut(fut: impl std::future::Future<Output = ()>) {
loop {
wait().await;
if true {
return fut.await;
} else {
wait().await;
}
}
}

pub async fn test() {
let fut = big_fut([0u8; 1024 * 16]);
foo().await;
calls_fut(fut).await;
}

pub fn foo() -> impl std::future::Future<Output = ()> {
async {
let x = [0i32; 1024 * 16];
async {}.await;
dbg!(x);
}
}

pub async fn lines() {
async {
let x = [0i32; 1024 * 16];
async {}.await;
println!("{:?}", x);
}
.await;
}

pub async fn macro_expn() {
macro_rules! macro_ {
() => {
async {
let x = [0i32; 1024 * 16];
async {}.await;
println!("macro: {:?}", x);
}
};
}
macro_!().await
}

fn main() {}
82 changes: 82 additions & 0 deletions tests/ui/large_futures.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
error: large future with a size of 16385 bytes
--> $DIR/large_futures.rs:10:9
|
LL | big_fut([0u8; 1024 * 16]).await;
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider `Box::pin` on it: `Box::pin(big_fut([0u8; 1024 * 16]))`
|
= note: `-D clippy::large-futures` implied by `-D warnings`

error: large future with a size of 16386 bytes
--> $DIR/large_futures.rs:12:5
|
LL | f.await
| ^ help: consider `Box::pin` on it: `Box::pin(f)`

error: large future with a size of 16387 bytes
--> $DIR/large_futures.rs:16:9
|
LL | wait().await;
| ^^^^^^ help: consider `Box::pin` on it: `Box::pin(wait())`

error: large future with a size of 16387 bytes
--> $DIR/large_futures.rs:20:13
|
LL | wait().await;
| ^^^^^^ help: consider `Box::pin` on it: `Box::pin(wait())`

error: large future with a size of 65540 bytes
--> $DIR/large_futures.rs:27:5
|
LL | foo().await;
| ^^^^^ help: consider `Box::pin` on it: `Box::pin(foo())`

error: large future with a size of 49159 bytes
--> $DIR/large_futures.rs:28:5
|
LL | calls_fut(fut).await;
| ^^^^^^^^^^^^^^ help: consider `Box::pin` on it: `Box::pin(calls_fut(fut))`

error: large future with a size of 65540 bytes
--> $DIR/large_futures.rs:40:5
|
LL | / async {
LL | | let x = [0i32; 1024 * 16];
LL | | async {}.await;
LL | | println!("{:?}", x);
LL | | }
| |_____^
|
help: consider `Box::pin` on it
|
LL ~ Box::pin(async {
LL + let x = [0i32; 1024 * 16];
LL + async {}.await;
LL + println!("{:?}", x);
LL + })
|

error: large future with a size of 65540 bytes
--> $DIR/large_futures.rs:51:13
|
LL | / async {
LL | | let x = [0i32; 1024 * 16];
LL | | async {}.await;
LL | | println!("macro: {:?}", x);
LL | | }
| |_____________^
...
LL | macro_!().await
| --------- in this macro invocation
|
= note: this error originates in the macro `macro_` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider `Box::pin` on it
|
LL ~ Box::pin(async {
LL + let x = [0i32; 1024 * 16];
LL + async {}.await;
LL + println!("macro: {:?}", x);
LL + })
|

error: aborting due to 8 previous errors

0 comments on commit 799732c

Please sign in to comment.