|
| 1 | +mod mcdc; |
1 | 2 | use std::assert_matches::assert_matches;
|
2 | 3 | use std::collections::hash_map::Entry;
|
3 |
| -use std::collections::VecDeque; |
4 | 4 |
|
5 | 5 | use rustc_data_structures::fx::FxHashMap;
|
6 |
| -use rustc_middle::mir::coverage::{ |
7 |
| - BlockMarkerId, BranchSpan, ConditionId, ConditionInfo, CoverageKind, MCDCBranchSpan, |
8 |
| - MCDCDecisionSpan, |
9 |
| -}; |
| 6 | +use rustc_middle::mir::coverage::{BlockMarkerId, BranchSpan, CoverageKind}; |
10 | 7 | use rustc_middle::mir::{self, BasicBlock, SourceInfo, UnOp};
|
11 |
| -use rustc_middle::thir::{ExprId, ExprKind, LogicalOp, Thir}; |
| 8 | +use rustc_middle::thir::{ExprId, ExprKind, Thir}; |
12 | 9 | use rustc_middle::ty::TyCtxt;
|
13 | 10 | use rustc_span::def_id::LocalDefId;
|
14 |
| -use rustc_span::Span; |
15 | 11 |
|
| 12 | +use crate::build::coverageinfo::mcdc::MCDCInfoBuilder; |
16 | 13 | use crate::build::{Builder, CFG};
|
17 |
| -use crate::errors::MCDCExceedsConditionNumLimit; |
18 | 14 |
|
19 | 15 | pub(crate) struct BranchInfoBuilder {
|
20 | 16 | /// Maps condition expressions to their enclosing `!`, for better instrumentation.
|
@@ -159,241 +155,6 @@ impl BranchInfoBuilder {
|
159 | 155 | }
|
160 | 156 | }
|
161 | 157 |
|
162 |
| -/// The MCDC bitmap scales exponentially (2^n) based on the number of conditions seen, |
163 |
| -/// So llvm sets a maximum value prevents the bitmap footprint from growing too large without the user's knowledge. |
164 |
| -/// This limit may be relaxed if the [upstream change](https://github.com/llvm/llvm-project/pull/82448) is merged. |
165 |
| -const MAX_CONDITIONS_NUM_IN_DECISION: usize = 6; |
166 |
| - |
167 |
| -#[derive(Default)] |
168 |
| -struct MCDCDecisionCtx { |
169 |
| - /// To construct condition evaluation tree. |
170 |
| - decision_stack: VecDeque<ConditionInfo>, |
171 |
| - processing_decision: Option<MCDCDecisionSpan>, |
172 |
| -} |
173 |
| - |
174 |
| -struct MCDCState { |
175 |
| - decision_ctx_stack: Vec<MCDCDecisionCtx>, |
176 |
| -} |
177 |
| - |
178 |
| -impl MCDCState { |
179 |
| - fn new() -> Self { |
180 |
| - Self { decision_ctx_stack: vec![MCDCDecisionCtx::default()] } |
181 |
| - } |
182 |
| - |
183 |
| - /// Decision depth is given as a u16 to reduce the size of the `CoverageKind`, |
184 |
| - /// as it is very unlikely that the depth ever reaches 2^16. |
185 |
| - #[inline] |
186 |
| - fn decision_depth(&self) -> u16 { |
187 |
| - match u16::try_from(self.decision_ctx_stack.len()) |
188 |
| - .expect( |
189 |
| - "decision depth did not fit in u16, this is likely to be an instrumentation error", |
190 |
| - ) |
191 |
| - .checked_sub(1) |
192 |
| - { |
193 |
| - Some(d) => d, |
194 |
| - None => bug!("Unexpected empty decision stack"), |
195 |
| - } |
196 |
| - } |
197 |
| - |
198 |
| - // At first we assign ConditionIds for each sub expression. |
199 |
| - // If the sub expression is composite, re-assign its ConditionId to its LHS and generate a new ConditionId for its RHS. |
200 |
| - // |
201 |
| - // Example: "x = (A && B) || (C && D) || (D && F)" |
202 |
| - // |
203 |
| - // Visit Depth1: |
204 |
| - // (A && B) || (C && D) || (D && F) |
205 |
| - // ^-------LHS--------^ ^-RHS--^ |
206 |
| - // ID=1 ID=2 |
207 |
| - // |
208 |
| - // Visit LHS-Depth2: |
209 |
| - // (A && B) || (C && D) |
210 |
| - // ^-LHS--^ ^-RHS--^ |
211 |
| - // ID=1 ID=3 |
212 |
| - // |
213 |
| - // Visit LHS-Depth3: |
214 |
| - // (A && B) |
215 |
| - // LHS RHS |
216 |
| - // ID=1 ID=4 |
217 |
| - // |
218 |
| - // Visit RHS-Depth3: |
219 |
| - // (C && D) |
220 |
| - // LHS RHS |
221 |
| - // ID=3 ID=5 |
222 |
| - // |
223 |
| - // Visit RHS-Depth2: (D && F) |
224 |
| - // LHS RHS |
225 |
| - // ID=2 ID=6 |
226 |
| - // |
227 |
| - // Visit Depth1: |
228 |
| - // (A && B) || (C && D) || (D && F) |
229 |
| - // ID=1 ID=4 ID=3 ID=5 ID=2 ID=6 |
230 |
| - // |
231 |
| - // A node ID of '0' always means MC/DC isn't being tracked. |
232 |
| - // |
233 |
| - // If a "next" node ID is '0', it means it's the end of the test vector. |
234 |
| - // |
235 |
| - // As the compiler tracks expression in pre-order, we can ensure that condition info of parents are always properly assigned when their children are visited. |
236 |
| - // - If the op is AND, the "false_next" of LHS and RHS should be the parent's "false_next". While "true_next" of the LHS is the RHS, the "true next" of RHS is the parent's "true_next". |
237 |
| - // - If the op is OR, the "true_next" of LHS and RHS should be the parent's "true_next". While "false_next" of the LHS is the RHS, the "false next" of RHS is the parent's "false_next". |
238 |
| - fn record_conditions(&mut self, op: LogicalOp, span: Span) { |
239 |
| - let decision_depth = self.decision_depth(); |
240 |
| - let Some(decision_ctx) = self.decision_ctx_stack.last_mut() else { |
241 |
| - bug!("Unexpected empty decision_ctx_stack") |
242 |
| - }; |
243 |
| - let decision = match decision_ctx.processing_decision.as_mut() { |
244 |
| - Some(decision) => { |
245 |
| - decision.span = decision.span.to(span); |
246 |
| - decision |
247 |
| - } |
248 |
| - None => decision_ctx.processing_decision.insert(MCDCDecisionSpan { |
249 |
| - span, |
250 |
| - conditions_num: 0, |
251 |
| - end_markers: vec![], |
252 |
| - decision_depth, |
253 |
| - }), |
254 |
| - }; |
255 |
| - |
256 |
| - let parent_condition = decision_ctx.decision_stack.pop_back().unwrap_or_default(); |
257 |
| - let lhs_id = if parent_condition.condition_id == ConditionId::NONE { |
258 |
| - decision.conditions_num += 1; |
259 |
| - ConditionId::from(decision.conditions_num) |
260 |
| - } else { |
261 |
| - parent_condition.condition_id |
262 |
| - }; |
263 |
| - |
264 |
| - decision.conditions_num += 1; |
265 |
| - let rhs_condition_id = ConditionId::from(decision.conditions_num); |
266 |
| - |
267 |
| - let (lhs, rhs) = match op { |
268 |
| - LogicalOp::And => { |
269 |
| - let lhs = ConditionInfo { |
270 |
| - condition_id: lhs_id, |
271 |
| - true_next_id: rhs_condition_id, |
272 |
| - false_next_id: parent_condition.false_next_id, |
273 |
| - }; |
274 |
| - let rhs = ConditionInfo { |
275 |
| - condition_id: rhs_condition_id, |
276 |
| - true_next_id: parent_condition.true_next_id, |
277 |
| - false_next_id: parent_condition.false_next_id, |
278 |
| - }; |
279 |
| - (lhs, rhs) |
280 |
| - } |
281 |
| - LogicalOp::Or => { |
282 |
| - let lhs = ConditionInfo { |
283 |
| - condition_id: lhs_id, |
284 |
| - true_next_id: parent_condition.true_next_id, |
285 |
| - false_next_id: rhs_condition_id, |
286 |
| - }; |
287 |
| - let rhs = ConditionInfo { |
288 |
| - condition_id: rhs_condition_id, |
289 |
| - true_next_id: parent_condition.true_next_id, |
290 |
| - false_next_id: parent_condition.false_next_id, |
291 |
| - }; |
292 |
| - (lhs, rhs) |
293 |
| - } |
294 |
| - }; |
295 |
| - // We visit expressions tree in pre-order, so place the left-hand side on the top. |
296 |
| - decision_ctx.decision_stack.push_back(rhs); |
297 |
| - decision_ctx.decision_stack.push_back(lhs); |
298 |
| - } |
299 |
| - |
300 |
| - fn take_condition( |
301 |
| - &mut self, |
302 |
| - true_marker: BlockMarkerId, |
303 |
| - false_marker: BlockMarkerId, |
304 |
| - ) -> (Option<ConditionInfo>, Option<MCDCDecisionSpan>) { |
305 |
| - let Some(decision_ctx) = self.decision_ctx_stack.last_mut() else { |
306 |
| - bug!("Unexpected empty decision_ctx_stack") |
307 |
| - }; |
308 |
| - let Some(condition_info) = decision_ctx.decision_stack.pop_back() else { |
309 |
| - return (None, None); |
310 |
| - }; |
311 |
| - let Some(decision) = decision_ctx.processing_decision.as_mut() else { |
312 |
| - bug!("Processing decision should have been created before any conditions are taken"); |
313 |
| - }; |
314 |
| - if condition_info.true_next_id == ConditionId::NONE { |
315 |
| - decision.end_markers.push(true_marker); |
316 |
| - } |
317 |
| - if condition_info.false_next_id == ConditionId::NONE { |
318 |
| - decision.end_markers.push(false_marker); |
319 |
| - } |
320 |
| - |
321 |
| - if decision_ctx.decision_stack.is_empty() { |
322 |
| - (Some(condition_info), decision_ctx.processing_decision.take()) |
323 |
| - } else { |
324 |
| - (Some(condition_info), None) |
325 |
| - } |
326 |
| - } |
327 |
| -} |
328 |
| - |
329 |
| -struct MCDCInfoBuilder { |
330 |
| - branch_spans: Vec<MCDCBranchSpan>, |
331 |
| - decision_spans: Vec<MCDCDecisionSpan>, |
332 |
| - state: MCDCState, |
333 |
| -} |
334 |
| - |
335 |
| -impl MCDCInfoBuilder { |
336 |
| - fn new() -> Self { |
337 |
| - Self { branch_spans: vec![], decision_spans: vec![], state: MCDCState::new() } |
338 |
| - } |
339 |
| - |
340 |
| - fn visit_evaluated_condition( |
341 |
| - &mut self, |
342 |
| - tcx: TyCtxt<'_>, |
343 |
| - source_info: SourceInfo, |
344 |
| - true_block: BasicBlock, |
345 |
| - false_block: BasicBlock, |
346 |
| - mut inject_block_marker: impl FnMut(SourceInfo, BasicBlock) -> BlockMarkerId, |
347 |
| - ) { |
348 |
| - let true_marker = inject_block_marker(source_info, true_block); |
349 |
| - let false_marker = inject_block_marker(source_info, false_block); |
350 |
| - |
351 |
| - let decision_depth = self.state.decision_depth(); |
352 |
| - let (mut condition_info, decision_result) = |
353 |
| - self.state.take_condition(true_marker, false_marker); |
354 |
| - // take_condition() returns Some for decision_result when the decision stack |
355 |
| - // is empty, i.e. when all the conditions of the decision were instrumented, |
356 |
| - // and the decision is "complete". |
357 |
| - if let Some(decision) = decision_result { |
358 |
| - match decision.conditions_num { |
359 |
| - 0 => { |
360 |
| - unreachable!("Decision with no condition is not expected"); |
361 |
| - } |
362 |
| - 1..=MAX_CONDITIONS_NUM_IN_DECISION => { |
363 |
| - self.decision_spans.push(decision); |
364 |
| - } |
365 |
| - _ => { |
366 |
| - // Do not generate mcdc mappings and statements for decisions with too many conditions. |
367 |
| - let rebase_idx = self.branch_spans.len() - decision.conditions_num + 1; |
368 |
| - for branch in &mut self.branch_spans[rebase_idx..] { |
369 |
| - branch.condition_info = None; |
370 |
| - } |
371 |
| - |
372 |
| - // ConditionInfo of this branch shall also be reset. |
373 |
| - condition_info = None; |
374 |
| - |
375 |
| - tcx.dcx().emit_warn(MCDCExceedsConditionNumLimit { |
376 |
| - span: decision.span, |
377 |
| - conditions_num: decision.conditions_num, |
378 |
| - max_conditions_num: MAX_CONDITIONS_NUM_IN_DECISION, |
379 |
| - }); |
380 |
| - } |
381 |
| - } |
382 |
| - } |
383 |
| - self.branch_spans.push(MCDCBranchSpan { |
384 |
| - span: source_info.span, |
385 |
| - condition_info, |
386 |
| - true_marker, |
387 |
| - false_marker, |
388 |
| - decision_depth, |
389 |
| - }); |
390 |
| - } |
391 |
| - |
392 |
| - fn into_done(self) -> (Vec<MCDCDecisionSpan>, Vec<MCDCBranchSpan>) { |
393 |
| - (self.decision_spans, self.branch_spans) |
394 |
| - } |
395 |
| -} |
396 |
| - |
397 | 158 | impl Builder<'_, '_> {
|
398 | 159 | /// If branch coverage is enabled, inject marker statements into `then_block`
|
399 | 160 | /// and `else_block`, and record their IDs in the table of branch spans.
|
@@ -434,30 +195,4 @@ impl Builder<'_, '_> {
|
434 | 195 |
|
435 | 196 | branch_info.add_two_way_branch(&mut self.cfg, source_info, then_block, else_block);
|
436 | 197 | }
|
437 |
| - |
438 |
| - pub(crate) fn visit_coverage_branch_operation(&mut self, logical_op: LogicalOp, span: Span) { |
439 |
| - if let Some(branch_info) = self.coverage_branch_info.as_mut() |
440 |
| - && let Some(mcdc_info) = branch_info.mcdc_info.as_mut() |
441 |
| - { |
442 |
| - mcdc_info.state.record_conditions(logical_op, span); |
443 |
| - } |
444 |
| - } |
445 |
| - |
446 |
| - pub(crate) fn mcdc_increment_depth_if_enabled(&mut self) { |
447 |
| - if let Some(branch_info) = self.coverage_branch_info.as_mut() |
448 |
| - && let Some(mcdc_info) = branch_info.mcdc_info.as_mut() |
449 |
| - { |
450 |
| - mcdc_info.state.decision_ctx_stack.push(MCDCDecisionCtx::default()); |
451 |
| - }; |
452 |
| - } |
453 |
| - |
454 |
| - pub(crate) fn mcdc_decrement_depth_if_enabled(&mut self) { |
455 |
| - if let Some(branch_info) = self.coverage_branch_info.as_mut() |
456 |
| - && let Some(mcdc_info) = branch_info.mcdc_info.as_mut() |
457 |
| - { |
458 |
| - if mcdc_info.state.decision_ctx_stack.pop().is_none() { |
459 |
| - bug!("Unexpected empty decision stack"); |
460 |
| - } |
461 |
| - }; |
462 |
| - } |
463 | 198 | }
|
0 commit comments