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>(tcx: TyCtxt<'tcx>, hir_body: &hir::Body<'tcx>) -> Vec<Span> {
103    struct HolesVisitor<'tcx> {
104        tcx: TyCtxt<'tcx>,
105        hole_spans: Vec<Span>,
106    }
107
108    impl<'tcx> Visitor<'tcx> for HolesVisitor<'tcx> {
109        /// We have special handling for nested items, but we still want to
110        /// traverse into nested bodies of things that are not considered items,
111        /// such as "anon consts" (e.g. array lengths).
112        type NestedFilter = nested_filter::OnlyBodies;
113
114        fn maybe_tcx(&mut self) -> TyCtxt<'tcx> {
115            self.tcx
116        }
117
118        /// We override `visit_nested_item` instead of `visit_item` because we
119        /// only need the item's span, not the item itself.
120        fn visit_nested_item(&mut self, id: hir::ItemId) -> Self::Result {
121            let span = self.tcx.def_span(id.owner_id.def_id);
122            self.visit_hole_span(span);
123            // Having visited this item, we don't care about its children,
124            // so don't call `walk_item`.
125        }
126
127        // We override `visit_expr` instead of the more specific expression
128        // visitors, so that we have direct access to the expression span.
129        fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
130            match expr.kind {
131                hir::ExprKind::Closure(_) | hir::ExprKind::ConstBlock(_) => {
132                    self.visit_hole_span(expr.span);
133                    // Having visited this expression, we don't care about its
134                    // children, so don't call `walk_expr`.
135                }
136
137                // For other expressions, recursively visit as normal.
138                _ => hir::intravisit::walk_expr(self, expr),
139            }
140        }
141    }
142    impl HolesVisitor<'_> {
143        fn visit_hole_span(&mut self, hole_span: Span) {
144            self.hole_spans.push(hole_span);
145        }
146    }
147
148    let mut visitor = HolesVisitor { tcx, hole_spans: vec![] };
149
150    visitor.visit_body(hir_body);
151    visitor.hole_spans
152}
153
154/// Use heuristics to detect HIR expression nodes that should be ignored during
155/// spans-from-MIR extraction.
156fn find_nodes_to_ignore<'tcx>(
157    tcx: TyCtxt<'tcx>,
158    def_id: LocalDefId,
159    hir_body: &hir::Body<'tcx>,
160) -> FxHashSet<HirId> {
161    /// Top-level visitor used by [`find_nodes_to_ignore`].
162    struct FindNodesToIgnoreVisitor<'tcx> {
163        tcx: TyCtxt<'tcx>,
164        typeck_results: &'tcx TypeckResults<'tcx>,
165        nodes_to_ignore: FxHashSet<HirId>,
166    }
167    /// Marks all expressions in a HIR subtree as ignored.
168    struct IgnoreAllSubexprsVisitor<'a, 'tcx> {
169        inner: &'a mut FindNodesToIgnoreVisitor<'tcx>,
170    }
171
172    impl<'tcx> Visitor<'tcx> for FindNodesToIgnoreVisitor<'tcx> {
173        fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
174            // Look for call expressions with a return type of `!` produced by a bang-macro.
175            // If we find one, ignore all subexpressions in the call's _arguments_.
176            // This avoids big regressions in coverage-report quality for code with assertions.
177            //
178            // FIXME(Zalathar): We might be able to remove this by extending `#[coverage(off)]`
179            // to support expressions, and using it the standard-library assert macros.
180            if let hir::ExprKind::Call(callee, args) = expr.kind
181                && let callee_ty = self.typeck_results.node_type(callee.hir_id)
182                && callee_ty.is_fn()
183                && let Some(output) = callee_ty.fn_sig(self.tcx).output().no_bound_vars()
184                && output.is_never()
185                && let ExpnKind::Macro(MacroKind::Bang, _) = expr.span.ctxt().outer_expn_data().kind
186            {
187                for arg in args {
188                    (IgnoreAllSubexprsVisitor { inner: self }).visit_expr(arg)
189                }
190            }
191
192            // Find and ignore block-expressions that are non-empty, as their subexpressions
193            // will produce more precise coverage spans.
194            if let hir::ExprKind::Block(block, _) = expr.kind
195                && let is_empty = (block.stmts.is_empty() && block.expr.is_none())
196                && !is_empty
197            {
198                // Ignore the block itself, but not its subexpressions.
199                self.nodes_to_ignore.insert(expr.hir_id);
200            }
201
202            hir::intravisit::walk_expr(self, expr);
203        }
204    }
205    impl<'tcx> Visitor<'tcx> for IgnoreAllSubexprsVisitor<'_, 'tcx> {
206        fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
207            self.inner.nodes_to_ignore.insert(expr.hir_id);
208            hir::intravisit::walk_expr(self, expr);
209        }
210    }
211
212    let mut visitor = FindNodesToIgnoreVisitor {
213        tcx,
214        typeck_results: tcx.typeck(def_id),
215        nodes_to_ignore: FxHashSet::default(),
216    };
217    visitor.visit_body(hir_body);
218    visitor.nodes_to_ignore
219}