Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 107 additions & 25 deletions clippy_lints/src/large_futures.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::snippet;
use clippy_utils::ty::implements_trait;
use rustc_abi::Size;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem, MatchSource};
use rustc_hir::def_id::LocalDefId;
use rustc_hir::intravisit::{FnKind, Visitor, walk_body, walk_expr};
use rustc_hir::{Body, Expr, FnDecl};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::Ty;
use rustc_session::impl_lint_pass;
use rustc_span::Span;

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 implementation](https://github.com/rust-lang/rust/issues/69826) of `Coroutine`,
/// large size of a `Future` may cause stack overflows.
/// large size of a `Future` may cause stack overflows or be inefficient.
///
/// ### Example
/// ```no_run
Expand All @@ -25,7 +30,7 @@ declare_clippy_lint! {
/// }
/// ```
///
/// `Box::pin` the big future instead.
/// `Box::pin` the big future or the captured elements inside the future instead.
///
/// ```no_run
/// async fn large_future(_x: [u8; 16 * 1024]) {}
Expand Down Expand Up @@ -54,29 +59,106 @@ impl LargeFuture {

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(scrutinee, _, MatchSource::AwaitDesugar) = expr.kind
&& let ExprKind::Call(func, [arg]) = scrutinee.kind
&& let ExprKind::Path(qpath) = func.kind
&& cx.tcx.qpath_is_lang_item(qpath, LangItem::IntoFutureIntoFuture)
struct AsyncFnVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
future_size_threshold: u64,
/// Depth of the visitor with value `0` denoting the top-level expression.
depth: usize,
/// Whether a clippy suggestion was emitted for deeper levels of expressions.
emitted: bool,
}

impl<'tcx> Visitor<'tcx> for AsyncFnVisitor<'_, 'tcx> {
type NestedFilter = nested_filter::OnlyBodies;

fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
// For each expression, try to find the 'deepest' occurance of a too large a future.
// Chances are that subsequent less deep expression contain that too large a future,
// and hence it is redudant to also emit a suggestion for these expressions.
// Expressions on the same depth level can however emit a suggestion too.

self.depth += 1;
let old_emitted = self.emitted;
self.emitted = false; // Reset emitted for deeper level, but recover it later.
walk_expr(self, expr);
self.depth -= 1;

if !self.emitted
&& !expr.span.from_expansion()
&& let ty = cx.typeck_results().expr_ty(arg)
&& 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.typing_env().as_query_input(ty))
&& let size = layout.layout.size()
&& size >= Size::from_bytes(self.future_size_threshold)
&& let Some(ty) = self.cx.typeck_results().expr_ty_opt(expr)
&& let Some(size) = type_is_large_future(self.cx, ty, self.future_size_threshold)
{
span_lint_and_sugg(
cx,
LARGE_FUTURES,
arg.span,
format!("large future with a size of {} bytes", size.bytes()),
"consider `Box::pin` on it",
format!("Box::pin({})", snippet(cx, arg.span, "..")),
Applicability::Unspecified,
);
if self.depth == 0 {
// Special lint for top level fn bodies.
span_lint_hir_and_then(
self.cx,
LARGE_FUTURES,
expr.hir_id,
expr.span,
format!("definition for a large future with a size of {} bytes", size.bytes()),
|db| {
db.span_help(expr.span, "consider `Box::pin` when constructing it or reducing direct ownership of the items in scope and use references instead where possible");
},
);
} else {
// Expressions within a fn body.
span_lint_hir_and_then(
self.cx,
LARGE_FUTURES,
expr.hir_id,
expr.span,
format!("usage of large future with a size of {} bytes", size.bytes()),
|db| {
db.span_suggestion(
expr.span,
"consider `Box::pin` on it or reducing direct ownership of the items in scope and use references instead where possible",
format!("Box::pin({})", snippet(self.cx, expr.span, "..")),
Applicability::Unspecified,
);
},
);
}

self.emitted = true;
}

self.emitted |= old_emitted;
}

fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
self.cx.tcx
}
}

fn type_is_large_future<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, future_size_threshold: u64) -> Option<Size> {
if 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.typing_env().as_query_input(ty))
&& let size = layout.layout.size()
&& size >= Size::from_bytes(future_size_threshold)
{
Some(size)
} else {
None
}
}

impl<'tcx> LateLintPass<'tcx> for LargeFuture {
fn check_fn(
&mut self,
cx: &LateContext<'tcx>,
_: FnKind<'tcx>,
_: &'tcx FnDecl<'_>,
body: &'tcx Body<'_>,
_: Span,
_: LocalDefId,
) {
let mut visitor = AsyncFnVisitor {
cx,
depth: 0,
future_size_threshold: self.future_size_threshold,
emitted: false,
};
walk_body(&mut visitor, body);
}
}
8 changes: 5 additions & 3 deletions tests/ui/large_futures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#![warn(clippy::large_futures)]

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

async fn wait() {
let f = async {
Expand Down Expand Up @@ -45,24 +46,24 @@ pub fn foo() -> impl std::future::Future<Output = ()> {
async {}.await;
dbg!(x);
}
//~^ large_futures
}

pub async fn lines() {
async {
//~^ large_futures

let x = [0i32; 1024 * 16];
async {}.await;

println!("{:?}", x);
}
//~^ large_futures
.await;
}

pub async fn macro_expn() {
macro_rules! macro_ {
() => {
async {
//~^ large_futures
let x = [0i32; 1024 * 16];
async {}.await;
println!("macro: {:?}", x);
Expand All @@ -71,5 +72,6 @@ pub async fn macro_expn() {
}
macro_!().await
}
//~^ large_futures

fn main() {}
Loading
Loading