1 | use std::assert_matches::assert_matches; |
---|---|
2 | use std::collections::hash_map::Entry; |
3 | |
4 | use rustc_data_structures::fx::FxHashMap; |
5 | use rustc_middle::mir::coverage::{BlockMarkerId, BranchSpan, CoverageInfoHi, CoverageKind}; |
6 | use rustc_middle::mir::{self, BasicBlock, SourceInfo, UnOp}; |
7 | use rustc_middle::thir::{ExprId, ExprKind, Pat, Thir}; |
8 | use rustc_middle::ty::TyCtxt; |
9 | use rustc_span::def_id::LocalDefId; |
10 | |
11 | use crate::builder::coverageinfo::mcdc::MCDCInfoBuilder; |
12 | use crate::builder::{Builder, CFG}; |
13 | |
14 | mod 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. |
18 | pub(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)] |
31 | struct BranchInfo { |
32 | branch_spans: Vec<BranchSpan>, |
33 | } |
34 | |
35 | #[derive(Clone, Copy)] |
36 | struct 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)] |
46 | struct BlockMarkerGen { |
47 | num_block_markers: usize, |
48 | } |
49 | |
50 | impl 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 | |
74 | impl 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 | |
192 | impl<'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 |
Definitions
- CoverageInfoBuilder
- nots
- markers
- branch_info
- mcdc_info
- BranchInfo
- branch_spans
- NotInfo
- enclosing_not
- is_flipped
- BlockMarkerGen
- num_block_markers
- next_block_marker_id
- inject_block_marker
- new_if_enabled
- visit_unary_not
- visit_with_not_info
- arg
- value
- source
- register_two_way_branch
- into_done
- num_block_markers
- branch_info
- mcdc_info
- visit_coverage_standalone_condition
- visit_coverage_branch_condition
- enclosing_not
- is_flipped
Learn Rust with the experts
Find out more