Skip to content

Reject tail calls of #[track_caller] functions #144762

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
12 changes: 12 additions & 0 deletions compiler/rustc_codegen_llvm/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1442,6 +1442,18 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
funclet: Option<&Self::Funclet>,
instance: Option<Instance<'tcx>>,
) {
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);

Expand Down
46 changes: 27 additions & 19 deletions compiler/rustc_mir_build/src/check_tail_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
12 changes: 4 additions & 8 deletions tests/ui/explicit-tail-calls/callee_is_track_caller.rs
Original file line number Diff line number Diff line change
@@ -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() {}
8 changes: 8 additions & 0 deletions tests/ui/explicit-tail-calls/callee_is_track_caller.stderr
Original file line number Diff line number Diff line change
@@ -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

21 changes: 21 additions & 0 deletions tests/ui/explicit-tail-calls/callee_is_track_caller_polymorphic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//@ build-fail
#![expect(incomplete_features)]
#![feature(explicit_tail_calls)]

fn c<T: Trait>() {
// `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::<()>();
}
Original file line number Diff line number Diff line change
@@ -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

7 changes: 5 additions & 2 deletions tests/ui/explicit-tail-calls/caller_is_track_caller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
10 changes: 8 additions & 2 deletions tests/ui/explicit-tail-calls/caller_is_track_caller.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Loading