summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_const_eval/src/interpret/intrinsics/caller_location.rs
blob: 5864b921552877b265743e5e3f448e3bba69b541 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
use std::convert::TryFrom;

use rustc_ast::Mutability;
use rustc_hir::lang_items::LangItem;
use rustc_middle::mir::TerminatorKind;
use rustc_middle::ty::layout::LayoutOf;
use rustc_middle::ty::subst::Subst;
use rustc_span::{Span, Symbol};

use crate::interpret::{
    intrinsics::{InterpCx, Machine},
    MPlaceTy, MemoryKind, Scalar,
};

impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
    /// Walks up the callstack from the intrinsic's callsite, searching for the first callsite in a
    /// frame which is not `#[track_caller]`.
    pub(crate) fn find_closest_untracked_caller_location(&self) -> Span {
        for frame in self.stack().iter().rev() {
            debug!("find_closest_untracked_caller_location: checking frame {:?}", frame.instance);

            // Assert that the frame we look at is actually executing code currently
            // (`loc` is `Err` when we are unwinding and the frame does not require cleanup).
            let loc = frame.loc.unwrap();

            // This could be a non-`Call` terminator (such as `Drop`), or not a terminator at all
            // (such as `box`). Use the normal span by default.
            let mut source_info = *frame.body.source_info(loc);

            // If this is a `Call` terminator, use the `fn_span` instead.
            let block = &frame.body.basic_blocks()[loc.block];
            if loc.statement_index == block.statements.len() {
                debug!(
                    "find_closest_untracked_caller_location: got terminator {:?} ({:?})",
                    block.terminator(),
                    block.terminator().kind
                );
                if let TerminatorKind::Call { fn_span, .. } = block.terminator().kind {
                    source_info.span = fn_span;
                }
            }

            // Walk up the `SourceScope`s, in case some of them are from MIR inlining.
            // If so, the starting `source_info.span` is in the innermost inlined
            // function, and will be replaced with outer callsite spans as long
            // as the inlined functions were `#[track_caller]`.
            loop {
                let scope_data = &frame.body.source_scopes[source_info.scope];

                if let Some((callee, callsite_span)) = scope_data.inlined {
                    // Stop inside the most nested non-`#[track_caller]` function,
                    // before ever reaching its caller (which is irrelevant).
                    if !callee.def.requires_caller_location(*self.tcx) {
                        return source_info.span;
                    }
                    source_info.span = callsite_span;
                }

                // Skip past all of the parents with `inlined: None`.
                match scope_data.inlined_parent_scope {
                    Some(parent) => source_info.scope = parent,
                    None => break,
                }
            }

            // Stop inside the most nested non-`#[track_caller]` function,
            // before ever reaching its caller (which is irrelevant).
            if !frame.instance.def.requires_caller_location(*self.tcx) {
                return source_info.span;
            }
        }

        span_bug!(self.cur_span(), "no non-`#[track_caller]` frame found")
    }

    /// Allocate a `const core::panic::Location` with the provided filename and line/column numbers.
    pub(crate) fn alloc_caller_location(
        &mut self,
        filename: Symbol,
        line: u32,
        col: u32,
    ) -> MPlaceTy<'tcx, M::Provenance> {
        let loc_details = &self.tcx.sess.opts.unstable_opts.location_detail;
        let file = if loc_details.file {
            self.allocate_str(filename.as_str(), MemoryKind::CallerLocation, Mutability::Not)
        } else {
            // FIXME: This creates a new allocation each time. It might be preferable to
            // perform this allocation only once, and re-use the `MPlaceTy`.
            // See https://github.com/rust-lang/rust/pull/89920#discussion_r730012398
            self.allocate_str("<redacted>", MemoryKind::CallerLocation, Mutability::Not)
        };
        let line = if loc_details.line { Scalar::from_u32(line) } else { Scalar::from_u32(0) };
        let col = if loc_details.column { Scalar::from_u32(col) } else { Scalar::from_u32(0) };

        // Allocate memory for `CallerLocation` struct.
        let loc_ty = self
            .tcx
            .bound_type_of(self.tcx.require_lang_item(LangItem::PanicLocation, None))
            .subst(*self.tcx, self.tcx.mk_substs([self.tcx.lifetimes.re_erased.into()].iter()));
        let loc_layout = self.layout_of(loc_ty).unwrap();
        // This can fail if rustc runs out of memory right here. Trying to emit an error would be
        // pointless, since that would require allocating more memory than a Location.
        let location = self.allocate(loc_layout, MemoryKind::CallerLocation).unwrap();

        // Initialize fields.
        self.write_immediate(file.to_ref(self), &self.mplace_field(&location, 0).unwrap().into())
            .expect("writing to memory we just allocated cannot fail");
        self.write_scalar(line, &self.mplace_field(&location, 1).unwrap().into())
            .expect("writing to memory we just allocated cannot fail");
        self.write_scalar(col, &self.mplace_field(&location, 2).unwrap().into())
            .expect("writing to memory we just allocated cannot fail");

        location
    }

    pub(crate) fn location_triple_for_span(&self, span: Span) -> (Symbol, u32, u32) {
        let topmost = span.ctxt().outer_expn().expansion_cause().unwrap_or(span);
        let caller = self.tcx.sess.source_map().lookup_char_pos(topmost.lo());
        (
            Symbol::intern(&caller.file.name.prefer_remapped().to_string_lossy()),
            u32::try_from(caller.line).unwrap(),
            u32::try_from(caller.col_display).unwrap().checked_add(1).unwrap(),
        )
    }

    pub fn alloc_caller_location_for_span(&mut self, span: Span) -> MPlaceTy<'tcx, M::Provenance> {
        let (file, line, column) = self.location_triple_for_span(span);
        self.alloc_caller_location(file, line, column)
    }
}