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
16/// Collects coverage-related information during MIR building, to eventually be
17/// turned into a function's [`CoverageInfoHi`] when MIR building is complete.
18pub(crate) struct CoverageInfoBuilder {
19 /// Maps condition expressions to their enclosing `!`, for better instrumentation.
20 nots: FxHashMap<ExprId, NotInfo>,
21
22 markers: BlockMarkerGen,
23
24 /// Present if branch coverage is enabled.
25 branch_info: Option<BranchInfo>,
26 /// Present if MC/DC coverage is enabled.
27 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 /// When visiting the associated expression as a branch condition, treat this
38 /// enclosing `!` as the branch condition instead.
39 enclosing_not: ExprId,
40 /// True if the associated expression is nested within an odd number of `!`
41 /// expressions relative to `enclosing_not` (inclusive of `enclosing_not`).
42 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: 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 /// Creates a new coverage info builder, but only if coverage instrumentation
76 /// is enabled and `def_id` represents a function that is eligible for coverage.
77 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 /// Unary `!` expressions inside an `if` condition are lowered by lowering
91 /// their argument instead, and then reversing the then/else arms of that `if`.
92 ///
93 /// That's awkward for branch coverage instrumentation, so to work around that
94 /// we pre-emptively visit any affected `!` expressions, and record extra
95 /// information that [`Builder::visit_coverage_branch_condition`] can use to
96 /// synthesize branch instrumentation for the enclosing `!`.
97 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 // The information collected by this visitor is only needed when branch
101 // coverage or higher is enabled.
102 if self.branch_info.is_none() {
103 return;
104 }
105
106 self.visit_with_not_info(
107 thir,
108 unary_not,
109 // Set `is_flipped: false` for the `!` itself, so that its enclosed
110 // expression will have `is_flipped: true`.
111 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 // This expression has already been marked by an enclosing `!`.
118 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 // Invert the `is_flipped` flag for the contents of this `!`.
125 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 // All other expressions (including `&&` and `||`) don't need any
131 // special handling of their contents, so stop visiting.
132 _ => {}
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 // Separate path for handling branches when MC/DC is enabled.
145 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 // Bail out if branch coverage is not enabled.
159 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 // For simplicity, always return an info struct (without Option), even
182 // if there's nothing interesting in it.
183 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 /// If condition coverage is enabled, inject extra blocks and marker statements
194 /// that will let us track the value of the condition in `place`.
195 pub(crate) fn visit_coverage_standalone_condition(
196 &mut self,
197 mut expr_id: ExprId, // Expression giving the span of the condition
198 place: mir::Place<'tcx>, // Already holds the boolean condition value
199 block: &mut BasicBlock,
200 ) {
201 // Bail out if condition coverage is not enabled for this function.
202 let Some(coverage_info) = self.coverage_info.as_mut() else { return };
203 if !self.tcx.sess.instrument_coverage_condition() {
204 return;
205 };
206
207 // Remove any wrappers, so that we can inspect the real underlying expression.
208 while let ExprKind::Use { source: inner } | ExprKind::Scope { value: inner, .. } =
209 self.thir[expr_id].kind
210 {
211 expr_id = inner;
212 }
213 // If the expression is a lazy logical op, it will naturally get branch
214 // coverage as part of its normal lowering, so we can disregard it here.
215 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 // Using the boolean value that has already been stored in `place`, set up
222 // control flow in the shape of a diamond, so that we can place separate
223 // marker statements in the true and false blocks. The coverage MIR pass
224 // will use those markers to inject coverage counters as appropriate.
225 //
226 // block
227 // / \
228 // true_block false_block
229 // (marker) (marker)
230 // \ /
231 // join_block
232
233 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 // Separate path for handling branches when MC/DC is enabled.
242 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 // Any subsequent codegen in the caller should use the new join block.
254 *block = join_block;
255 }
256
257 /// If branch coverage is enabled, inject marker statements into `then_block`
258 /// and `else_block`, and record their IDs in the table of branch spans.
259 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 // Bail out if coverage is not enabled for this function.
266 let Some(coverage_info) = self.coverage_info.as_mut() else { return };
267
268 // If this condition expression is nested within one or more `!` expressions,
269 // replace it with the enclosing `!` collected by `visit_unary_not`.
270 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 /// If branch coverage is enabled, inject marker statements into `true_block`
289 /// and `false_block`, and record their IDs in the table of branches.
290 ///
291 /// Used to instrument let-else and if-let (including let-chains) for branch coverage.
292 pub(crate) fn visit_coverage_conditional_let(
293 &mut self,
294 pattern: &Pat<'tcx>, // Pattern that has been matched when the true path is taken
295 true_block: BasicBlock,
296 false_block: BasicBlock,
297 ) {
298 // Bail out if coverage is not enabled for this function.
299 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}
311

Provided by KDAB

Privacy Policy
Learn Rust with the experts
Find out more