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