From 47b0ecdb55de84a38a636a2fa5b87daaaeafd5b5 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Thu, 31 Jul 2025 23:59:06 +0200 Subject: [PATCH] reject tail calls of `#[track_caller]` functions --- compiler/rustc_codegen_llvm/src/builder.rs | 12 +++++ .../rustc_mir_build/src/check_tail_calls.rs | 46 +++++++++++-------- .../callee_is_track_caller.rs | 12 ++--- .../callee_is_track_caller.stderr | 8 ++++ .../callee_is_track_caller_polymorphic.rs | 21 +++++++++ .../callee_is_track_caller_polymorphic.stderr | 8 ++++ .../caller_is_track_caller.rs | 7 ++- .../caller_is_track_caller.stderr | 10 +++- 8 files changed, 93 insertions(+), 31 deletions(-) create mode 100644 tests/ui/explicit-tail-calls/callee_is_track_caller.stderr create mode 100644 tests/ui/explicit-tail-calls/callee_is_track_caller_polymorphic.rs create mode 100644 tests/ui/explicit-tail-calls/callee_is_track_caller_polymorphic.stderr diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs index da2a153d819f9..c31c854c3d5c5 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -1442,6 +1442,18 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { funclet: Option<&Self::Funclet>, instance: Option>, ) { + if let Some(instance) = instance + && instance.def.requires_caller_location(self.tcx) + { + self.tcx + .dcx() + .struct_span_fatal( + self.tcx.def_span(instance.def.def_id()), + "a function marked with `#[track_caller]` cannot be tail-called (llvm)", + ) + .emit(); + } + let call = self.call(llty, fn_attrs, Some(fn_abi), llfn, args, funclet, instance); llvm::LLVMRustSetTailCallKind(call, llvm::TailCallKind::MustTail); diff --git a/compiler/rustc_mir_build/src/check_tail_calls.rs b/compiler/rustc_mir_build/src/check_tail_calls.rs index 18db8c1debb14..f5b1a2b2f4862 100644 --- a/compiler/rustc_mir_build/src/check_tail_calls.rs +++ b/compiler/rustc_mir_build/src/check_tail_calls.rs @@ -8,7 +8,7 @@ use rustc_middle::thir::visit::{self, Visitor}; use rustc_middle::thir::{BodyTy, Expr, ExprId, ExprKind, Thir}; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_span::def_id::{DefId, LocalDefId}; -use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span}; +use rustc_span::{ErrorGuaranteed, Span}; pub(crate) fn check_tail_calls(tcx: TyCtxt<'_>, def: LocalDefId) -> Result<(), ErrorGuaranteed> { let (thir, expr) = tcx.thir_body(def)?; @@ -135,22 +135,19 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { // `#[track_caller]` affects the ABI of a function (by adding a location argument), // so a `track_caller` can only tail call other `track_caller` functions. // - // The issue is however that we can't know if a function is `track_caller` or not at - // this point (THIR can be polymorphic, we may have an unresolved trait function). - // We could only allow functions that we *can* resolve and *are* `track_caller`, - // but that would turn changing `track_caller`-ness into a breaking change, - // which is probably undesirable. + // The issue is however that we can't always know if a function is `track_caller` or not + // at this point (THIR can be polymorphic, we may have an unresolved trait function). + // To prevent problems we deny code if we detect `#[track_caller]`. // - // Also note that we don't check callee's `track_caller`-ness at all, mostly for the - // reasons above, but also because we can always tailcall the shim we'd generate for - // coercing the function to an `fn()` pointer. (although in that case the tailcall is - // basically useless -- the shim calls the actual function, so tailcalling the shim is - // equivalent to calling the function) - let caller_needs_location = self.needs_location(self.caller_ty); - - if caller_needs_location { + // N.B. because we can't always know if the callee needs location, + // backends have to re-check that. + if self.needs_location(self.caller_ty) { self.report_track_caller_caller(expr.span); } + + if self.needs_location(ty) { + self.report_track_caller_callee(expr.span); + } } if caller_sig.c_variadic { @@ -165,12 +162,13 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { /// Returns true if function of type `ty` needs location argument /// (i.e. if a function is marked as `#[track_caller]`). /// - /// Panics if the function's instance can't be immediately resolved. + /// N.B. might spuriously return `false` in cases when it's not known if the function is + /// `#[track_caller]` or not (e.g. polymorphic thir) fn needs_location(&self, ty: Ty<'tcx>) -> bool { - if let &ty::FnDef(did, substs) = ty.kind() { - let instance = - ty::Instance::expect_resolve(self.tcx, self.typing_env, did, substs, DUMMY_SP); - + if let &ty::FnDef(did, args) = ty.kind() + && let Ok(Some(instance)) = + ty::Instance::try_resolve(self.tcx, self.typing_env, did, args) + { instance.def.requires_caller_location(self.tcx) } else { false @@ -321,6 +319,16 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { self.found_errors = Err(err); } + fn report_track_caller_callee(&mut self, sp: Span) { + let err = self + .tcx + .dcx() + .struct_span_err(sp, "a function marked with `#[track_caller]` cannot be tail-called") + .emit(); + + self.found_errors = Err(err); + } + fn report_c_variadic_caller(&mut self, sp: Span) { let err = self .tcx diff --git a/tests/ui/explicit-tail-calls/callee_is_track_caller.rs b/tests/ui/explicit-tail-calls/callee_is_track_caller.rs index bcb93fda8c856..dc14e0169ba91 100644 --- a/tests/ui/explicit-tail-calls/callee_is_track_caller.rs +++ b/tests/ui/explicit-tail-calls/callee_is_track_caller.rs @@ -1,15 +1,11 @@ -//@ check-pass -// FIXME(explicit_tail_calls): make this run-pass, once tail calls are properly implemented #![expect(incomplete_features)] #![feature(explicit_tail_calls)] -fn a(x: u32) -> u32 { - become b(x); +fn a() { + become b(); //~ error: a function marked with `#[track_caller]` cannot be tail-called } #[track_caller] -fn b(x: u32) -> u32 { x + 42 } +fn b() {} -fn main() { - assert_eq!(a(12), 54); -} +fn main() {} diff --git a/tests/ui/explicit-tail-calls/callee_is_track_caller.stderr b/tests/ui/explicit-tail-calls/callee_is_track_caller.stderr new file mode 100644 index 0000000000000..ebb259fa6100b --- /dev/null +++ b/tests/ui/explicit-tail-calls/callee_is_track_caller.stderr @@ -0,0 +1,8 @@ +error: a function marked with `#[track_caller]` cannot be tail-called + --> $DIR/callee_is_track_caller.rs:5:5 + | +LL | become b(); + | ^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/explicit-tail-calls/callee_is_track_caller_polymorphic.rs b/tests/ui/explicit-tail-calls/callee_is_track_caller_polymorphic.rs new file mode 100644 index 0000000000000..5382e3641686d --- /dev/null +++ b/tests/ui/explicit-tail-calls/callee_is_track_caller_polymorphic.rs @@ -0,0 +1,21 @@ +//@ build-fail +#![expect(incomplete_features)] +#![feature(explicit_tail_calls)] + +fn c() { + // `T::f` is not known when checking tail calls, + // so this has to be checked by the backend. + become T::f(); +} + +trait Trait { + #[track_caller] + // FIXME(explicit_tail_calls): make error point to the tail call, not callee definition + fn f() {} //~ error: a function marked with `#[track_caller]` cannot be tail-called +} + +impl Trait for () {} + +fn main() { + c::<()>(); +} diff --git a/tests/ui/explicit-tail-calls/callee_is_track_caller_polymorphic.stderr b/tests/ui/explicit-tail-calls/callee_is_track_caller_polymorphic.stderr new file mode 100644 index 0000000000000..e379979ed7d44 --- /dev/null +++ b/tests/ui/explicit-tail-calls/callee_is_track_caller_polymorphic.stderr @@ -0,0 +1,8 @@ +error: a function marked with `#[track_caller]` cannot be tail-called (llvm) + --> $DIR/callee_is_track_caller_polymorphic.rs:14:5 + | +LL | fn f() {} + | ^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/explicit-tail-calls/caller_is_track_caller.rs b/tests/ui/explicit-tail-calls/caller_is_track_caller.rs index 4e5f3f12f8399..8bdafda3e2846 100644 --- a/tests/ui/explicit-tail-calls/caller_is_track_caller.rs +++ b/tests/ui/explicit-tail-calls/caller_is_track_caller.rs @@ -3,14 +3,17 @@ #[track_caller] fn a() { - become b(); //~ error: a function marked with `#[track_caller]` cannot perform a tail-call + become b(); + //~^ error: a function marked with `#[track_caller]` cannot perform a tail-call } fn b() {} #[track_caller] fn c() { - become a(); //~ error: a function marked with `#[track_caller]` cannot perform a tail-call + become a(); + //~^ error: a function marked with `#[track_caller]` cannot perform a tail-call + //~| error: a function marked with `#[track_caller]` cannot be tail-called } fn main() {} diff --git a/tests/ui/explicit-tail-calls/caller_is_track_caller.stderr b/tests/ui/explicit-tail-calls/caller_is_track_caller.stderr index 79b9b45986c47..dbe3832aee5f1 100644 --- a/tests/ui/explicit-tail-calls/caller_is_track_caller.stderr +++ b/tests/ui/explicit-tail-calls/caller_is_track_caller.stderr @@ -5,10 +5,16 @@ LL | become b(); | ^^^^^^^^^^ error: a function marked with `#[track_caller]` cannot perform a tail-call - --> $DIR/caller_is_track_caller.rs:13:5 + --> $DIR/caller_is_track_caller.rs:14:5 | LL | become a(); | ^^^^^^^^^^ -error: aborting due to 2 previous errors +error: a function marked with `#[track_caller]` cannot be tail-called + --> $DIR/caller_is_track_caller.rs:14:5 + | +LL | become a(); + | ^^^^^^^^^^ + +error: aborting due to 3 previous errors