Skip to content

Commit eee8d77

Browse files
Rollup merge of #144817 - WaffleLapkin:reject-referety, r=Urgau
Properly reject tail calls to `&FnPtr` or `&FnDef` Fixes #144795
2 parents ebd865e + 8b65f3d commit eee8d77

File tree

7 files changed

+195
-3
lines changed

7 files changed

+195
-3
lines changed

compiler/rustc_middle/src/ty/sty.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1456,7 +1456,7 @@ impl<'tcx> Ty<'tcx> {
14561456
}
14571457
}
14581458

1459-
/// Returns the type and mutability of `*ty`.
1459+
/// Returns the type of `*ty`.
14601460
///
14611461
/// The parameter `explicit` indicates if this is an *explicit* dereference.
14621462
/// Some types -- notably raw ptrs -- can only be dereferenced explicitly.

compiler/rustc_mir_build/src/check_tail_calls.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,15 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
9595
// So we have to check for them in this weird way...
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.
@@ -109,6 +115,13 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
109115
}
110116
}
111117

118+
let (ty::FnDef(..) | ty::FnPtr(..)) = ty.kind() else {
119+
self.report_nonfn_callee(fn_span, self.thir[fun].span, ty);
120+
121+
// `fn_sig` below panics otherwise
122+
return;
123+
};
124+
112125
// Erase regions since tail calls don't care about lifetimes
113126
let callee_sig =
114127
self.tcx.normalize_erasing_late_bound_regions(self.typing_env, ty.fn_sig(self.tcx));
@@ -294,6 +307,40 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
294307
self.found_errors = Err(err);
295308
}
296309

310+
fn report_nonfn_callee(&mut self, call_sp: Span, fun_sp: Span, ty: Ty<'_>) {
311+
let mut err = self
312+
.tcx
313+
.dcx()
314+
.struct_span_err(
315+
call_sp,
316+
"tail calls can only be performed with function definitions or pointers",
317+
)
318+
.with_note(format!("callee has type `{ty}`"));
319+
320+
let mut ty = ty;
321+
let mut refs = 0;
322+
while ty.is_box() || ty.is_ref() {
323+
ty = ty.builtin_deref(false).unwrap();
324+
refs += 1;
325+
}
326+
327+
if refs > 0 && ty.is_fn() {
328+
let thing = if ty.is_fn_ptr() { "pointer" } else { "definition" };
329+
330+
let derefs =
331+
std::iter::once('(').chain(std::iter::repeat_n('*', refs)).collect::<String>();
332+
333+
err.multipart_suggestion(
334+
format!("consider dereferencing the expression to get a function {thing}"),
335+
vec![(fun_sp.shrink_to_lo(), derefs), (fun_sp.shrink_to_hi(), ")".to_owned())],
336+
Applicability::MachineApplicable,
337+
);
338+
}
339+
340+
let err = err.emit();
341+
self.found_errors = Err(err);
342+
}
343+
297344
fn report_abi_mismatch(&mut self, sp: Span, caller_abi: ExternAbi, callee_abi: ExternAbi) {
298345
let err = self
299346
.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+
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#![feature(explicit_tail_calls, exclusive_wrapper, fn_traits, unboxed_closures)]
2+
#![expect(incomplete_features)]
3+
4+
fn f() {}
5+
6+
fn g() {
7+
become std::sync::Exclusive::new(f)() //~ error: tail calls can only be performed with function definitions or pointers
8+
}
9+
10+
fn h() {
11+
become (&mut &std::sync::Exclusive::new(f))() //~ error: tail calls can only be performed with function definitions or pointers
12+
}
13+
14+
fn i() {
15+
struct J;
16+
17+
impl FnOnce<()> for J {
18+
type Output = ();
19+
extern "rust-call" fn call_once(self, (): ()) -> Self::Output {}
20+
}
21+
22+
become J(); //~ error: tail calls can only be performed with function definitions or pointers
23+
}
24+
25+
fn main() {
26+
g();
27+
h();
28+
i();
29+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
error: tail calls can only be performed with function definitions or pointers
2+
--> $DIR/callee_is_weird.rs:7:12
3+
|
4+
LL | become std::sync::Exclusive::new(f)()
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: callee has type `Exclusive<fn() {f}>`
8+
9+
error: tail calls can only be performed with function definitions or pointers
10+
--> $DIR/callee_is_weird.rs:11:12
11+
|
12+
LL | become (&mut &std::sync::Exclusive::new(f))()
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
14+
|
15+
= note: callee has type `Exclusive<fn() {f}>`
16+
17+
error: tail calls can only be performed with function definitions or pointers
18+
--> $DIR/callee_is_weird.rs:22:12
19+
|
20+
LL | become J();
21+
| ^^^
22+
|
23+
= note: callee has type `J`
24+
25+
error: aborting due to 3 previous errors
26+

0 commit comments

Comments
 (0)