Skip to content

Commit 83b10ac

Browse files
committed
properly reject tail calls to &FnPtr or &FnDef
1 parent 00ce6d1 commit 83b10ac

File tree

4 files changed

+139
-2
lines changed

4 files changed

+139
-2
lines changed

compiler/rustc_mir_build/src/check_tail_calls.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,16 +95,29 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
9595
if let &ty::FnDef(did, args) = ty.kind() {
9696
let parent = self.tcx.parent(did);
9797
if self.tcx.fn_trait_kind_from_def_id(parent).is_some()
98-
&& args.first().and_then(|arg| arg.as_type()).is_some_and(Ty::is_closure)
98+
&& let Some(this) = args.first()
99+
&& let Some(this) = this.as_type()
99100
{
100-
self.report_calling_closure(&self.thir[fun], args[1].as_type().unwrap(), expr);
101+
if this.is_closure() {
102+
self.report_calling_closure(&self.thir[fun], args[1].as_type().unwrap(), expr);
103+
} else {
104+
// This can happen when tail calling `Box` that wraps a function
105+
self.report_nonfn_callee(fn_span, self.thir[fun].span, this);
106+
}
101107

102108
// Tail calling is likely to cause unrelated errors (ABI, argument mismatches),
103109
// skip them, producing an error about calling a closure is enough.
104110
return;
105111
};
106112
}
107113

114+
let (ty::FnDef(..) | ty::FnPtr(..)) = ty.kind() else {
115+
self.report_nonfn_callee(fn_span, self.thir[fun].span, ty);
116+
117+
// `fn_sig` below panics otherwise
118+
return;
119+
};
120+
108121
// Erase regions since tail calls don't care about lifetimes
109122
let callee_sig =
110123
self.tcx.normalize_erasing_late_bound_regions(self.typing_env, ty.fn_sig(self.tcx));
@@ -280,6 +293,40 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
280293
self.found_errors = Err(err);
281294
}
282295

296+
fn report_nonfn_callee(&mut self, call_sp: Span, fun_sp: Span, ty: Ty<'_>) {
297+
let mut err = self
298+
.tcx
299+
.dcx()
300+
.struct_span_err(
301+
call_sp,
302+
"tail calls can only be performed with function definitions or pointers",
303+
)
304+
.with_note(format!("callee has type `{ty}`"));
305+
306+
let mut ty = ty;
307+
let mut refs = 0;
308+
while ty.is_box() || ty.is_ref() {
309+
ty = ty.builtin_deref(false).unwrap();
310+
refs += 1;
311+
}
312+
313+
if refs > 0 && ty.is_fn() {
314+
let thing = if ty.is_fn_ptr() { "pointer" } else { "definition" };
315+
316+
let derefs =
317+
std::iter::once('(').chain(std::iter::repeat_n('*', refs)).collect::<String>();
318+
319+
err.multipart_suggestion(
320+
format!("consider dereferencing the expression to get a function {thing}"),
321+
vec![(fun_sp.shrink_to_lo(), derefs), (fun_sp.shrink_to_hi(), ")".to_owned())],
322+
Applicability::MachineApplicable,
323+
);
324+
}
325+
326+
let err = err.emit();
327+
self.found_errors = Err(err);
328+
}
329+
283330
fn report_abi_mismatch(&mut self, sp: Span, caller_abi: ExternAbi, callee_abi: ExternAbi) {
284331
let err = self
285332
.tcx
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//@ run-rustfix
2+
#![feature(explicit_tail_calls)]
3+
#![expect(incomplete_features)]
4+
5+
fn f() {}
6+
7+
fn g() {
8+
become (*(&f))() //~ error: tail calls can only be performed with function definitions or pointers
9+
}
10+
11+
fn h() {
12+
let table = [f as fn()];
13+
if let Some(fun) = table.get(0) {
14+
become (*fun)(); //~ error: tail calls can only be performed with function definitions or pointers
15+
}
16+
}
17+
18+
fn i() {
19+
become (***Box::new(&mut &f))(); //~ error: tail calls can only be performed with function definitions or pointers
20+
}
21+
22+
fn main() {
23+
g();
24+
h();
25+
i();
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//@ run-rustfix
2+
#![feature(explicit_tail_calls)]
3+
#![expect(incomplete_features)]
4+
5+
fn f() {}
6+
7+
fn g() {
8+
become (&f)() //~ error: tail calls can only be performed with function definitions or pointers
9+
}
10+
11+
fn h() {
12+
let table = [f as fn()];
13+
if let Some(fun) = table.get(0) {
14+
become fun(); //~ error: tail calls can only be performed with function definitions or pointers
15+
}
16+
}
17+
18+
fn i() {
19+
become Box::new(&mut &f)(); //~ error: tail calls can only be performed with function definitions or pointers
20+
}
21+
22+
fn main() {
23+
g();
24+
h();
25+
i();
26+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
error: tail calls can only be performed with function definitions or pointers
2+
--> $DIR/callee_is_ref.rs:8:12
3+
|
4+
LL | become (&f)()
5+
| ^^^^^^
6+
|
7+
= note: callee has type `&fn() {f}`
8+
help: consider dereferencing the expression to get a function definition
9+
|
10+
LL | become (*(&f))()
11+
| ++ +
12+
13+
error: tail calls can only be performed with function definitions or pointers
14+
--> $DIR/callee_is_ref.rs:14:16
15+
|
16+
LL | become fun();
17+
| ^^^^^
18+
|
19+
= note: callee has type `&fn()`
20+
help: consider dereferencing the expression to get a function pointer
21+
|
22+
LL | become (*fun)();
23+
| ++ +
24+
25+
error: tail calls can only be performed with function definitions or pointers
26+
--> $DIR/callee_is_ref.rs:19:12
27+
|
28+
LL | become Box::new(&mut &f)();
29+
| ^^^^^^^^^^^^^^^^^^^
30+
|
31+
= note: callee has type `Box<&mut &fn() {f}>`
32+
help: consider dereferencing the expression to get a function definition
33+
|
34+
LL | become (***Box::new(&mut &f))();
35+
| ++++ +
36+
37+
error: aborting due to 3 previous errors
38+

0 commit comments

Comments
 (0)