Skip to content

Commit 47b0ecd

Browse files
committed
reject tail calls of #[track_caller] functions
1 parent adcb3d3 commit 47b0ecd

File tree

8 files changed

+93
-31
lines changed

8 files changed

+93
-31
lines changed

compiler/rustc_codegen_llvm/src/builder.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1442,6 +1442,18 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
14421442
funclet: Option<&Self::Funclet>,
14431443
instance: Option<Instance<'tcx>>,
14441444
) {
1445+
if let Some(instance) = instance
1446+
&& instance.def.requires_caller_location(self.tcx)
1447+
{
1448+
self.tcx
1449+
.dcx()
1450+
.struct_span_fatal(
1451+
self.tcx.def_span(instance.def.def_id()),
1452+
"a function marked with `#[track_caller]` cannot be tail-called (llvm)",
1453+
)
1454+
.emit();
1455+
}
1456+
14451457
let call = self.call(llty, fn_attrs, Some(fn_abi), llfn, args, funclet, instance);
14461458
llvm::LLVMRustSetTailCallKind(call, llvm::TailCallKind::MustTail);
14471459

compiler/rustc_mir_build/src/check_tail_calls.rs

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_middle::thir::visit::{self, Visitor};
88
use rustc_middle::thir::{BodyTy, Expr, ExprId, ExprKind, Thir};
99
use rustc_middle::ty::{self, Ty, TyCtxt};
1010
use rustc_span::def_id::{DefId, LocalDefId};
11-
use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span};
11+
use rustc_span::{ErrorGuaranteed, Span};
1212

1313
pub(crate) fn check_tail_calls(tcx: TyCtxt<'_>, def: LocalDefId) -> Result<(), ErrorGuaranteed> {
1414
let (thir, expr) = tcx.thir_body(def)?;
@@ -135,22 +135,19 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
135135
// `#[track_caller]` affects the ABI of a function (by adding a location argument),
136136
// so a `track_caller` can only tail call other `track_caller` functions.
137137
//
138-
// The issue is however that we can't know if a function is `track_caller` or not at
139-
// this point (THIR can be polymorphic, we may have an unresolved trait function).
140-
// We could only allow functions that we *can* resolve and *are* `track_caller`,
141-
// but that would turn changing `track_caller`-ness into a breaking change,
142-
// which is probably undesirable.
138+
// The issue is however that we can't always know if a function is `track_caller` or not
139+
// at this point (THIR can be polymorphic, we may have an unresolved trait function).
140+
// To prevent problems we deny code if we detect `#[track_caller]`.
143141
//
144-
// Also note that we don't check callee's `track_caller`-ness at all, mostly for the
145-
// reasons above, but also because we can always tailcall the shim we'd generate for
146-
// coercing the function to an `fn()` pointer. (although in that case the tailcall is
147-
// basically useless -- the shim calls the actual function, so tailcalling the shim is
148-
// equivalent to calling the function)
149-
let caller_needs_location = self.needs_location(self.caller_ty);
150-
151-
if caller_needs_location {
142+
// N.B. because we can't always know if the callee needs location,
143+
// backends have to re-check that.
144+
if self.needs_location(self.caller_ty) {
152145
self.report_track_caller_caller(expr.span);
153146
}
147+
148+
if self.needs_location(ty) {
149+
self.report_track_caller_callee(expr.span);
150+
}
154151
}
155152

156153
if caller_sig.c_variadic {
@@ -165,12 +162,13 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
165162
/// Returns true if function of type `ty` needs location argument
166163
/// (i.e. if a function is marked as `#[track_caller]`).
167164
///
168-
/// Panics if the function's instance can't be immediately resolved.
165+
/// N.B. might spuriously return `false` in cases when it's not known if the function is
166+
/// `#[track_caller]` or not (e.g. polymorphic thir)
169167
fn needs_location(&self, ty: Ty<'tcx>) -> bool {
170-
if let &ty::FnDef(did, substs) = ty.kind() {
171-
let instance =
172-
ty::Instance::expect_resolve(self.tcx, self.typing_env, did, substs, DUMMY_SP);
173-
168+
if let &ty::FnDef(did, args) = ty.kind()
169+
&& let Ok(Some(instance)) =
170+
ty::Instance::try_resolve(self.tcx, self.typing_env, did, args)
171+
{
174172
instance.def.requires_caller_location(self.tcx)
175173
} else {
176174
false
@@ -321,6 +319,16 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
321319
self.found_errors = Err(err);
322320
}
323321

322+
fn report_track_caller_callee(&mut self, sp: Span) {
323+
let err = self
324+
.tcx
325+
.dcx()
326+
.struct_span_err(sp, "a function marked with `#[track_caller]` cannot be tail-called")
327+
.emit();
328+
329+
self.found_errors = Err(err);
330+
}
331+
324332
fn report_c_variadic_caller(&mut self, sp: Span) {
325333
let err = self
326334
.tcx
Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
1-
//@ check-pass
2-
// FIXME(explicit_tail_calls): make this run-pass, once tail calls are properly implemented
31
#![expect(incomplete_features)]
42
#![feature(explicit_tail_calls)]
53

6-
fn a(x: u32) -> u32 {
7-
become b(x);
4+
fn a() {
5+
become b(); //~ error: a function marked with `#[track_caller]` cannot be tail-called
86
}
97

108
#[track_caller]
11-
fn b(x: u32) -> u32 { x + 42 }
9+
fn b() {}
1210

13-
fn main() {
14-
assert_eq!(a(12), 54);
15-
}
11+
fn main() {}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error: a function marked with `#[track_caller]` cannot be tail-called
2+
--> $DIR/callee_is_track_caller.rs:5:5
3+
|
4+
LL | become b();
5+
| ^^^^^^^^^^
6+
7+
error: aborting due to 1 previous error
8+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//@ build-fail
2+
#![expect(incomplete_features)]
3+
#![feature(explicit_tail_calls)]
4+
5+
fn c<T: Trait>() {
6+
// `T::f` is not known when checking tail calls,
7+
// so this has to be checked by the backend.
8+
become T::f();
9+
}
10+
11+
trait Trait {
12+
#[track_caller]
13+
// FIXME(explicit_tail_calls): make error point to the tail call, not callee definition
14+
fn f() {} //~ error: a function marked with `#[track_caller]` cannot be tail-called
15+
}
16+
17+
impl Trait for () {}
18+
19+
fn main() {
20+
c::<()>();
21+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error: a function marked with `#[track_caller]` cannot be tail-called (llvm)
2+
--> $DIR/callee_is_track_caller_polymorphic.rs:14:5
3+
|
4+
LL | fn f() {}
5+
| ^^^^^^
6+
7+
error: aborting due to 1 previous error
8+

tests/ui/explicit-tail-calls/caller_is_track_caller.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33

44
#[track_caller]
55
fn a() {
6-
become b(); //~ error: a function marked with `#[track_caller]` cannot perform a tail-call
6+
become b();
7+
//~^ error: a function marked with `#[track_caller]` cannot perform a tail-call
78
}
89

910
fn b() {}
1011

1112
#[track_caller]
1213
fn c() {
13-
become a(); //~ error: a function marked with `#[track_caller]` cannot perform a tail-call
14+
become a();
15+
//~^ error: a function marked with `#[track_caller]` cannot perform a tail-call
16+
//~| error: a function marked with `#[track_caller]` cannot be tail-called
1417
}
1518

1619
fn main() {}

tests/ui/explicit-tail-calls/caller_is_track_caller.stderr

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,16 @@ LL | become b();
55
| ^^^^^^^^^^
66

77
error: a function marked with `#[track_caller]` cannot perform a tail-call
8-
--> $DIR/caller_is_track_caller.rs:13:5
8+
--> $DIR/caller_is_track_caller.rs:14:5
99
|
1010
LL | become a();
1111
| ^^^^^^^^^^
1212

13-
error: aborting due to 2 previous errors
13+
error: a function marked with `#[track_caller]` cannot be tail-called
14+
--> $DIR/caller_is_track_caller.rs:14:5
15+
|
16+
LL | become a();
17+
| ^^^^^^^^^^
18+
19+
error: aborting due to 3 previous errors
1420

0 commit comments

Comments
 (0)