Skip to main content

rustc_mir_transform/coverage/
hir_info.rs

1use rustc_data_structures::fx::FxHashSet;
2use rustc_hir::intravisit::Visitor;
3use rustc_hir::{self as hir, HirId};
4use rustc_middle::hir::nested_filter;
5use rustc_middle::mir;
6use rustc_middle::ty::{self, TyCtxt, TypeckResults};
7use rustc_span::def_id::LocalDefId;
8use rustc_span::{ExpnKind, MacroKind, Span};
9
10/// Function information extracted from HIR by the coverage instrumentor.
11#[derive(Debug)]
12pub(crate) struct ExtractedHirInfo {
13    pub(crate) function_source_hash: u64,
14    pub(crate) is_async_fn: bool,
15    /// The span of the function's signature, if available.
16    /// Must have the same context and filename as the body span.
17    pub(crate) fn_sig_span: Option<Span>,
18    pub(crate) body_span: Span,
19    /// "Holes" are regions within the function body (or its expansions) that
20    /// should not be included in coverage spans for this function
21    /// (e.g. closures and nested items).
22    pub(crate) hole_spans: Vec<Span>,
23    /// HIR nodes that should be ignored when extracting spans from marker
24    /// statements in MIR.
25    pub(crate) nodes_to_ignore: FxHashSet<HirId>,
26}
27
28pub(crate) fn extract_hir_info<'tcx>(
29    tcx: TyCtxt<'tcx>,
30    mir_body: &mir::Body<'tcx>,
31) -> ExtractedHirInfo {
32    let def_id: LocalDefId = {
33        let mut def_id = mir_body.source.def_id().expect_local();
34
35        // Synthetic by-move coroutine bodies don't have useful HIR of their own.
36        // Use the original coroutine body instead. These synthetic bodies are
37        // created with a coroutine type, so we can inspect that type as-is.
38        if tcx.is_synthetic_mir(def_id) {
39            match *tcx.type_of(def_id).instantiate_identity().skip_normalization().kind() {
40                ty::Coroutine(coroutine_def_id, _) => def_id = coroutine_def_id.expect_local(),
41                _ => def_id = tcx.local_parent(def_id),
42            }
43        }
44        def_id
45    };
46
47    let hir_node = tcx.hir_node_by_def_id(def_id);
48    let fn_body_id = hir_node.body_id().expect("HIR node is a function with body");
49    let hir_body = tcx.hir_body(fn_body_id);
50
51    let maybe_fn_sig = hir_node.fn_sig();
52    let is_async_fn = maybe_fn_sig.is_some_and(|fn_sig| fn_sig.header.is_async());
53
54    let mut body_span = hir_body.value.span;
55
56    // Unexpand a closure's body span back to the context of its declaration.
57    // This helps with closure bodies that consist of just a single bang-macro,
58    // and also with closure bodies produced by async desugaring.
59    if let hir::Node::Expr(expr) = hir_node
60        && let hir::ExprKind::Closure(closure) = expr.kind
61        && let Some(effective_body_span) =
62            body_span.find_ancestor_in_same_ctxt(closure.fn_decl_span)
63    {
64        body_span = effective_body_span;
65    }
66
67    // The actual signature span is only used if it has the same context and
68    // filename as the body, and precedes the body.
69    let fn_sig_span = maybe_fn_sig.map(|fn_sig| fn_sig.span).filter(|&fn_sig_span| {
70        let source_map = tcx.sess.source_map();
71        let file_idx = |span: Span| source_map.lookup_source_file_idx(span.lo());
72
73        fn_sig_span.eq_ctxt(body_span)
74            && fn_sig_span.hi() <= body_span.lo()
75            && file_idx(fn_sig_span) == file_idx(body_span)
76    });
77
78    let function_source_hash = hash_mir_source(tcx, hir_body);
79
80    let hole_spans = extract_hole_spans_from_hir(tcx, hir_body);
81    let nodes_to_ignore = find_nodes_to_ignore(tcx, def_id, hir_body);
82
83    ExtractedHirInfo {
84        function_source_hash,
85        is_async_fn,
86        fn_sig_span,
87        body_span,
88        hole_spans,
89        nodes_to_ignore,
90    }
91}
92
93fn hash_mir_source<'tcx>(tcx: TyCtxt<'tcx>, hir_body: &'tcx hir::Body<'tcx>) -> u64 {
94    let owner = hir_body.id().hir_id.owner;
95    tcx.hir_owner_nodes(owner)
96        .opt_hash
97        .expect("hash should be present when coverage instrumentation is enabled")
98        .to_smaller_hash()
99        .as_u64()
100}
101
102fn extract_hole_spans_from_hir<'tcx>(
103    tcx: TyCtxt<'tcx>,
104    hir_body: &'tcx hir::Body<'tcx>,
105) -> Vec<Span> {
106    struct HolesVisitor<'tcx> {
107        tcx: TyCtxt<'tcx>,
108        hole_spans: Vec<Span>,
109    }
110
111    impl<'tcx> Visitor<'tcx> for HolesVisitor<'tcx> {
112        /// We have special handling for nested items, but we still want to
113        /// traverse into nested bodies of things that are not considered items,
114        /// such as "anon consts" (e.g. array lengths).
115        type NestedFilter = nested_filter::OnlyBodies;
116
117        fn maybe_tcx(&mut self) -> TyCtxt<'tcx> {
118            self.tcx
119        }
120
121        /// We override `visit_nested_item` instead of `visit_item` because we
122        /// only need the item's span, not the item itself.
123        fn visit_nested_item(&mut self, id: hir::ItemId) -> Self::Result {
124            let span = self.tcx.def_span(id.owner_id.def_id);
125            self.visit_hole_span(span);
126            // Having visited this item, we don't care about its children,
127            // so don't call `walk_item`.
128        }
129
130        // We override `visit_expr` instead of the more specific expression
131        // visitors, so that we have direct access to the expression span.
132        fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
133            match expr.kind {
134                hir::ExprKind::Closure(_) | hir::ExprKind::ConstBlock(_) => {
135                    self.visit_hole_span(expr.span);
136                    // Having visited this expression, we don't care about its
137                    // children, so don't call `walk_expr`.
138                }
139
140                // For other expressions, recursively visit as normal.
141                _ => hir::intravisit::walk_expr(self, expr),
142            }
143        }
144    }
145    impl HolesVisitor<'_> {
146        fn visit_hole_span(&mut self, hole_span: Span) {
147            self.hole_spans.push(hole_span);
148        }
149    }
150
151    let mut visitor = HolesVisitor { tcx, hole_spans: vec![] };
152
153    visitor.visit_body(hir_body);
154    visitor.hole_spans
155}
156
157/// Use heuristics to detect HIR expression nodes that should be ignored during
158/// spans-from-MIR extraction.
159fn find_nodes_to_ignore<'tcx>(
160    tcx: TyCtxt<'tcx>,
161    def_id: LocalDefId,
162    hir_body: &'tcx hir::Body<'tcx>,
163) -> FxHashSet<HirId> {
164    /// Top-level visitor used by [`find_nodes_to_ignore`].
165    struct FindNodesToIgnoreVisitor<'tcx> {
166        tcx: TyCtxt<'tcx>,
167        typeck_results: &'tcx TypeckResults<'tcx>,
168        nodes_to_ignore: FxHashSet<HirId>,
169    }
170    /// Marks all expressions in a HIR subtree as ignored.
171    struct IgnoreAllSubexprsVisitor<'a, 'tcx> {
172        inner: &'a mut FindNodesToIgnoreVisitor<'tcx>,
173    }
174
175    impl<'tcx> Visitor<'tcx> for FindNodesToIgnoreVisitor<'tcx> {
176        fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
177            // Look for call expressions with a return type of `!` produced by a bang-macro.
178            // If we find one, ignore all subexpressions in the call's _arguments_.
179            // This avoids big regressions in coverage-report quality for code with assertions.
180            //
181            // FIXME(Zalathar): We might be able to remove this by extending `#[coverage(off)]`
182            // to support expressions, and using it the standard-library assert macros.
183            if let hir::ExprKind::Call(callee, args) = expr.kind
184                && let callee_ty = self.typeck_results.node_type(callee.hir_id)
185                && callee_ty.is_fn()
186                && let Some(output) = callee_ty.fn_sig(self.tcx).output().no_bound_vars()
187                && output.is_never()
188                && let ExpnKind::Macro(MacroKind::Bang, _) = expr.span.ctxt().outer_expn_data().kind
189            {
190                for arg in args {
191                    (IgnoreAllSubexprsVisitor { inner: self }).visit_expr(arg)
192                }
193            }
194
195            // Find and ignore block-expressions that are non-empty, as their subexpressions
196            // will produce more precise coverage spans.
197            if let hir::ExprKind::Block(block, _) = expr.kind
198                && let is_empty = (block.stmts.is_empty() && block.expr.is_none())
199                && !is_empty
200            {
201                // Ignore the block itself, but not its subexpressions.
202                self.nodes_to_ignore.insert(expr.hir_id);
203            }
204
205            hir::intravisit::walk_expr(self, expr);
206        }
207    }
208    impl<'tcx> Visitor<'tcx> for IgnoreAllSubexprsVisitor<'_, 'tcx> {
209        fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
210            self.inner.nodes_to_ignore.insert(expr.hir_id);
211            hir::intravisit::walk_expr(self, expr);
212        }
213    }
214
215    let mut visitor = FindNodesToIgnoreVisitor {
216        tcx,
217        typeck_results: tcx.typeck(def_id),
218        nodes_to_ignore: FxHashSet::default(),
219    };
220    visitor.visit_body(hir_body);
221    visitor.nodes_to_ignore
222}