Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 035e0a8

Browse files
author
zhuyunxing
committedApr 30, 2024
coverage. Split mcdc to a sub module of coverageinfo
1 parent 7823bf0 commit 035e0a8

File tree

2 files changed

+333
-290
lines changed

2 files changed

+333
-290
lines changed
 
Lines changed: 55 additions & 290 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,54 @@
1+
mod mcdc;
12
use std::assert_matches::assert_matches;
23
use std::collections::hash_map::Entry;
3-
use std::collections::VecDeque;
44

55
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};
107
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};
129
use rustc_middle::ty::TyCtxt;
1310
use rustc_span::def_id::LocalDefId;
14-
use rustc_span::Span;
1511

1612
use crate::build::{Builder, CFG};
17-
use crate::errors::MCDCExceedsConditionNumLimit;
13+
use mcdc::MCDCInfoBuilder;
1814

1915
pub(crate) struct BranchInfoBuilder {
2016
/// Maps condition expressions to their enclosing `!`, for better instrumentation.
2117
nots: FxHashMap<ExprId, NotInfo>,
18+
block_marker_gen: BlockMarkerGen,
2219

23-
num_block_markers: usize,
2420
branch_spans: Vec<BranchSpan>,
2521

26-
mcdc_branch_spans: Vec<MCDCBranchSpan>,
27-
mcdc_decision_spans: Vec<MCDCDecisionSpan>,
28-
mcdc_state: Option<MCDCState>,
22+
mcdc_builder: Option<MCDCInfoBuilder>,
23+
}
24+
25+
#[derive(Default)]
26+
struct BlockMarkerGen {
27+
num_block_markers: usize,
28+
}
29+
30+
impl BlockMarkerGen {
31+
fn next_block_marker_id(&mut self) -> BlockMarkerId {
32+
let id = BlockMarkerId::from_usize(self.num_block_markers);
33+
self.num_block_markers += 1;
34+
id
35+
}
36+
37+
fn inject_block_marker(
38+
&mut self,
39+
cfg: &mut CFG<'_>,
40+
source_info: SourceInfo,
41+
block: BasicBlock,
42+
) -> BlockMarkerId {
43+
let id = self.next_block_marker_id();
44+
let marker_statement = mir::Statement {
45+
source_info,
46+
kind: mir::StatementKind::Coverage(CoverageKind::BlockMarker { id }),
47+
};
48+
cfg.push(block, marker_statement);
49+
50+
id
51+
}
2952
}
3053

3154
#[derive(Clone, Copy)]
@@ -45,11 +68,9 @@ impl BranchInfoBuilder {
4568
if tcx.sess.instrument_coverage_branch() && tcx.is_eligible_for_coverage(def_id) {
4669
Some(Self {
4770
nots: FxHashMap::default(),
48-
num_block_markers: 0,
71+
block_marker_gen: BlockMarkerGen::default(),
4972
branch_spans: vec![],
50-
mcdc_branch_spans: vec![],
51-
mcdc_decision_spans: vec![],
52-
mcdc_state: MCDCState::new_if_enabled(tcx),
73+
mcdc_builder: tcx.sess.instrument_coverage_mcdc().then(MCDCInfoBuilder::new),
5374
})
5475
} else {
5576
None
@@ -96,99 +117,35 @@ impl BranchInfoBuilder {
96117
}
97118
}
98119

99-
fn fetch_mcdc_condition_info(
100-
&mut self,
101-
tcx: TyCtxt<'_>,
102-
true_marker: BlockMarkerId,
103-
false_marker: BlockMarkerId,
104-
) -> Option<(u16, ConditionInfo)> {
105-
let mcdc_state = self.mcdc_state.as_mut()?;
106-
let decision_depth = mcdc_state.decision_depth();
107-
let (mut condition_info, decision_result) =
108-
mcdc_state.take_condition(true_marker, false_marker);
109-
// take_condition() returns Some for decision_result when the decision stack
110-
// is empty, i.e. when all the conditions of the decision were instrumented,
111-
// and the decision is "complete".
112-
if let Some(decision) = decision_result {
113-
match decision.conditions_num {
114-
0 => {
115-
unreachable!("Decision with no condition is not expected");
116-
}
117-
1..=MAX_CONDITIONS_NUM_IN_DECISION => {
118-
self.mcdc_decision_spans.push(decision);
119-
}
120-
_ => {
121-
// Do not generate mcdc mappings and statements for decisions with too many conditions.
122-
let rebase_idx = self.mcdc_branch_spans.len() - decision.conditions_num + 1;
123-
for branch in &mut self.mcdc_branch_spans[rebase_idx..] {
124-
branch.condition_info = None;
125-
}
126-
127-
// ConditionInfo of this branch shall also be reset.
128-
condition_info = None;
129-
130-
tcx.dcx().emit_warn(MCDCExceedsConditionNumLimit {
131-
span: decision.span,
132-
conditions_num: decision.conditions_num,
133-
max_conditions_num: MAX_CONDITIONS_NUM_IN_DECISION,
134-
});
135-
}
136-
}
137-
}
138-
condition_info.map(|cond_info| (decision_depth, cond_info))
139-
}
140-
141120
fn add_two_way_branch<'tcx>(
142121
&mut self,
143122
cfg: &mut CFG<'tcx>,
144123
source_info: SourceInfo,
145124
true_block: BasicBlock,
146125
false_block: BasicBlock,
147126
) {
148-
let true_marker = self.inject_block_marker(cfg, source_info, true_block);
149-
let false_marker = self.inject_block_marker(cfg, source_info, false_block);
127+
let true_marker = self.block_marker_gen.inject_block_marker(cfg, source_info, true_block);
128+
let false_marker = self.block_marker_gen.inject_block_marker(cfg, source_info, false_block);
150129

151130
self.branch_spans.push(BranchSpan { span: source_info.span, true_marker, false_marker });
152131
}
153132

154-
fn next_block_marker_id(&mut self) -> BlockMarkerId {
155-
let id = BlockMarkerId::from_usize(self.num_block_markers);
156-
self.num_block_markers += 1;
157-
id
158-
}
159-
160-
fn inject_block_marker(
161-
&mut self,
162-
cfg: &mut CFG<'_>,
163-
source_info: SourceInfo,
164-
block: BasicBlock,
165-
) -> BlockMarkerId {
166-
let id = self.next_block_marker_id();
167-
168-
let marker_statement = mir::Statement {
169-
source_info,
170-
kind: mir::StatementKind::Coverage(CoverageKind::BlockMarker { id }),
171-
};
172-
cfg.push(block, marker_statement);
173-
174-
id
175-
}
176-
177133
pub(crate) fn into_done(self) -> Option<Box<mir::coverage::BranchInfo>> {
178134
let Self {
179135
nots: _,
180-
num_block_markers,
136+
block_marker_gen: BlockMarkerGen { num_block_markers },
181137
branch_spans,
182-
mcdc_branch_spans,
183-
mcdc_decision_spans,
184-
mcdc_state: _,
138+
mcdc_builder,
185139
} = self;
186140

187141
if num_block_markers == 0 {
188142
assert!(branch_spans.is_empty());
189143
return None;
190144
}
191145

146+
let (mcdc_decision_spans, mcdc_branch_spans) =
147+
mcdc_builder.map(MCDCInfoBuilder::into_done).unwrap_or_default();
148+
192149
Some(Box::new(mir::coverage::BranchInfo {
193150
num_block_markers,
194151
branch_spans,
@@ -198,168 +155,6 @@ impl BranchInfoBuilder {
198155
}
199156
}
200157

201-
/// The MCDC bitmap scales exponentially (2^n) based on the number of conditions seen,
202-
/// So llvm sets a maximum value prevents the bitmap footprint from growing too large without the user's knowledge.
203-
/// This limit may be relaxed if the [upstream change](https://github.com/llvm/llvm-project/pull/82448) is merged.
204-
const MAX_CONDITIONS_NUM_IN_DECISION: usize = 6;
205-
206-
#[derive(Default)]
207-
struct MCDCDecisionCtx {
208-
/// To construct condition evaluation tree.
209-
decision_stack: VecDeque<ConditionInfo>,
210-
processing_decision: Option<MCDCDecisionSpan>,
211-
}
212-
213-
struct MCDCState {
214-
decision_ctx_stack: Vec<MCDCDecisionCtx>,
215-
}
216-
217-
impl MCDCState {
218-
fn new_if_enabled(tcx: TyCtxt<'_>) -> Option<Self> {
219-
tcx.sess
220-
.instrument_coverage_mcdc()
221-
.then(|| Self { decision_ctx_stack: vec![MCDCDecisionCtx::default()] })
222-
}
223-
224-
/// Decision depth is given as a u16 to reduce the size of the `CoverageKind`,
225-
/// as it is very unlikely that the depth ever reaches 2^16.
226-
#[inline]
227-
fn decision_depth(&self) -> u16 {
228-
u16::try_from(
229-
self.decision_ctx_stack.len().checked_sub(1).expect("Unexpected empty decision stack"),
230-
)
231-
.expect("decision depth did not fit in u16, this is likely to be an instrumentation error")
232-
}
233-
234-
// At first we assign ConditionIds for each sub expression.
235-
// If the sub expression is composite, re-assign its ConditionId to its LHS and generate a new ConditionId for its RHS.
236-
//
237-
// Example: "x = (A && B) || (C && D) || (D && F)"
238-
//
239-
// Visit Depth1:
240-
// (A && B) || (C && D) || (D && F)
241-
// ^-------LHS--------^ ^-RHS--^
242-
// ID=1 ID=2
243-
//
244-
// Visit LHS-Depth2:
245-
// (A && B) || (C && D)
246-
// ^-LHS--^ ^-RHS--^
247-
// ID=1 ID=3
248-
//
249-
// Visit LHS-Depth3:
250-
// (A && B)
251-
// LHS RHS
252-
// ID=1 ID=4
253-
//
254-
// Visit RHS-Depth3:
255-
// (C && D)
256-
// LHS RHS
257-
// ID=3 ID=5
258-
//
259-
// Visit RHS-Depth2: (D && F)
260-
// LHS RHS
261-
// ID=2 ID=6
262-
//
263-
// Visit Depth1:
264-
// (A && B) || (C && D) || (D && F)
265-
// ID=1 ID=4 ID=3 ID=5 ID=2 ID=6
266-
//
267-
// A node ID of '0' always means MC/DC isn't being tracked.
268-
//
269-
// If a "next" node ID is '0', it means it's the end of the test vector.
270-
//
271-
// 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.
272-
// - 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".
273-
// - 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".
274-
fn record_conditions(&mut self, op: LogicalOp, span: Span) {
275-
let decision_depth = self.decision_depth();
276-
let decision_ctx =
277-
self.decision_ctx_stack.last_mut().expect("Unexpected empty decision_ctx_stack");
278-
let decision = match decision_ctx.processing_decision.as_mut() {
279-
Some(decision) => {
280-
decision.span = decision.span.to(span);
281-
decision
282-
}
283-
None => decision_ctx.processing_decision.insert(MCDCDecisionSpan {
284-
span,
285-
conditions_num: 0,
286-
end_markers: vec![],
287-
decision_depth,
288-
}),
289-
};
290-
291-
let parent_condition = decision_ctx.decision_stack.pop_back().unwrap_or_default();
292-
let lhs_id = if parent_condition.condition_id == ConditionId::NONE {
293-
decision.conditions_num += 1;
294-
ConditionId::from(decision.conditions_num)
295-
} else {
296-
parent_condition.condition_id
297-
};
298-
299-
decision.conditions_num += 1;
300-
let rhs_condition_id = ConditionId::from(decision.conditions_num);
301-
302-
let (lhs, rhs) = match op {
303-
LogicalOp::And => {
304-
let lhs = ConditionInfo {
305-
condition_id: lhs_id,
306-
true_next_id: rhs_condition_id,
307-
false_next_id: parent_condition.false_next_id,
308-
};
309-
let rhs = ConditionInfo {
310-
condition_id: rhs_condition_id,
311-
true_next_id: parent_condition.true_next_id,
312-
false_next_id: parent_condition.false_next_id,
313-
};
314-
(lhs, rhs)
315-
}
316-
LogicalOp::Or => {
317-
let lhs = ConditionInfo {
318-
condition_id: lhs_id,
319-
true_next_id: parent_condition.true_next_id,
320-
false_next_id: rhs_condition_id,
321-
};
322-
let rhs = ConditionInfo {
323-
condition_id: rhs_condition_id,
324-
true_next_id: parent_condition.true_next_id,
325-
false_next_id: parent_condition.false_next_id,
326-
};
327-
(lhs, rhs)
328-
}
329-
};
330-
// We visit expressions tree in pre-order, so place the left-hand side on the top.
331-
decision_ctx.decision_stack.push_back(rhs);
332-
decision_ctx.decision_stack.push_back(lhs);
333-
}
334-
335-
fn take_condition(
336-
&mut self,
337-
true_marker: BlockMarkerId,
338-
false_marker: BlockMarkerId,
339-
) -> (Option<ConditionInfo>, Option<MCDCDecisionSpan>) {
340-
let decision_ctx =
341-
self.decision_ctx_stack.last_mut().expect("Unexpected empty decision_ctx_stack");
342-
let Some(condition_info) = decision_ctx.decision_stack.pop_back() else {
343-
return (None, None);
344-
};
345-
let Some(decision) = decision_ctx.processing_decision.as_mut() else {
346-
bug!("Processing decision should have been created before any conditions are taken");
347-
};
348-
if condition_info.true_next_id == ConditionId::NONE {
349-
decision.end_markers.push(true_marker);
350-
}
351-
if condition_info.false_next_id == ConditionId::NONE {
352-
decision.end_markers.push(false_marker);
353-
}
354-
355-
if decision_ctx.decision_stack.is_empty() {
356-
(Some(condition_info), decision_ctx.processing_decision.take())
357-
} else {
358-
(Some(condition_info), None)
359-
}
360-
}
361-
}
362-
363158
impl Builder<'_, '_> {
364159
/// If branch coverage is enabled, inject marker statements into `then_block`
365160
/// and `else_block`, and record their IDs in the table of branch spans.
@@ -384,50 +179,20 @@ impl Builder<'_, '_> {
384179
let source_info = SourceInfo { span: self.thir[expr_id].span, scope: self.source_scope };
385180

386181
// Separate path for handling branches when MC/DC is enabled.
387-
if branch_info.mcdc_state.is_some() {
388-
let mut inject_block_marker =
389-
|block| branch_info.inject_block_marker(&mut self.cfg, source_info, block);
390-
let true_marker = inject_block_marker(then_block);
391-
let false_marker = inject_block_marker(else_block);
392-
let (decision_depth, condition_info) = branch_info
393-
.fetch_mcdc_condition_info(self.tcx, true_marker, false_marker)
394-
.map_or((0, None), |(decision_depth, condition_info)| {
395-
(decision_depth, Some(condition_info))
396-
});
397-
branch_info.mcdc_branch_spans.push(MCDCBranchSpan {
398-
span: source_info.span,
399-
condition_info,
400-
true_marker,
401-
false_marker,
402-
decision_depth,
403-
});
182+
if let Some(mcdc_builder) = branch_info.mcdc_builder.as_mut() {
183+
let inject_block_marker = |source_info, block| {
184+
branch_info.block_marker_gen.inject_block_marker(&mut self.cfg, source_info, block)
185+
};
186+
mcdc_builder.visit_evaluated_condition(
187+
self.tcx,
188+
source_info,
189+
then_block,
190+
else_block,
191+
inject_block_marker,
192+
);
404193
return;
405194
}
406195

407196
branch_info.add_two_way_branch(&mut self.cfg, source_info, then_block, else_block);
408197
}
409-
410-
pub(crate) fn visit_coverage_branch_operation(&mut self, logical_op: LogicalOp, span: Span) {
411-
if let Some(branch_info) = self.coverage_branch_info.as_mut()
412-
&& let Some(mcdc_state) = branch_info.mcdc_state.as_mut()
413-
{
414-
mcdc_state.record_conditions(logical_op, span);
415-
}
416-
}
417-
418-
pub(crate) fn mcdc_increment_depth_if_enabled(&mut self) {
419-
if let Some(branch_info) = self.coverage_branch_info.as_mut()
420-
&& let Some(mcdc_state) = branch_info.mcdc_state.as_mut()
421-
{
422-
mcdc_state.decision_ctx_stack.push(MCDCDecisionCtx::default());
423-
};
424-
}
425-
426-
pub(crate) fn mcdc_decrement_depth_if_enabled(&mut self) {
427-
if let Some(branch_info) = self.coverage_branch_info.as_mut()
428-
&& let Some(mcdc_state) = branch_info.mcdc_state.as_mut()
429-
{
430-
mcdc_state.decision_ctx_stack.pop().expect("Unexpected empty decision stack");
431-
};
432-
}
433198
}
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
use std::collections::VecDeque;
2+
3+
use rustc_middle::mir::coverage::{
4+
BlockMarkerId, ConditionId, ConditionInfo, MCDCBranchSpan, MCDCDecisionSpan,
5+
};
6+
use rustc_middle::mir::{BasicBlock, SourceInfo};
7+
use rustc_middle::thir::LogicalOp;
8+
use rustc_middle::ty::TyCtxt;
9+
10+
use rustc_span::Span;
11+
12+
use crate::build::Builder;
13+
use crate::errors::MCDCExceedsConditionNumLimit;
14+
15+
/// The MCDC bitmap scales exponentially (2^n) based on the number of conditions seen,
16+
/// So llvm sets a maximum value prevents the bitmap footprint from growing too large without the user's knowledge.
17+
/// This limit may be relaxed if the [upstream change](https://github.com/llvm/llvm-project/pull/82448) is merged.
18+
const MAX_CONDITIONS_NUM_IN_DECISION: usize = 6;
19+
20+
#[derive(Default)]
21+
struct MCDCDecisionCtx {
22+
/// To construct condition evaluation tree.
23+
decision_stack: VecDeque<ConditionInfo>,
24+
processing_decision: Option<MCDCDecisionSpan>,
25+
}
26+
27+
pub(super) struct MCDCState {
28+
decision_ctx_stack: Vec<MCDCDecisionCtx>,
29+
}
30+
31+
pub(super) struct MCDCInfoBuilder {
32+
branch_spans: Vec<MCDCBranchSpan>,
33+
decision_spans: Vec<MCDCDecisionSpan>,
34+
state: MCDCState,
35+
}
36+
37+
impl MCDCInfoBuilder {
38+
pub fn new() -> Self {
39+
Self { branch_spans: vec![], decision_spans: vec![], state: MCDCState::init() }
40+
}
41+
42+
pub fn visit_evaluated_condition(
43+
&mut self,
44+
tcx: TyCtxt<'_>,
45+
source_info: SourceInfo,
46+
true_block: BasicBlock,
47+
false_block: BasicBlock,
48+
mut inject_block_marker: impl FnMut(SourceInfo, BasicBlock) -> BlockMarkerId,
49+
) {
50+
let true_marker = inject_block_marker(source_info, true_block);
51+
let false_marker = inject_block_marker(source_info, false_block);
52+
let decision_depth = self.state.decision_depth();
53+
let (mut condition_info, decision_result) =
54+
self.state.take_condition(true_marker, false_marker);
55+
// take_condition() returns Some for decision_result when the decision stack
56+
// is empty, i.e. when all the conditions of the decision were instrumented,
57+
// and the decision is "complete".
58+
if let Some(decision) = decision_result {
59+
match decision.conditions_num {
60+
0 => {
61+
unreachable!("Decision with no condition is not expected");
62+
}
63+
1..=MAX_CONDITIONS_NUM_IN_DECISION => {
64+
self.decision_spans.push(decision);
65+
}
66+
_ => {
67+
// Do not generate mcdc mappings and statements for decisions with too many conditions.
68+
let rebase_idx = self.branch_spans.len() - decision.conditions_num + 1;
69+
for branch in &mut self.branch_spans[rebase_idx..] {
70+
branch.condition_info = None;
71+
}
72+
73+
// ConditionInfo of this branch shall also be reset.
74+
condition_info = None;
75+
76+
tcx.dcx().emit_warn(MCDCExceedsConditionNumLimit {
77+
span: decision.span,
78+
conditions_num: decision.conditions_num,
79+
max_conditions_num: MAX_CONDITIONS_NUM_IN_DECISION,
80+
});
81+
}
82+
}
83+
}
84+
self.branch_spans.push(MCDCBranchSpan {
85+
span: source_info.span,
86+
condition_info,
87+
true_marker,
88+
false_marker,
89+
decision_depth,
90+
});
91+
}
92+
93+
pub fn into_done(self) -> (Vec<MCDCDecisionSpan>, Vec<MCDCBranchSpan>) {
94+
(self.decision_spans, self.branch_spans)
95+
}
96+
}
97+
98+
impl MCDCState {
99+
fn init() -> Self {
100+
Self { decision_ctx_stack: vec![MCDCDecisionCtx::default()] }
101+
}
102+
103+
#[inline]
104+
fn decision_depth(&self) -> u16 {
105+
match u16::try_from(self.decision_ctx_stack.len())
106+
.expect(
107+
"decision depth did not fit in u16, this is likely to be an instrumentation error",
108+
)
109+
.checked_sub(1)
110+
{
111+
Some(d) => d,
112+
None => bug!("Unexpected empty decision stack"),
113+
}
114+
}
115+
116+
// At first we assign ConditionIds for each sub expression.
117+
// If the sub expression is composite, re-assign its ConditionId to its LHS and generate a new ConditionId for its RHS.
118+
//
119+
// Example: "x = (A && B) || (C && D) || (D && F)"
120+
//
121+
// Visit Depth1:
122+
// (A && B) || (C && D) || (D && F)
123+
// ^-------LHS--------^ ^-RHS--^
124+
// ID=1 ID=2
125+
//
126+
// Visit LHS-Depth2:
127+
// (A && B) || (C && D)
128+
// ^-LHS--^ ^-RHS--^
129+
// ID=1 ID=3
130+
//
131+
// Visit LHS-Depth3:
132+
// (A && B)
133+
// LHS RHS
134+
// ID=1 ID=4
135+
//
136+
// Visit RHS-Depth3:
137+
// (C && D)
138+
// LHS RHS
139+
// ID=3 ID=5
140+
//
141+
// Visit RHS-Depth2: (D && F)
142+
// LHS RHS
143+
// ID=2 ID=6
144+
//
145+
// Visit Depth1:
146+
// (A && B) || (C && D) || (D && F)
147+
// ID=1 ID=4 ID=3 ID=5 ID=2 ID=6
148+
//
149+
// A node ID of '0' always means MC/DC isn't being tracked.
150+
//
151+
// If a "next" node ID is '0', it means it's the end of the test vector.
152+
//
153+
// 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.
154+
// - 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".
155+
// - 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".
156+
fn record_conditions(&mut self, op: LogicalOp, span: Span) {
157+
let decision_depth = self.decision_depth();
158+
let decision_ctx =
159+
self.decision_ctx_stack.last_mut().expect("Unexpected empty decision_ctx_stack");
160+
let decision = match decision_ctx.processing_decision.as_mut() {
161+
Some(decision) => {
162+
decision.span = decision.span.to(span);
163+
decision
164+
}
165+
None => decision_ctx.processing_decision.insert(MCDCDecisionSpan {
166+
span,
167+
conditions_num: 0,
168+
end_markers: vec![],
169+
decision_depth,
170+
}),
171+
};
172+
173+
let parent_condition = decision_ctx.decision_stack.pop_back().unwrap_or_default();
174+
let lhs_id = if parent_condition.condition_id == ConditionId::NONE {
175+
decision.conditions_num += 1;
176+
ConditionId::from(decision.conditions_num)
177+
} else {
178+
parent_condition.condition_id
179+
};
180+
181+
decision.conditions_num += 1;
182+
let rhs_condition_id = ConditionId::from(decision.conditions_num);
183+
184+
let (lhs, rhs) = match op {
185+
LogicalOp::And => {
186+
let lhs = ConditionInfo {
187+
condition_id: lhs_id,
188+
true_next_id: rhs_condition_id,
189+
false_next_id: parent_condition.false_next_id,
190+
};
191+
let rhs = ConditionInfo {
192+
condition_id: rhs_condition_id,
193+
true_next_id: parent_condition.true_next_id,
194+
false_next_id: parent_condition.false_next_id,
195+
};
196+
(lhs, rhs)
197+
}
198+
LogicalOp::Or => {
199+
let lhs = ConditionInfo {
200+
condition_id: lhs_id,
201+
true_next_id: parent_condition.true_next_id,
202+
false_next_id: rhs_condition_id,
203+
};
204+
let rhs = ConditionInfo {
205+
condition_id: rhs_condition_id,
206+
true_next_id: parent_condition.true_next_id,
207+
false_next_id: parent_condition.false_next_id,
208+
};
209+
(lhs, rhs)
210+
}
211+
};
212+
// We visit expressions tree in pre-order, so place the left-hand side on the top.
213+
decision_ctx.decision_stack.push_back(rhs);
214+
decision_ctx.decision_stack.push_back(lhs);
215+
}
216+
217+
fn take_condition(
218+
&mut self,
219+
true_marker: BlockMarkerId,
220+
false_marker: BlockMarkerId,
221+
) -> (Option<ConditionInfo>, Option<MCDCDecisionSpan>) {
222+
let Some(decision_ctx) = self.decision_ctx_stack.last_mut() else {
223+
bug!("Unexpected empty decision_ctx_stack")
224+
};
225+
let Some(condition_info) = decision_ctx.decision_stack.pop_back() else {
226+
return (None, None);
227+
};
228+
let Some(decision) = decision_ctx.processing_decision.as_mut() else {
229+
bug!("Processing decision should have been created before any conditions are taken");
230+
};
231+
if condition_info.true_next_id == ConditionId::NONE {
232+
decision.end_markers.push(true_marker);
233+
}
234+
if condition_info.false_next_id == ConditionId::NONE {
235+
decision.end_markers.push(false_marker);
236+
}
237+
238+
if decision_ctx.decision_stack.is_empty() {
239+
(Some(condition_info), decision_ctx.processing_decision.take())
240+
} else {
241+
(Some(condition_info), None)
242+
}
243+
}
244+
}
245+
246+
impl Builder<'_, '_> {
247+
pub(crate) fn visit_coverage_branch_operation(&mut self, logical_op: LogicalOp, span: Span) {
248+
if let Some(mcdc_builder) = self
249+
.coverage_branch_info
250+
.as_mut()
251+
.and_then(|branch_info| branch_info.mcdc_builder.as_mut())
252+
{
253+
mcdc_builder.state.record_conditions(logical_op, span);
254+
}
255+
}
256+
257+
pub(crate) fn mcdc_increment_depth_if_enabled(&mut self) {
258+
if let Some(mcdc_builder) = self
259+
.coverage_branch_info
260+
.as_mut()
261+
.and_then(|branch_info| branch_info.mcdc_builder.as_mut())
262+
{
263+
mcdc_builder.state.decision_ctx_stack.push(MCDCDecisionCtx::default());
264+
};
265+
}
266+
267+
pub(crate) fn mcdc_decrement_depth_if_enabled(&mut self) {
268+
if let Some(mcdc_builder) = self
269+
.coverage_branch_info
270+
.as_mut()
271+
.and_then(|branch_info| branch_info.mcdc_builder.as_mut())
272+
{
273+
if mcdc_builder.state.decision_ctx_stack.pop().is_none() {
274+
bug!("Unexpected empty decision stack");
275+
}
276+
};
277+
}
278+
}

0 commit comments

Comments
 (0)
Please sign in to comment.