1use std::assert_matches::assert_matches;
2use std::collections::hash_map::Entry;
3
4use rustc_data_structures::fx::FxHashMap;
5use rustc_middle::mir::coverage::{BlockMarkerId, BranchSpan, CoverageInfoHi, CoverageKind};
6use rustc_middle::mir::{self, BasicBlock, SourceInfo, UnOp};
7use rustc_middle::thir::{ExprId, ExprKind, Pat, Thir};
8use rustc_middle::ty::TyCtxt;
9use rustc_span::def_id::LocalDefId;
10
11use crate::builder::coverageinfo::mcdc::MCDCInfoBuilder;
12use crate::builder::{Builder, CFG};
13
14mod mcdc;
15
16pub(crate) struct CoverageInfoBuilder {
19 nots: FxHashMap<ExprId, NotInfo>,
21
22 markers: BlockMarkerGen,
23
24 branch_info: Option<BranchInfo>,
26 mcdc_info: Option<MCDCInfoBuilder>,
28}
29
30#[derive(Default)]
31struct BranchInfo {
32 branch_spans: Vec<BranchSpan>,
33}
34
35#[derive(Clone, Copy)]
36struct NotInfo {
37 enclosing_not: ExprId,
40 is_flipped: bool,
43}
44
45#[derive(Default)]
46struct BlockMarkerGen {
47 num_block_markers: usize,
48}
49
50impl BlockMarkerGen {
51 fn next_block_marker_id(&mut self) -> BlockMarkerId {
52 let id = BlockMarkerId::from_usize(self.num_block_markers);
53 self.num_block_markers += 1;
54 id
55 }
56
57 fn inject_block_marker(
58 &mut self,
59 cfg: &mut CFG<'_>,
60 source_info: SourceInfo,
61 block: BasicBlock,
62 ) -> BlockMarkerId {
63 let id = self.next_block_marker_id();
64 let marker_statement = mir::Statement {
65 source_info,
66 kind: mir::StatementKind::Coverage(CoverageKind::BlockMarker { id }),
67 };
68 cfg.push(block, marker_statement);
69
70 id
71 }
72}
73
74impl CoverageInfoBuilder {
75 pub(crate) fn new_if_enabled(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<Self> {
78 if !tcx.sess.instrument_coverage() || !tcx.is_eligible_for_coverage(def_id) {
79 return None;
80 }
81
82 Some(Self {
83 nots: FxHashMap::default(),
84 markers: BlockMarkerGen::default(),
85 branch_info: tcx.sess.instrument_coverage_branch().then(BranchInfo::default),
86 mcdc_info: tcx.sess.instrument_coverage_mcdc().then(MCDCInfoBuilder::new),
87 })
88 }
89
90 pub(crate) fn visit_unary_not(&mut self, thir: &Thir<'_>, unary_not: ExprId) {
98 assert_matches!(thir[unary_not].kind, ExprKind::Unary { op: UnOp::Not, .. });
99
100 if self.branch_info.is_none() {
103 return;
104 }
105
106 self.visit_with_not_info(
107 thir,
108 unary_not,
109 NotInfo { enclosing_not: unary_not, is_flipped: false },
112 );
113 }
114
115 fn visit_with_not_info(&mut self, thir: &Thir<'_>, expr_id: ExprId, not_info: NotInfo) {
116 match self.nots.entry(expr_id) {
117 Entry::Occupied(_) => return,
119 Entry::Vacant(entry) => entry.insert(not_info),
120 };
121
122 match thir[expr_id].kind {
123 ExprKind::Unary { op: UnOp::Not, arg } => {
124 let not_info = NotInfo { is_flipped: !not_info.is_flipped, ..not_info };
126 self.visit_with_not_info(thir, arg, not_info);
127 }
128 ExprKind::Scope { value, .. } => self.visit_with_not_info(thir, value, not_info),
129 ExprKind::Use { source } => self.visit_with_not_info(thir, source, not_info),
130 _ => {}
133 }
134 }
135
136 fn register_two_way_branch<'tcx>(
137 &mut self,
138 tcx: TyCtxt<'tcx>,
139 cfg: &mut CFG<'tcx>,
140 source_info: SourceInfo,
141 true_block: BasicBlock,
142 false_block: BasicBlock,
143 ) {
144 if let Some(mcdc_info) = self.mcdc_info.as_mut() {
146 let inject_block_marker =
147 |source_info, block| self.markers.inject_block_marker(cfg, source_info, block);
148 mcdc_info.visit_evaluated_condition(
149 tcx,
150 source_info,
151 true_block,
152 false_block,
153 inject_block_marker,
154 );
155 return;
156 }
157
158 let Some(branch_info) = self.branch_info.as_mut() else { return };
160
161 let true_marker = self.markers.inject_block_marker(cfg, source_info, true_block);
162 let false_marker = self.markers.inject_block_marker(cfg, source_info, false_block);
163
164 branch_info.branch_spans.push(BranchSpan {
165 span: source_info.span,
166 true_marker,
167 false_marker,
168 });
169 }
170
171 pub(crate) fn into_done(self) -> Box<CoverageInfoHi> {
172 let Self { nots: _, markers: BlockMarkerGen { num_block_markers }, branch_info, mcdc_info } =
173 self;
174
175 let branch_spans =
176 branch_info.map(|branch_info| branch_info.branch_spans).unwrap_or_default();
177
178 let (mcdc_spans, mcdc_degraded_branch_spans) =
179 mcdc_info.map(MCDCInfoBuilder::into_done).unwrap_or_default();
180
181 Box::new(CoverageInfoHi {
184 num_block_markers,
185 branch_spans,
186 mcdc_degraded_branch_spans,
187 mcdc_spans,
188 })
189 }
190}
191
192impl<'tcx> Builder<'_, 'tcx> {
193 pub(crate) fn visit_coverage_standalone_condition(
196 &mut self,
197 mut expr_id: ExprId, place: mir::Place<'tcx>, block: &mut BasicBlock,
200 ) {
201 let Some(coverage_info) = self.coverage_info.as_mut() else { return };
203 if !self.tcx.sess.instrument_coverage_condition() {
204 return;
205 };
206
207 while let ExprKind::Use { source: inner } | ExprKind::Scope { value: inner, .. } =
209 self.thir[expr_id].kind
210 {
211 expr_id = inner;
212 }
213 if let ExprKind::LogicalOp { .. } = self.thir[expr_id].kind {
216 return;
217 }
218
219 let source_info = SourceInfo { span: self.thir[expr_id].span, scope: self.source_scope };
220
221 let true_block = self.cfg.start_new_block();
234 let false_block = self.cfg.start_new_block();
235 self.cfg.terminate(
236 *block,
237 source_info,
238 mir::TerminatorKind::if_(mir::Operand::Copy(place), true_block, false_block),
239 );
240
241 coverage_info.register_two_way_branch(
243 self.tcx,
244 &mut self.cfg,
245 source_info,
246 true_block,
247 false_block,
248 );
249
250 let join_block = self.cfg.start_new_block();
251 self.cfg.goto(true_block, source_info, join_block);
252 self.cfg.goto(false_block, source_info, join_block);
253 *block = join_block;
255 }
256
257 pub(crate) fn visit_coverage_branch_condition(
260 &mut self,
261 mut expr_id: ExprId,
262 mut then_block: BasicBlock,
263 mut else_block: BasicBlock,
264 ) {
265 let Some(coverage_info) = self.coverage_info.as_mut() else { return };
267
268 if let Some(&NotInfo { enclosing_not, is_flipped }) = coverage_info.nots.get(&expr_id) {
271 expr_id = enclosing_not;
272 if is_flipped {
273 std::mem::swap(&mut then_block, &mut else_block);
274 }
275 }
276
277 let source_info = SourceInfo { span: self.thir[expr_id].span, scope: self.source_scope };
278
279 coverage_info.register_two_way_branch(
280 self.tcx,
281 &mut self.cfg,
282 source_info,
283 then_block,
284 else_block,
285 );
286 }
287
288 pub(crate) fn visit_coverage_conditional_let(
293 &mut self,
294 pattern: &Pat<'tcx>, true_block: BasicBlock,
296 false_block: BasicBlock,
297 ) {
298 let Some(coverage_info) = self.coverage_info.as_mut() else { return };
300
301 let source_info = SourceInfo { span: pattern.span, scope: self.source_scope };
302 coverage_info.register_two_way_branch(
303 self.tcx,
304 &mut self.cfg,
305 source_info,
306 true_block,
307 false_block,
308 );
309 }
310}