diff --git a/compiler/rustc_error_messages/locales/en-US/lint.ftl b/compiler/rustc_error_messages/locales/en-US/lint.ftl index 7e28f22c0ba8b..3980f9a2a7a58 100644 --- a/compiler/rustc_error_messages/locales/en-US/lint.ftl +++ b/compiler/rustc_error_messages/locales/en-US/lint.ftl @@ -350,6 +350,8 @@ lint_builtin_mutable_transmutes = lint_builtin_unstable_features = unstable feature +lint_ungated_async_fn_track_caller = `#[track_caller]` on async functions is a no-op, unless the `closure_track_caller` feature is enabled + lint_builtin_unreachable_pub = unreachable `pub` {$what} .suggestion = consider restricting its visibility .help = or consider exporting it for use by other crates diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index cd19e65b6fc32..d7d91a12e6584 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -25,6 +25,7 @@ use crate::{ types::{transparent_newtype_field, CItemKind}, EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext, }; +use hir::IsAsync; use rustc_ast::attr; use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_ast::visit::{FnCtxt, FnKind}; @@ -40,7 +41,10 @@ use rustc_feature::{deprecated_attributes, AttributeGate, BuiltinAttribute, Gate use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LocalDefId, LocalDefIdSet, CRATE_DEF_ID}; -use rustc_hir::{ForeignItemKind, GenericParamKind, HirId, Node, PatKind, PredicateOrigin}; +use rustc_hir::intravisit::FnKind as HirFnKind; +use rustc_hir::{ + Body, FnDecl, ForeignItemKind, GenericParamKind, HirId, Node, PatKind, PredicateOrigin, +}; use rustc_index::vec::Idx; use rustc_middle::lint::in_external_macro; use rustc_middle::ty::layout::{LayoutError, LayoutOf}; @@ -1370,6 +1374,45 @@ impl<'tcx> LateLintPass<'tcx> for UnstableFeatures { } } +declare_lint! { + /// `#[track_caller]` is a no-op without corresponding feature flag + UNGATED_ASYNC_FN_TRACK_CALLER, + Warn, + "enabling track_caller on an async fn is a no-op unless the closure_track_caller feature is enabled" +} + +declare_lint_pass!( + /// Explains corresponding feature flag must be enabled for the `#[track_caller] attribute to + /// do anything + UngatedAsyncFnTrackCaller => [UNGATED_ASYNC_FN_TRACK_CALLER] +); + +impl<'tcx> LateLintPass<'tcx> for UngatedAsyncFnTrackCaller { + fn check_fn( + &mut self, + cx: &LateContext<'_>, + fn_kind: HirFnKind<'_>, + _: &'tcx FnDecl<'_>, + _: &'tcx Body<'_>, + span: Span, + hir_id: HirId, + ) { + if let HirFnKind::ItemFn(_, _, _) = fn_kind && fn_kind.asyncness() == IsAsync::Async && !cx.tcx.features().closure_track_caller { + // Now, check if the function has the `#[track_caller]` attribute + let attrs = cx.tcx.hir().attrs(hir_id); + let maybe_track_caller = attrs.iter().find(|attr| attr.has_name(sym::track_caller)); + if let Some(attr) = maybe_track_caller { + cx.struct_span_lint( + UNGATED_ASYNC_FN_TRACK_CALLER, + span.with_hi(attr.span.hi()), + fluent::lint_ungated_async_fn_track_caller, + |lint| lint, + ); + } + } + } +} + declare_lint! { /// The `unreachable_pub` lint triggers for `pub` items not reachable from /// the crate root. diff --git a/src/test/ui/async-await/track-caller/issue-104588-no-op-panic-track-caller.rs b/src/test/ui/async-await/track-caller/issue-104588-no-op-panic-track-caller.rs new file mode 100644 index 0000000000000..5ef40408e2693 --- /dev/null +++ b/src/test/ui/async-await/track-caller/issue-104588-no-op-panic-track-caller.rs @@ -0,0 +1,78 @@ +// run-pass +// edition:2021 +// needs-unwind + + +use std::future::Future; +use std::panic; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll, Wake}; +use std::thread::{self, Thread}; + +/// A waker that wakes up the current thread when called. +struct ThreadWaker(Thread); + +impl Wake for ThreadWaker { + fn wake(self: Arc) { + self.0.unpark(); + } +} + +/// Run a future to completion on the current thread. +fn block_on(fut: impl Future) -> T { + // Pin the future so it can be polled. + let mut fut = Box::pin(fut); + + // Create a new context to be passed to the future. + let t = thread::current(); + let waker = Arc::new(ThreadWaker(t)).into(); + let mut cx = Context::from_waker(&waker); + + // Run the future to completion. + loop { + match fut.as_mut().poll(&mut cx) { + Poll::Ready(res) => return res, + Poll::Pending => thread::park(), + } + } +} + +async fn bar() { + panic!() +} + +async fn foo() { + bar().await +} + +#[track_caller] //~ WARN `#[track_caller]` on async functions is a no-op, unless the `closure_track_caller` feature is enabled [ungated_async_fn_track_caller] +async fn bar_track_caller() { + panic!() +} + +async fn foo_track_caller() { + bar_track_caller().await +} + +fn panicked_at(f: impl FnOnce() + panic::UnwindSafe) -> u32 { + let loc = Arc::new(Mutex::new(None)); + + let hook = panic::take_hook(); + { + let loc = loc.clone(); + panic::set_hook(Box::new(move |info| { + *loc.lock().unwrap() = info.location().map(|loc| loc.line()) + })); + } + panic::catch_unwind(f).unwrap_err(); + panic::set_hook(hook); + let x = loc.lock().unwrap().unwrap(); + x +} + +fn main() { + assert_eq!(panicked_at(|| block_on(foo())), 41); + // Since the `closure_track_caller` feature is not enabled, the + // `track_caller annotation does nothing. + assert_eq!(panicked_at(|| block_on(foo_track_caller())), 50); +} diff --git a/src/test/ui/async-await/track-caller/issue-104588-no-op-panic-track-caller.stderr b/src/test/ui/async-await/track-caller/issue-104588-no-op-panic-track-caller.stderr new file mode 100644 index 0000000000000..5bfd9ed8490fd --- /dev/null +++ b/src/test/ui/async-await/track-caller/issue-104588-no-op-panic-track-caller.stderr @@ -0,0 +1,12 @@ +warning: `#[track_caller]` on async functions is a no-op, unless the `closure_track_caller` feature is enabled + --> $DIR/issue-104588-no-op-panic-track-caller.rs:48:16 + | +LL | #[track_caller] + | ________________^ +LL | | async fn bar_track_caller() { + | |_ + | + = note: `#[warn(ungated_async_fn_track_caller)]` on by default + +warning: 1 warning emitted + diff --git a/src/test/ui/async-await/track-caller/issue-104588-no-op-track-caller.rs b/src/test/ui/async-await/track-caller/issue-104588-no-op-track-caller.rs new file mode 100644 index 0000000000000..146d3c9ec53dc --- /dev/null +++ b/src/test/ui/async-await/track-caller/issue-104588-no-op-track-caller.rs @@ -0,0 +1,9 @@ +// check-pass +// edition:2021 + +#[track_caller] //~ WARN `#[track_caller]` on async functions is a no-op, unless the `closure_track_caller` feature is enabled +async fn foo() {} + +fn main() { + foo(); +} diff --git a/src/test/ui/async-await/track-caller/issue-104588-no-op-track-caller.stderr b/src/test/ui/async-await/track-caller/issue-104588-no-op-track-caller.stderr new file mode 100644 index 0000000000000..bf66cc9ea90fc --- /dev/null +++ b/src/test/ui/async-await/track-caller/issue-104588-no-op-track-caller.stderr @@ -0,0 +1,12 @@ +warning: `#[track_caller]` on async functions is a no-op, unless the `closure_track_caller` feature is enabled + --> $DIR/issue-104588-no-op-track-caller.rs:4:16 + | +LL | #[track_caller] + | ________________^ +LL | | async fn foo() {} + | |_ + | + = note: `#[warn(ungated_async_fn_track_caller)]` on by default + +warning: 1 warning emitted +