rust_verify_test coverage (fbbbbcf)

Coverage Report

Created: 2026-08-23 08:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
rustc_mir_build/src/check_tail_calls.rs
Line
Count
Source
1
use rustc_abi::ExternAbi;
2
use rustc_data_structures::stack::ensure_sufficient_stack;
3
use rustc_errors::Applicability;
4
use rustc_hir::LangItem;
5
use rustc_hir::def::DefKind;
6
use rustc_hir::def_id::CRATE_DEF_ID;
7
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
8
use rustc_middle::span_bug;
9
use rustc_middle::thir::visit::{self, Visitor};
10
use rustc_middle::thir::{BodyTy, Expr, ExprId, ExprKind, Thir};
11
use rustc_middle::ty::{self, Ty, TyCtxt};
12
use rustc_span::def_id::{DefId, LocalDefId};
13
use rustc_span::{ErrorGuaranteed, Span};
14
15
0
pub(crate) fn check_tail_calls(tcx: TyCtxt<'_>, def: LocalDefId) -> Result<(), ErrorGuaranteed> {
16
0
    let (thir, expr) = tcx.thir_body(def)?;
17
0
    let thir = &thir.borrow();
18
19
    // If `thir` is empty, a type error occurred, skip this body.
20
0
    if thir.exprs.is_empty() {
21
0
        return Ok(());
22
0
    }
23
24
0
    let is_closure = matches!(tcx.def_kind(def), DefKind::Closure);
25
26
0
    let mut visitor = TailCallCkVisitor {
27
0
        tcx,
28
0
        thir,
29
0
        found_errors: Ok(()),
30
0
        // FIXME(#132279): we're clearly in a body here.
31
0
        typing_env: ty::TypingEnv::non_body_analysis(tcx, def),
32
0
        is_closure,
33
0
        caller_def_id: def,
34
0
    };
35
36
0
    visitor.visit_expr(&thir[expr]);
37
38
0
    visitor.found_errors
39
0
}
40
41
struct TailCallCkVisitor<'a, 'tcx> {
42
    tcx: TyCtxt<'tcx>,
43
    thir: &'a Thir<'tcx>,
44
    typing_env: ty::TypingEnv<'tcx>,
45
    /// Whatever the currently checked body is one of a closure
46
    is_closure: bool,
47
    /// The result of the checks, `Err(_)` if there was a problem with some
48
    /// tail call, `Ok(())` if all of them were fine.
49
    found_errors: Result<(), ErrorGuaranteed>,
50
    /// `LocalDefId` of the caller function.
51
    caller_def_id: LocalDefId,
52
}
53
54
impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
55
0
    fn check_tail_call(&mut self, call: &Expr<'_>, expr: &Expr<'_>) {
56
0
        if self.is_closure {
57
0
            self.report_in_closure(expr);
58
0
            return;
59
0
        }
60
61
0
        let BodyTy::Fn(caller_sig) = self.thir.body_type else {
62
0
            span_bug!(
63
0
                call.span,
64
                "`become` outside of functions should have been disallowed by hir_typeck"
65
            )
66
        };
67
        // While the `caller_sig` does have its free regions erased, it does not have its
68
        // binders anonymized. We call `erase_and_anonymize_regions` once again to anonymize any binders
69
        // within the signature, such as in function pointer or `dyn Trait` args.
70
0
        let caller_sig = self.tcx.erase_and_anonymize_regions(caller_sig);
71
72
0
        let ExprKind::Scope { value, .. } = call.kind else {
73
0
            span_bug!(call.span, "expected scope, found: {call:?}")
74
        };
75
0
        let value = &self.thir[value];
76
77
0
        if matches!(
78
0
            value.kind,
79
            ExprKind::Binary { .. }
80
                | ExprKind::Unary { .. }
81
                | ExprKind::AssignOp { .. }
82
                | ExprKind::Index { .. }
83
        ) {
84
0
            self.report_builtin_op(call, expr);
85
0
            return;
86
0
        }
87
88
0
        let ExprKind::Call { ty, fun, ref args, from_hir_call, fn_span } = value.kind else {
89
0
            self.report_non_call(value, expr);
90
0
            return;
91
        };
92
93
0
        if !from_hir_call {
94
0
            self.report_op(ty, args, fn_span, expr);
95
0
        }
96
97
0
        if let &ty::FnDef(did, args) = ty.kind() {
98
            // Closures in thir look something akin to
99
            // `for<'a> extern "rust-call" fn(&'a [closure@...], ()) -> <[closure@...] as FnOnce<()>>::Output {<[closure@...] as Fn<()>>::call}`
100
            // So we have to check for them in this weird way...
101
0
            let parent = self.tcx.parent(did);
102
0
            if self.tcx.fn_trait_kind_from_def_id(parent).is_some()
103
0
                && let Some(this) = args.first()
104
0
                && let Some(this) = this.as_type()
105
            {
106
0
                if this.is_closure() {
107
0
                    self.report_calling_closure(&self.thir[fun], args[1].as_type().unwrap(), expr);
108
0
                } else {
109
0
                    // This can happen when tail calling `Box` that wraps a function
110
0
                    self.report_nonfn_callee(fn_span, self.thir[fun].span, this);
111
0
                }
112
113
                // Tail calling is likely to cause unrelated errors (ABI, argument mismatches),
114
                // skip them, producing an error about calling a closure is enough.
115
0
                return;
116
0
            };
117
118
0
            if self.tcx.intrinsic(did).is_some() {
119
0
                self.report_calling_intrinsic(expr);
120
0
            }
121
0
        }
122
123
0
        let (ty::FnDef(..) | ty::FnPtr(..)) = ty.kind() else {
124
0
            self.report_nonfn_callee(fn_span, self.thir[fun].span, ty);
125
126
            // `fn_sig` below panics otherwise
127
0
            return;
128
        };
129
130
        // Erase regions since tail calls don't care about lifetimes
131
0
        let callee_sig =
132
0
            self.tcx.normalize_erasing_late_bound_regions(self.typing_env, ty.fn_sig(self.tcx));
133
134
0
        if caller_sig.abi() != callee_sig.abi() {
135
0
            self.report_abi_mismatch(expr.span, caller_sig.abi(), callee_sig.abi());
136
0
        }
137
138
0
        if !callee_sig.abi().supports_guaranteed_tail_call() {
139
0
            self.report_unsupported_abi(expr.span, callee_sig.abi());
140
0
        }
141
142
        // FIXME(explicit_tail_calls): this currently fails for cases where opaques are used.
143
        // e.g.
144
        // ```
145
        // fn a() -> impl Sized { become b() } // ICE
146
        // fn b() -> u8 { 0 }
147
        // ```
148
        // we should think what is the expected behavior here.
149
        // (we should probably just accept this by revealing opaques?)
150
0
        if caller_sig.inputs_and_output != callee_sig.inputs_and_output {
151
0
            let caller_ty = self.tcx.type_of(self.caller_def_id).skip_binder();
152
0
153
0
            self.report_signature_mismatch(
154
0
                expr.span,
155
0
                self.tcx.liberate_late_bound_regions(
156
0
                    CRATE_DEF_ID.to_def_id(),
157
0
                    caller_ty.fn_sig(self.tcx),
158
0
                ),
159
0
                self.tcx.liberate_late_bound_regions(CRATE_DEF_ID.to_def_id(), ty.fn_sig(self.tcx)),
160
0
            );
161
0
        }
162
163
        {
164
            // `#[track_caller]` affects the ABI of a function (by adding a location argument),
165
            // so a `track_caller` can only tail call other `track_caller` functions.
166
            //
167
            // The issue is however that we can't know if a function is `track_caller` or not at
168
            // this point (THIR can be polymorphic, we may have an unresolved trait function).
169
            // We could only allow functions that we *can* resolve and *are* `track_caller`,
170
            // but that would turn changing `track_caller`-ness into a breaking change,
171
            // which is probably undesirable.
172
            //
173
            // Also note that we don't check callee's `track_caller`-ness at all, mostly for the
174
            // reasons above, but also because we can always tailcall the shim we'd generate for
175
            // coercing the function to an `fn()` pointer. (although in that case the tailcall is
176
            // basically useless -- the shim calls the actual function, so tailcalling the shim is
177
            // equivalent to calling the function)
178
0
            let caller_needs_location = self.caller_needs_location();
179
180
0
            if caller_needs_location {
181
0
                self.report_track_caller_caller(expr.span);
182
0
            }
183
        }
184
185
0
        if caller_sig.c_variadic() {
186
0
            self.report_c_variadic_caller(expr.span);
187
0
        }
188
189
0
        if callee_sig.c_variadic() {
190
0
            self.report_c_variadic_callee(expr.span);
191
0
        }
192
0
    }
193
194
    /// Returns true if the caller function needs a location argument
195
    /// (i.e. if a function is marked as `#[track_caller]`)
196
0
    fn caller_needs_location(&self) -> bool {
197
0
        let flags = self.tcx.codegen_fn_attrs(self.caller_def_id).flags;
198
0
        flags.contains(CodegenFnAttrFlags::TRACK_CALLER)
199
0
    }
200
201
0
    fn report_in_closure(&mut self, expr: &Expr<'_>) {
202
0
        let err = self.tcx.dcx().span_err(expr.span, "`become` is not allowed in closures");
203
0
        self.found_errors = Err(err);
204
0
    }
205
206
0
    fn report_builtin_op(&mut self, value: &Expr<'_>, expr: &Expr<'_>) {
207
0
        let err = self
208
0
            .tcx
209
0
            .dcx()
210
0
            .struct_span_err(value.span, "`become` does not support operators")
211
0
            .with_note("using `become` on a builtin operator is not useful")
212
0
            .with_span_suggestion(
213
0
                value.span.until(expr.span),
214
                "try using `return` instead",
215
                "return ",
216
0
                Applicability::MachineApplicable,
217
            )
218
0
            .emit();
219
0
        self.found_errors = Err(err);
220
0
    }
221
222
0
    fn report_op(&mut self, fun_ty: Ty<'_>, args: &[ExprId], fn_span: Span, expr: &Expr<'_>) {
223
0
        let mut err =
224
0
            self.tcx.dcx().struct_span_err(fn_span, "`become` does not support operators");
225
226
0
        if let &ty::FnDef(did, _substs) = fun_ty.kind()
227
0
            && let parent = self.tcx.parent(did)
228
0
            && matches!(self.tcx.def_kind(parent), DefKind::Trait)
229
0
            && let Some(method) = op_trait_as_method_name(self.tcx, parent)
230
        {
231
0
            match args {
232
0
                &[arg] => {
233
0
                    let arg = &self.thir[arg];
234
0
235
0
                    err.multipart_suggestion(
236
0
                        "try using the method directly",
237
0
                        vec![
238
0
                            (fn_span.shrink_to_lo().until(arg.span), "(".to_owned()),
239
0
                            (arg.span.shrink_to_hi(), format!(").{method}()")),
240
0
                        ],
241
0
                        Applicability::MaybeIncorrect,
242
0
                    );
243
0
                }
244
0
                &[lhs, rhs] => {
245
0
                    let lhs = &self.thir[lhs];
246
0
                    let rhs = &self.thir[rhs];
247
0
248
0
                    err.multipart_suggestion(
249
0
                        "try using the method directly",
250
0
                        vec![
251
0
                            (lhs.span.shrink_to_lo(), format!("(")),
252
0
                            (lhs.span.between(rhs.span), format!(").{method}(")),
253
0
                            (rhs.span.between(expr.span.shrink_to_hi()), ")".to_owned()),
254
0
                        ],
255
0
                        Applicability::MaybeIncorrect,
256
0
                    );
257
0
                }
258
0
                _ => span_bug!(expr.span, "operator with more than 2 args? {args:?}"),
259
            }
260
0
        }
261
262
0
        self.found_errors = Err(err.emit());
263
0
    }
264
265
0
    fn report_non_call(&mut self, value: &Expr<'_>, expr: &Expr<'_>) {
266
0
        let err = self
267
0
            .tcx
268
0
            .dcx()
269
0
            .struct_span_err(value.span, "`become` requires a function call")
270
0
            .with_span_note(value.span, "not a function call")
271
0
            .with_span_suggestion(
272
0
                value.span.until(expr.span),
273
                "try using `return` instead",
274
                "return ",
275
0
                Applicability::MaybeIncorrect,
276
            )
277
0
            .emit();
278
0
        self.found_errors = Err(err);
279
0
    }
280
281
0
    fn report_calling_closure(&mut self, fun: &Expr<'_>, tupled_args: Ty<'_>, expr: &Expr<'_>) {
282
0
        let underscored_args = match tupled_args.kind() {
283
0
            ty::Tuple(tys) if tys.is_empty() => "".to_owned(),
284
0
            ty::Tuple(tys) => std::iter::repeat_n("_, ", tys.len() - 1).chain(["_"]).collect(),
285
0
            _ => "_".to_owned(),
286
        };
287
288
0
        let err = self
289
0
            .tcx
290
0
            .dcx()
291
0
            .struct_span_err(expr.span, "tail calling closures directly is not allowed")
292
0
            .with_multipart_suggestion(
293
                "try casting the closure to a function pointer type",
294
0
                vec![
295
0
                    (fun.span.shrink_to_lo(), "(".to_owned()),
296
0
                    (fun.span.shrink_to_hi(), format!(" as fn({underscored_args}) -> _)")),
297
                ],
298
0
                Applicability::MaybeIncorrect,
299
            )
300
0
            .emit();
301
0
        self.found_errors = Err(err);
302
0
    }
303
304
0
    fn report_calling_intrinsic(&mut self, expr: &Expr<'_>) {
305
0
        let err = self
306
0
            .tcx
307
0
            .dcx()
308
0
            .struct_span_err(expr.span, "tail calling intrinsics is not allowed")
309
0
            .emit();
310
311
0
        self.found_errors = Err(err);
312
0
    }
313
314
0
    fn report_nonfn_callee(&mut self, call_sp: Span, fun_sp: Span, ty: Ty<'_>) {
315
0
        let mut err = self
316
0
            .tcx
317
0
            .dcx()
318
0
            .struct_span_err(
319
0
                call_sp,
320
                "tail calls can only be performed with function definitions or pointers",
321
            )
322
0
            .with_note(format!("callee has type `{ty}`"));
323
324
0
        let mut ty = ty;
325
0
        let mut refs = 0;
326
0
        while ty.is_box() || ty.is_ref() {
327
0
            ty = ty.builtin_deref(false).unwrap();
328
0
            refs += 1;
329
0
        }
330
331
0
        if refs > 0 && ty.is_fn() {
332
0
            let thing = if ty.is_fn_ptr() { "pointer" } else { "definition" };
333
334
0
            let derefs =
335
0
                std::iter::once('(').chain(std::iter::repeat_n('*', refs)).collect::<String>();
336
337
0
            err.multipart_suggestion(
338
0
                format!("consider dereferencing the expression to get a function {thing}"),
339
0
                vec![(fun_sp.shrink_to_lo(), derefs), (fun_sp.shrink_to_hi(), ")".to_owned())],
340
0
                Applicability::MachineApplicable,
341
            );
342
0
        }
343
344
0
        let err = err.emit();
345
0
        self.found_errors = Err(err);
346
0
    }
347
348
0
    fn report_abi_mismatch(&mut self, sp: Span, caller_abi: ExternAbi, callee_abi: ExternAbi) {
349
0
        let err = self
350
0
            .tcx
351
0
            .dcx()
352
0
            .struct_span_err(sp, "mismatched function ABIs")
353
0
            .with_note("`become` requires caller and callee to have the same ABI")
354
0
            .with_note(format!("caller ABI is `{caller_abi}`, while callee ABI is `{callee_abi}`"))
355
0
            .emit();
356
0
        self.found_errors = Err(err);
357
0
    }
358
359
0
    fn report_unsupported_abi(&mut self, sp: Span, callee_abi: ExternAbi) {
360
0
        let err = self
361
0
            .tcx
362
0
            .dcx()
363
0
            .struct_span_err(sp, "ABI does not support guaranteed tail calls")
364
0
            .with_note(format!("`become` is not supported for `extern {callee_abi}` functions"))
365
0
            .emit();
366
0
        self.found_errors = Err(err);
367
0
    }
368
369
0
    fn report_signature_mismatch(
370
0
        &mut self,
371
0
        sp: Span,
372
0
        caller_sig: ty::FnSig<'_>,
373
0
        callee_sig: ty::FnSig<'_>,
374
0
    ) {
375
0
        let err = self
376
0
            .tcx
377
0
            .dcx()
378
0
            .struct_span_err(sp, "mismatched signatures")
379
0
            .with_note("`become` requires caller and callee to have matching signatures")
380
0
            .with_note(format!("caller signature: `{caller_sig}`"))
381
0
            .with_note(format!("callee signature: `{callee_sig}`"))
382
0
            .emit();
383
0
        self.found_errors = Err(err);
384
0
    }
385
386
0
    fn report_track_caller_caller(&mut self, sp: Span) {
387
0
        let err = self
388
0
            .tcx
389
0
            .dcx()
390
0
            .struct_span_err(
391
0
                sp,
392
                "a function marked with `#[track_caller]` cannot perform a tail-call",
393
            )
394
0
            .emit();
395
396
0
        self.found_errors = Err(err);
397
0
    }
398
399
0
    fn report_c_variadic_caller(&mut self, sp: Span) {
400
0
        let err = self
401
0
            .tcx
402
0
            .dcx()
403
            // FIXME(explicit_tail_calls): highlight the `...`
404
0
            .struct_span_err(sp, "tail-calls are not allowed in c-variadic functions")
405
0
            .emit();
406
407
0
        self.found_errors = Err(err);
408
0
    }
409
410
0
    fn report_c_variadic_callee(&mut self, sp: Span) {
411
0
        let err = self
412
0
            .tcx
413
0
            .dcx()
414
            // FIXME(explicit_tail_calls): highlight the function or something...
415
0
            .struct_span_err(sp, "c-variadic functions can't be tail-called")
416
0
            .emit();
417
418
0
        self.found_errors = Err(err);
419
0
    }
420
}
421
422
impl<'a, 'tcx> Visitor<'a, 'tcx> for TailCallCkVisitor<'a, 'tcx> {
423
0
    fn thir(&self) -> &'a Thir<'tcx> {
424
0
        &self.thir
425
0
    }
426
427
0
    fn visit_expr(&mut self, expr: &'a Expr<'tcx>) {
428
0
        ensure_sufficient_stack(|| {
429
0
            if let ExprKind::Become { value } = expr.kind {
430
0
                let call = &self.thir[value];
431
0
                self.check_tail_call(call, expr);
432
0
            }
433
434
0
            visit::walk_expr(self, expr);
435
0
        });
436
0
    }
437
}
438
439
0
fn op_trait_as_method_name(tcx: TyCtxt<'_>, trait_did: DefId) -> Option<&'static str> {
440
0
    let m = match tcx.as_lang_item(trait_did)? {
441
0
        LangItem::Add => "add",
442
0
        LangItem::Sub => "sub",
443
0
        LangItem::Mul => "mul",
444
0
        LangItem::Div => "div",
445
0
        LangItem::Rem => "rem",
446
0
        LangItem::Neg => "neg",
447
0
        LangItem::Not => "not",
448
0
        LangItem::BitXor => "bitxor",
449
0
        LangItem::BitAnd => "bitand",
450
0
        LangItem::BitOr => "bitor",
451
0
        LangItem::Shl => "shl",
452
0
        LangItem::Shr => "shr",
453
0
        LangItem::AddAssign => "add_assign",
454
0
        LangItem::SubAssign => "sub_assign",
455
0
        LangItem::MulAssign => "mul_assign",
456
0
        LangItem::DivAssign => "div_assign",
457
0
        LangItem::RemAssign => "rem_assign",
458
0
        LangItem::BitXorAssign => "bitxor_assign",
459
0
        LangItem::BitAndAssign => "bitand_assign",
460
0
        LangItem::BitOrAssign => "bitor_assign",
461
0
        LangItem::ShlAssign => "shl_assign",
462
0
        LangItem::ShrAssign => "shr_assign",
463
0
        LangItem::Index => "index",
464
0
        LangItem::IndexMut => "index_mut",
465
0
        _ => return None,
466
    };
467
468
0
    Some(m)
469
0
}