rustc_mir_transform/coverage/
hir_info.rs1use 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#[derive(Debug)]
12pub(crate) struct ExtractedHirInfo {
13 pub(crate) function_source_hash: u64,
14 pub(crate) is_async_fn: bool,
15 pub(crate) fn_sig_span: Option<Span>,
18 pub(crate) body_span: Span,
19 pub(crate) hole_spans: Vec<Span>,
23 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 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 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 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 type NestedFilter = nested_filter::OnlyBodies;
113
114 fn maybe_tcx(&mut self) -> TyCtxt<'tcx> {
115 self.tcx
116 }
117
118 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 }
126
127 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 }
136
137 _ => 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
154fn find_nodes_to_ignore<'tcx>(
157 tcx: TyCtxt<'tcx>,
158 def_id: LocalDefId,
159 hir_body: &hir::Body<'tcx>,
160) -> FxHashSet<HirId> {
161 struct FindNodesToIgnoreVisitor<'tcx> {
163 tcx: TyCtxt<'tcx>,
164 typeck_results: &'tcx TypeckResults<'tcx>,
165 nodes_to_ignore: FxHashSet<HirId>,
166 }
167 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 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 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 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}