diff --git a/CHANGELOG.md b/CHANGELOG.md index 765826ed8..0352f956d 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 cd5dd7a57..208112b40 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 000000000..494bb2a97 --- /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) {} + /// + /// 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) {} + /// + /// 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 145cf5246..155aa1063 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 5f74de5a2..639f8d958 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 000000000..61bb17fdf --- /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 000000000..1238c512b --- /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 000000000..80039d904 --- /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 000000000..f7895f8ea --- /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 6a246afac..8447c3172 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 000000000..9d839998a --- /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) { + 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 { + 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 000000000..8b7aaa61b --- /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) { + 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 { + 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 000000000..557455299 --- /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 +