about summary refs log tree commit diff
diff options
context:
space:
mode:
authorcsmoe <csmoe@msn.com>2023-02-27 11:57:32 +0000
committercsmoe <csmoe@msn.com>2023-03-20 22:51:01 +0800
commit4fdae81c70bfa1481eebc7ebec09f121c6eaf53c (patch)
tree5d752395d7de7997d4e806c3ecaff5643197a99e
parentba7fd68e87cd13ece77baa83684396c8b9cbc633 (diff)
downloadrust-4fdae81c70bfa1481eebc7ebec09f121c6eaf53c.tar.gz
rust-4fdae81c70bfa1481eebc7ebec09f121c6eaf53c.zip
add large future lint
-rw-r--r--CHANGELOG.md3
-rw-r--r--clippy_lints/src/declared_lints.rs1
-rw-r--r--clippy_lints/src/large_futures.rs90
-rw-r--r--clippy_lints/src/lib.rs3
-rw-r--r--clippy_lints/src/utils/conf.rs4
-rw-r--r--tests/ui-toml/large_futures/clippy.toml1
-rw-r--r--tests/ui-toml/large_futures/large_futures.fixed29
-rw-r--r--tests/ui-toml/large_futures/large_futures.rs29
-rw-r--r--tests/ui-toml/large_futures/large_futures.stderr10
-rw-r--r--tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr1
-rw-r--r--tests/ui/large_futures.fixed41
-rw-r--r--tests/ui/large_futures.rs41
-rw-r--r--tests/ui/large_futures.stderr40
13 files changed, 292 insertions, 1 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 765826ed867..0352f956dfb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -278,7 +278,7 @@ Released 2022-12-15
   [#9490](https://github.com/rust-lang/rust-clippy/pull/9490)
 * [`almost_complete_letter_range`]: No longer lints in external macros
   [#9467](https://github.com/rust-lang/rust-clippy/pull/9467)
-* [`drop_copy`]: No longer lints on idiomatic cases in match arms 
+* [`drop_copy`]: No longer lints on idiomatic cases in match arms
   [#9491](https://github.com/rust-lang/rust-clippy/pull/9491)
 * [`question_mark`]: No longer lints in const context
   [#9487](https://github.com/rust-lang/rust-clippy/pull/9487)
@@ -4485,6 +4485,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
diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs
index cd5dd7a5706..208112b4025 100644
--- a/clippy_lints/src/declared_lints.rs
+++ b/clippy_lints/src/declared_lints.rs
@@ -216,6 +216,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,
diff --git a/clippy_lints/src/large_futures.rs b/clippy_lints/src/large_futures.rs
new file mode 100644
index 00000000000..494bb2a97d2
--- /dev/null
+++ b/clippy_lints/src/large_futures.rs
@@ -0,0 +1,90 @@
+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 let ExprKind::Match(expr, _, MatchSource::AwaitDesugar) = expr.kind {
+            if let ExprKind::Call(func, [expr, ..]) = expr.kind {
+                if matches!(
+                    func.kind,
+                    ExprKind::Path(QPath::LangItem(LangItem::IntoFutureIntoFuture, ..))
+                ) {
+                    let ty = cx.typeck_results().expr_ty(expr);
+                    if let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait()
+                        && implements_trait(cx, ty, future_trait_def_id, &[]) {
+                            if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty)) {
+                                let size = layout.layout.size();
+                                if 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::MachineApplicable,
+                                    );
+                                }
+                            }
+                        }
+                }
+            }
+        }
+    }
+}
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 145cf524652..155aa106322 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -161,6 +161,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;
@@ -800,6 +801,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));
diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs
index 5f74de5a288..639f8d95877 100644
--- a/clippy_lints/src/utils/conf.rs
+++ b/clippy_lints/src/utils/conf.rs
@@ -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.
diff --git a/tests/ui-toml/large_futures/clippy.toml b/tests/ui-toml/large_futures/clippy.toml
new file mode 100644
index 00000000000..61bb17fdf6b
--- /dev/null
+++ b/tests/ui-toml/large_futures/clippy.toml
@@ -0,0 +1 @@
+future-size-threshold = 1024
diff --git a/tests/ui-toml/large_futures/large_futures.fixed b/tests/ui-toml/large_futures/large_futures.fixed
new file mode 100644
index 00000000000..1238c512b0f
--- /dev/null
+++ b/tests/ui-toml/large_futures/large_futures.fixed
@@ -0,0 +1,29 @@
+// run-rustfix
+
+#![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() {
+    Box::pin(should_warn()).await;
+
+    async {
+        let x = [0u8; 1024];
+        dbg!(x);
+    }
+    .await;
+
+    should_not_warn().await;
+}
diff --git a/tests/ui-toml/large_futures/large_futures.rs b/tests/ui-toml/large_futures/large_futures.rs
new file mode 100644
index 00000000000..80039d9047b
--- /dev/null
+++ b/tests/ui-toml/large_futures/large_futures.rs
@@ -0,0 +1,29 @@
+// run-rustfix
+
+#![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;
+}
diff --git a/tests/ui-toml/large_futures/large_futures.stderr b/tests/ui-toml/large_futures/large_futures.stderr
new file mode 100644
index 00000000000..f7895f8eaf7
--- /dev/null
+++ b/tests/ui-toml/large_futures/large_futures.stderr
@@ -0,0 +1,10 @@
+error: large future with a size of 1026 bytes
+  --> $DIR/large_futures.rs:20: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
+
diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr
index 6a246afac76..8447c31722d 100644
--- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr
+++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr
@@ -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
diff --git a/tests/ui/large_futures.fixed b/tests/ui/large_futures.fixed
new file mode 100644
index 00000000000..9d839998afd
--- /dev/null
+++ b/tests/ui/large_futures.fixed
@@ -0,0 +1,41 @@
+// run-rustfix
+
+#![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 {
+        Box::pin(big_fut([0u8; 1024 * 16])).await;
+    };
+    Box::pin(f).await
+}
+async fn calls_fut(fut: impl std::future::Future<Output = ()>) {
+    loop {
+        Box::pin(wait()).await;
+        if true {
+            return fut.await;
+        } else {
+            Box::pin(wait()).await;
+        }
+    }
+}
+
+pub async fn test() {
+    let fut = big_fut([0u8; 1024 * 16]);
+    Box::pin(foo()).await;
+    Box::pin(calls_fut(fut)).await;
+}
+
+pub fn foo() -> impl std::future::Future<Output = ()> {
+    async {
+        let x = [0i32; 1024 * 16];
+        async {}.await;
+        dbg!(x);
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/large_futures.rs b/tests/ui/large_futures.rs
new file mode 100644
index 00000000000..8b7aaa61b88
--- /dev/null
+++ b/tests/ui/large_futures.rs
@@ -0,0 +1,41 @@
+// run-rustfix
+
+#![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);
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/large_futures.stderr b/tests/ui/large_futures.stderr
new file mode 100644
index 00000000000..557455299a9
--- /dev/null
+++ b/tests/ui/large_futures.stderr
@@ -0,0 +1,40 @@
+error: large future with a size of 16385 bytes
+  --> $DIR/large_futures.rs:12: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:14: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:18: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:22: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:29: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:30:5
+   |
+LL |     calls_fut(fut).await;
+   |     ^^^^^^^^^^^^^^ help: consider `Box::pin` on it: `Box::pin(calls_fut(fut))`
+
+error: aborting due to 6 previous errors
+