fix tail calls to #[track_caller] functions

This commit is contained in:
Waffle Lapkin 2025-08-03 17:03:25 +02:00
parent 07b7dc90ee
commit 85d1c89e0f
No known key found for this signature in database
9 changed files with 134 additions and 8 deletions

View file

@ -3622,6 +3622,7 @@ dependencies = [
"rustc_hir",
"rustc_incremental",
"rustc_index",
"rustc_lint_defs",
"rustc_macros",
"rustc_metadata",
"rustc_middle",

View file

@ -26,6 +26,7 @@ rustc_hashes = { path = "../rustc_hashes" }
rustc_hir = { path = "../rustc_hir" }
rustc_incremental = { path = "../rustc_incremental" }
rustc_index = { path = "../rustc_index" }
rustc_lint_defs = { path = "../rustc_lint_defs" }
rustc_macros = { path = "../rustc_macros" }
rustc_metadata = { path = "../rustc_metadata" }
rustc_middle = { path = "../rustc_middle" }

View file

@ -5,6 +5,7 @@ use rustc_ast as ast;
use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece};
use rustc_data_structures::packed::Pu128;
use rustc_hir::lang_items::LangItem;
use rustc_lint_defs::builtin::TAIL_CALL_TRACK_CALLER;
use rustc_middle::mir::{self, AssertKind, InlineAsmMacro, SwitchTargets, UnwindTerminateReason};
use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, ValidityRequirement};
use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths};
@ -906,7 +907,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
fn_span,
);
let instance = match instance.def {
match instance.def {
// We don't need AsyncDropGlueCtorShim here because it is not `noop func`,
// it is `func returning noop future`
ty::InstanceKind::DropGlue(_, None) => {
@ -995,14 +996,35 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
intrinsic.name,
);
}
instance
(Some(instance), None)
}
}
}
_ => instance,
};
(Some(instance), None)
_ if kind == CallKind::Tail
&& instance.def.requires_caller_location(bx.tcx()) =>
{
if let Some(hir_id) =
terminator.source_info.scope.lint_root(&self.mir.source_scopes)
{
let msg = "tail calling a function marked with `#[track_caller]` has no special effect";
bx.tcx().node_lint(TAIL_CALL_TRACK_CALLER, hir_id, |d| {
_ = d.primary_message(msg).span(fn_span)
});
}
let instance = ty::Instance::resolve_for_fn_ptr(
bx.tcx(),
bx.typing_env(),
def_id,
generic_args,
)
.unwrap();
(None, Some(bx.get_fn_addr(instance)))
}
_ => (Some(instance), None),
}
}
ty::FnPtr(..) => (None, Some(callee.immediate())),
_ => bug!("{} is not callable", callee.layout.ty),

View file

@ -5100,3 +5100,36 @@ declare_lint! {
report_in_deps: true,
};
}
declare_lint! {
/// The `tail_call_track_caller` lint detects usage of `become` attempting to tail call
/// a function marked with `#[track_caller]`.
///
/// ### Example
///
/// ```rust
/// #![feature(explicit_tail_calls)]
/// #![expect(incomplete_features)]
///
/// #[track_caller]
/// fn f() {}
///
/// fn g() {
/// become f();
/// }
///
/// g();
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// Due to implementation details of tail calls and `#[track_caller]` attribute, calls to
/// functions marked with `#[track_caller]` cannot become tail calls. As such using `become`
/// is no different than a normal call (except for changes in drop order).
pub TAIL_CALL_TRACK_CALLER,
Warn,
"detects tail calls of functions marked with `#[track_caller]`",
@feature_gate = explicit_tail_calls;
}

View file

@ -788,7 +788,35 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> {
// *Before* monomorphizing, record that we already handled this mention.
self.used_mentioned_items.insert(MentionedItem::Fn(callee_ty));
let callee_ty = self.monomorphize(callee_ty);
visit_fn_use(self.tcx, callee_ty, true, source, &mut self.used_items)
// HACK(explicit_tail_calls): collect tail calls to `#[track_caller]` functions as indirect,
// because we later call them as such, to prevent issues with ABI incompatibility.
// Ideally we'd replace such tail calls with normal call + return, but this requires
// post-mono MIR optimizations, which we don't yet have.
let force_indirect_call =
if matches!(terminator.kind, mir::TerminatorKind::TailCall { .. })
&& let &ty::FnDef(def_id, args) = callee_ty.kind()
&& let instance = ty::Instance::expect_resolve(
self.tcx,
ty::TypingEnv::fully_monomorphized(),
def_id,
args,
source,
)
&& instance.def.requires_caller_location(self.tcx)
{
true
} else {
false
};
visit_fn_use(
self.tcx,
callee_ty,
!force_indirect_call,
source,
&mut self.used_items,
)
}
mir::TerminatorKind::Drop { ref place, .. } => {
let ty = place.ty(self.body, self.tcx).ty;

View file

@ -1,10 +1,11 @@
//@ check-pass
// FIXME(explicit_tail_calls): make this run-pass, once tail calls are properly implemented
//@ run-pass
//@ ignore-pass
#![expect(incomplete_features)]
#![feature(explicit_tail_calls)]
fn a(x: u32) -> u32 {
become b(x);
//~^ warning: tail calling a function marked with `#[track_caller]` has no special effect
}
#[track_caller]

View file

@ -0,0 +1,10 @@
warning: tail calling a function marked with `#[track_caller]` has no special effect
--> $DIR/callee_is_track_caller.rs:7:12
|
LL | become b(x);
| ^^^^
|
= note: `#[warn(tail_call_track_caller)]` on by default
warning: 1 warning emitted

View file

@ -0,0 +1,20 @@
//@ run-pass
//@ ignore-pass
#![expect(incomplete_features)]
#![feature(explicit_tail_calls)]
fn c<T: Trait>() {
become T::f();
//~^ warning: tail calling a function marked with `#[track_caller]` has no special effect
}
trait Trait {
#[track_caller]
fn f() {}
}
impl Trait for () {}
fn main() {
c::<()>();
}

View file

@ -0,0 +1,10 @@
warning: tail calling a function marked with `#[track_caller]` has no special effect
--> $DIR/callee_is_track_caller_polymorphic.rs:7:12
|
LL | become T::f();
| ^^^^^^
|
= note: `#[warn(tail_call_track_caller)]` on by default
warning: 1 warning emitted