rustc_mir_transform/coverage/
mod.rs
1pub(super) mod query;
2
3mod counters;
4mod graph;
5mod mappings;
6mod spans;
7#[cfg(test)]
8mod tests;
9mod unexpand;
10
11use rustc_hir as hir;
12use rustc_hir::intravisit::{Visitor, walk_expr};
13use rustc_middle::hir::map::Map;
14use rustc_middle::hir::nested_filter;
15use rustc_middle::mir::coverage::{
16 CoverageKind, DecisionInfo, FunctionCoverageInfo, Mapping, MappingKind,
17};
18use rustc_middle::mir::{self, BasicBlock, Statement, StatementKind, TerminatorKind};
19use rustc_middle::ty::TyCtxt;
20use rustc_span::Span;
21use rustc_span::def_id::LocalDefId;
22use tracing::{debug, debug_span, trace};
23
24use crate::coverage::counters::BcbCountersData;
25use crate::coverage::graph::CoverageGraph;
26use crate::coverage::mappings::ExtractedMappings;
27
28pub(super) struct InstrumentCoverage;
32
33impl<'tcx> crate::MirPass<'tcx> for InstrumentCoverage {
34 fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
35 sess.instrument_coverage()
36 }
37
38 fn run_pass(&self, tcx: TyCtxt<'tcx>, mir_body: &mut mir::Body<'tcx>) {
39 let mir_source = mir_body.source;
40
41 assert!(mir_source.promoted.is_none());
44
45 let def_id = mir_source.def_id().expect_local();
46
47 if !tcx.is_eligible_for_coverage(def_id) {
48 trace!("InstrumentCoverage skipped for {def_id:?} (not eligible)");
49 return;
50 }
51
52 match mir_body.basic_blocks[mir::START_BLOCK].terminator().kind {
55 TerminatorKind::Unreachable => {
56 trace!("InstrumentCoverage skipped for unreachable `START_BLOCK`");
57 return;
58 }
59 _ => {}
60 }
61
62 instrument_function_for_coverage(tcx, mir_body);
63 }
64
65 fn is_required(&self) -> bool {
66 false
67 }
68}
69
70fn instrument_function_for_coverage<'tcx>(tcx: TyCtxt<'tcx>, mir_body: &mut mir::Body<'tcx>) {
71 let def_id = mir_body.source.def_id();
72 let _span = debug_span!("instrument_function_for_coverage", ?def_id).entered();
73
74 let hir_info = extract_hir_info(tcx, def_id.expect_local());
75
76 let graph = CoverageGraph::from_mir(mir_body);
79
80 let extracted_mappings =
83 mappings::extract_all_mapping_info_from_mir(tcx, mir_body, &hir_info, &graph);
84
85 let mappings = create_mappings(&extracted_mappings);
86 if mappings.is_empty() {
87 debug!("no spans could be converted into valid mappings; skipping");
89 return;
90 }
91
92 let BcbCountersData { node_flow_data, priority_list } =
96 counters::prepare_bcb_counters_data(&graph);
97
98 inject_coverage_statements(mir_body, &graph);
100 inject_mcdc_statements(mir_body, &graph, &extracted_mappings);
101
102 let mcdc_num_condition_bitmaps = extracted_mappings
103 .mcdc_mappings
104 .iter()
105 .map(|&(mappings::MCDCDecision { decision_depth, .. }, _)| decision_depth)
106 .max()
107 .map_or(0, |max| usize::from(max) + 1);
108
109 mir_body.function_coverage_info = Some(Box::new(FunctionCoverageInfo {
110 function_source_hash: hir_info.function_source_hash,
111 body_span: hir_info.body_span,
112
113 node_flow_data,
114 priority_list,
115
116 mappings,
117
118 mcdc_bitmap_bits: extracted_mappings.mcdc_bitmap_bits,
119 mcdc_num_condition_bitmaps,
120 }));
121}
122
123fn create_mappings(extracted_mappings: &ExtractedMappings) -> Vec<Mapping> {
129 let ExtractedMappings {
131 code_mappings,
132 branch_pairs,
133 mcdc_bitmap_bits: _,
134 mcdc_degraded_branches,
135 mcdc_mappings,
136 } = extracted_mappings;
137 let mut mappings = Vec::new();
138
139 mappings.extend(code_mappings.iter().map(
140 |&mappings::CodeMapping { span, bcb }| {
142 let kind = MappingKind::Code { bcb };
143 Mapping { kind, span }
144 },
145 ));
146
147 mappings.extend(branch_pairs.iter().map(
148 |&mappings::BranchPair { span, true_bcb, false_bcb }| {
149 let kind = MappingKind::Branch { true_bcb, false_bcb };
150 Mapping { kind, span }
151 },
152 ));
153
154 mappings.extend(mcdc_degraded_branches.iter().map(
156 |&mappings::MCDCBranch {
157 span,
158 true_bcb,
159 false_bcb,
160 condition_info: _,
161 true_index: _,
162 false_index: _,
163 }| { Mapping { kind: MappingKind::Branch { true_bcb, false_bcb }, span } },
164 ));
165
166 for (decision, branches) in mcdc_mappings {
167 let conditions = branches
174 .into_iter()
175 .map(
176 |&mappings::MCDCBranch {
177 span,
178 true_bcb,
179 false_bcb,
180 condition_info,
181 true_index: _,
182 false_index: _,
183 }| {
184 Mapping {
185 kind: MappingKind::MCDCBranch {
186 true_bcb,
187 false_bcb,
188 mcdc_params: condition_info,
189 },
190 span,
191 }
192 },
193 )
194 .collect::<Vec<_>>();
195
196 let kind = MappingKind::MCDCDecision(DecisionInfo {
198 bitmap_idx: (decision.bitmap_idx + decision.num_test_vectors) as u32,
199 num_conditions: u16::try_from(conditions.len()).unwrap(),
200 });
201 let span = decision.span;
202 mappings.extend(std::iter::once(Mapping { kind, span }).chain(conditions.into_iter()));
203 }
204
205 mappings
206}
207
208fn inject_coverage_statements<'tcx>(mir_body: &mut mir::Body<'tcx>, graph: &CoverageGraph) {
210 for (bcb, data) in graph.iter_enumerated() {
211 let target_bb = data.leader_bb();
212 inject_statement(mir_body, CoverageKind::VirtualCounter { bcb }, target_bb);
213 }
214}
215
216fn inject_mcdc_statements<'tcx>(
219 mir_body: &mut mir::Body<'tcx>,
220 graph: &CoverageGraph,
221 extracted_mappings: &ExtractedMappings,
222) {
223 for (decision, conditions) in &extracted_mappings.mcdc_mappings {
224 for &end in &decision.end_bcbs {
226 let end_bb = graph[end].leader_bb();
227 inject_statement(
228 mir_body,
229 CoverageKind::TestVectorBitmapUpdate {
230 bitmap_idx: decision.bitmap_idx as u32,
231 decision_depth: decision.decision_depth,
232 },
233 end_bb,
234 );
235 }
236
237 for &mappings::MCDCBranch {
238 span: _,
239 true_bcb,
240 false_bcb,
241 condition_info: _,
242 true_index,
243 false_index,
244 } in conditions
245 {
246 for (index, bcb) in [(false_index, false_bcb), (true_index, true_bcb)] {
247 let bb = graph[bcb].leader_bb();
248 inject_statement(
249 mir_body,
250 CoverageKind::CondBitmapUpdate {
251 index: index as u32,
252 decision_depth: decision.decision_depth,
253 },
254 bb,
255 );
256 }
257 }
258 }
259}
260
261fn inject_statement(mir_body: &mut mir::Body<'_>, counter_kind: CoverageKind, bb: BasicBlock) {
262 debug!(" injecting statement {counter_kind:?} for {bb:?}");
263 let data = &mut mir_body[bb];
264 let source_info = data.terminator().source_info;
265 let statement = Statement { source_info, kind: StatementKind::Coverage(counter_kind) };
266 data.statements.insert(0, statement);
267}
268
269#[derive(Debug)]
271struct ExtractedHirInfo {
272 function_source_hash: u64,
273 is_async_fn: bool,
274 fn_sig_span_extended: Option<Span>,
277 body_span: Span,
278 hole_spans: Vec<Span>,
281}
282
283fn extract_hir_info<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> ExtractedHirInfo {
284 if tcx.is_synthetic_mir(def_id) {
289 return extract_hir_info(tcx, tcx.local_parent(def_id));
290 }
291
292 let hir_node = tcx.hir_node_by_def_id(def_id);
293 let fn_body_id = hir_node.body_id().expect("HIR node is a function with body");
294 let hir_body = tcx.hir().body(fn_body_id);
295
296 let maybe_fn_sig = hir_node.fn_sig();
297 let is_async_fn = maybe_fn_sig.is_some_and(|fn_sig| fn_sig.header.is_async());
298
299 let mut body_span = hir_body.value.span;
300
301 use hir::{Closure, Expr, ExprKind, Node};
302 if let Node::Expr(&Expr { kind: ExprKind::Closure(&Closure { fn_decl_span, .. }), .. }) =
306 hir_node
307 {
308 body_span = body_span.find_ancestor_in_same_ctxt(fn_decl_span).unwrap_or(body_span);
309 }
310
311 let fn_sig_span_extended = maybe_fn_sig
314 .map(|fn_sig| fn_sig.span)
315 .filter(|&fn_sig_span| {
316 let source_map = tcx.sess.source_map();
317 let file_idx = |span: Span| source_map.lookup_source_file_idx(span.lo());
318
319 fn_sig_span.eq_ctxt(body_span)
320 && fn_sig_span.hi() <= body_span.lo()
321 && file_idx(fn_sig_span) == file_idx(body_span)
322 })
323 .map(|fn_sig_span| fn_sig_span.with_hi(body_span.lo()));
325
326 let function_source_hash = hash_mir_source(tcx, hir_body);
327
328 let hole_spans = extract_hole_spans_from_hir(tcx, body_span, hir_body);
329
330 ExtractedHirInfo {
331 function_source_hash,
332 is_async_fn,
333 fn_sig_span_extended,
334 body_span,
335 hole_spans,
336 }
337}
338
339fn hash_mir_source<'tcx>(tcx: TyCtxt<'tcx>, hir_body: &'tcx hir::Body<'tcx>) -> u64 {
340 let owner = hir_body.id().hir_id.owner;
342 tcx.hir_owner_nodes(owner).opt_hash_including_bodies.unwrap().to_smaller_hash().as_u64()
343}
344
345fn extract_hole_spans_from_hir<'tcx>(
346 tcx: TyCtxt<'tcx>,
347 body_span: Span, hir_body: &hir::Body<'tcx>,
349) -> Vec<Span> {
350 struct HolesVisitor<'hir, F> {
351 hir: Map<'hir>,
352 visit_hole_span: F,
353 }
354
355 impl<'hir, F: FnMut(Span)> Visitor<'hir> for HolesVisitor<'hir, F> {
356 type NestedFilter = nested_filter::All;
362
363 fn nested_visit_map(&mut self) -> Self::Map {
364 self.hir
365 }
366
367 fn visit_item(&mut self, item: &'hir hir::Item<'hir>) {
368 (self.visit_hole_span)(item.span);
369 }
372
373 fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
376 match expr.kind {
377 hir::ExprKind::Closure(_) | hir::ExprKind::ConstBlock(_) => {
378 (self.visit_hole_span)(expr.span);
379 }
382
383 _ => walk_expr(self, expr),
385 }
386 }
387 }
388
389 let mut hole_spans = vec![];
390 let mut visitor = HolesVisitor {
391 hir: tcx.hir(),
392 visit_hole_span: |hole_span| {
393 if body_span.contains(hole_span) && body_span.eq_ctxt(hole_span) {
395 hole_spans.push(hole_span);
396 }
397 },
398 };
399
400 visitor.visit_body(hir_body);
401 hole_spans
402}