Skip to content

Commit b2754ec

Browse files
author
zhuyunxing
committed
coverage. MC/DC analysis ignores decisions with only one condition and add test for nested if
1 parent e1b5fb4 commit b2754ec

File tree

10 files changed

+290
-133
lines changed

10 files changed

+290
-133
lines changed

compiler/rustc_middle/src/mir/coverage.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,6 @@ pub struct DecisionInfo {
319319
#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
320320
pub struct DecisionSpan {
321321
pub span: Span,
322-
pub conditions_num: u16,
323-
pub join_marker: BlockMarkerId,
322+
pub conditions_num: usize,
323+
pub end_marker: Vec<BlockMarkerId>,
324324
}

compiler/rustc_mir_build/src/build/coverageinfo.rs

Lines changed: 94 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use rustc_middle::mir::{self, BasicBlock, UnOp};
1010
use rustc_middle::thir::{ExprId, ExprKind, LogicalOp, Thir};
1111
use rustc_middle::ty::TyCtxt;
1212
use rustc_span::def_id::LocalDefId;
13+
use rustc_span::Span;
1314

1415
use crate::build::Builder;
1516
use crate::errors::MCDCExceedsConditionNumLimit;
@@ -91,8 +92,50 @@ impl BranchInfoBuilder {
9192
}
9293
}
9394

94-
fn get_mcdc_state_mut(&mut self) -> Option<&mut MCDCState> {
95-
self.mcdc_state.as_mut()
95+
fn record_conditions_operation(&mut self, logical_op: LogicalOp, span: Span) {
96+
if let Some(mcdc_state) = self.mcdc_state.as_mut() {
97+
mcdc_state.record_conditions(logical_op, span);
98+
}
99+
}
100+
101+
fn fetch_condition_info(
102+
&mut self,
103+
tcx: TyCtxt<'_>,
104+
true_marker: BlockMarkerId,
105+
false_marker: BlockMarkerId,
106+
) -> ConditionInfo {
107+
let Some(mcdc_state) = self.mcdc_state.as_mut() else {
108+
return ConditionInfo::default();
109+
};
110+
let (mut condition_info, decision_result) =
111+
mcdc_state.take_condition(true_marker, false_marker);
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.decision_spans.push(decision);
119+
}
120+
_ => {
121+
// Do not generate mcdc mappings and statements for decisions with too many conditions.
122+
for branch in
123+
self.branch_spans.iter_mut().rev().take(decision.conditions_num - 1)
124+
{
125+
branch.condition_info = ConditionInfo::default();
126+
}
127+
// ConditionInfo of this branch shall also be reset.
128+
condition_info = ConditionInfo::default();
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
96139
}
97140

98141
fn next_block_marker_id(&mut self) -> BlockMarkerId {
@@ -125,14 +168,14 @@ const MAX_CONDITIONS_NUM_IN_DECISION: usize = 6;
125168
struct MCDCState {
126169
/// To construct condition evaluation tree.
127170
decision_stack: VecDeque<ConditionInfo>,
128-
next_condition_id: usize,
171+
processing_decision: Option<DecisionSpan>,
129172
}
130173

131174
impl MCDCState {
132175
fn new_if_enabled(tcx: TyCtxt<'_>) -> Option<Self> {
133176
tcx.sess
134177
.instrument_coverage_mcdc()
135-
.then(|| Self { decision_stack: VecDeque::new(), next_condition_id: 0 })
178+
.then(|| Self { decision_stack: VecDeque::new(), processing_decision: None })
136179
}
137180

138181
/// At first we assign ConditionIds for each sub expression.
@@ -175,17 +218,29 @@ impl MCDCState {
175218
/// 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.
176219
/// - 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".
177220
/// - 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".
178-
fn record_conditions(&mut self, op: LogicalOp) {
221+
fn record_conditions(&mut self, op: LogicalOp, span: Span) {
222+
let decision = match self.processing_decision.as_mut() {
223+
Some(decision) => {
224+
decision.span = decision.span.to(span);
225+
decision
226+
}
227+
None => self.processing_decision.insert(DecisionSpan {
228+
span,
229+
conditions_num: 0,
230+
end_marker: vec![],
231+
}),
232+
};
233+
179234
let parent_condition = self.decision_stack.pop_back().unwrap_or_default();
180235
let lhs_id = if parent_condition.condition_id == ConditionId::NONE {
181-
self.next_condition_id += 1;
182-
ConditionId::from(self.next_condition_id)
236+
decision.conditions_num += 1;
237+
ConditionId::from(decision.conditions_num)
183238
} else {
184239
parent_condition.condition_id
185240
};
186241

187-
self.next_condition_id += 1;
188-
let rhs_condition_id = ConditionId::from(self.next_condition_id);
242+
decision.conditions_num += 1;
243+
let rhs_condition_id = ConditionId::from(decision.conditions_num);
189244

190245
let (lhs, rhs) = match op {
191246
LogicalOp::And => {
@@ -219,6 +274,31 @@ impl MCDCState {
219274
self.decision_stack.push_back(rhs);
220275
self.decision_stack.push_back(lhs);
221276
}
277+
278+
fn take_condition(
279+
&mut self,
280+
true_marker: BlockMarkerId,
281+
false_marker: BlockMarkerId,
282+
) -> (ConditionInfo, Option<DecisionSpan>) {
283+
let Some(condition_info) = self.decision_stack.pop_back() else {
284+
return (ConditionInfo::default(), None);
285+
};
286+
let Some(decision) = self.processing_decision.as_mut() else {
287+
bug!("Processing decision should have been created before any conditions are taken");
288+
};
289+
if condition_info.true_next_id == ConditionId::NONE {
290+
decision.end_marker.push(true_marker);
291+
}
292+
if condition_info.false_next_id == ConditionId::NONE {
293+
decision.end_marker.push(false_marker);
294+
}
295+
296+
if self.decision_stack.is_empty() {
297+
(condition_info, self.processing_decision.take())
298+
} else {
299+
(condition_info, None)
300+
}
301+
}
222302
}
223303

224304
impl Builder<'_, '_> {
@@ -246,12 +326,6 @@ impl Builder<'_, '_> {
246326
// Now that we have `source_info`, we can upgrade to a &mut reference.
247327
let branch_info = self.coverage_branch_info.as_mut().expect("upgrading & to &mut");
248328

249-
let condition_info = branch_info
250-
.mcdc_state
251-
.as_mut()
252-
.and_then(|state| state.decision_stack.pop_back())
253-
.unwrap_or_default();
254-
255329
let mut inject_branch_marker = |block: BasicBlock| {
256330
let id = branch_info.next_block_marker_id();
257331

@@ -267,6 +341,8 @@ impl Builder<'_, '_> {
267341
let true_marker = inject_branch_marker(then_block);
268342
let false_marker = inject_branch_marker(else_block);
269343

344+
let condition_info = branch_info.fetch_condition_info(self.tcx, true_marker, false_marker);
345+
270346
branch_info.branch_spans.push(BranchSpan {
271347
span: source_info.span,
272348
condition_info,
@@ -275,64 +351,9 @@ impl Builder<'_, '_> {
275351
});
276352
}
277353

278-
pub(crate) fn visit_coverage_decision(&mut self, expr_id: ExprId, join_block: BasicBlock) {
279-
if let Some((mcdc_state, branches)) = self
280-
.coverage_branch_info
281-
.as_mut()
282-
.and_then(|builder| builder.mcdc_state.as_mut().zip(Some(&mut builder.branch_spans)))
283-
{
284-
assert!(
285-
mcdc_state.decision_stack.is_empty(),
286-
"All condition should have been checked before the decision ends"
287-
);
288-
289-
let conditions_num = mcdc_state.next_condition_id;
290-
291-
mcdc_state.next_condition_id = 0;
292-
293-
match conditions_num {
294-
0 => {
295-
unreachable!("Decision with no conditions is not allowed");
296-
}
297-
1..=MAX_CONDITIONS_NUM_IN_DECISION => {
298-
let span = self.thir[expr_id].span;
299-
let branch_info =
300-
self.coverage_branch_info.as_mut().expect("updating to existed");
301-
let id = branch_info.next_block_marker_id();
302-
303-
branch_info.decision_spans.push(DecisionSpan {
304-
span,
305-
conditions_num: conditions_num as u16,
306-
join_marker: id,
307-
});
308-
309-
let statement = mir::Statement {
310-
source_info: self.source_info(span),
311-
kind: mir::StatementKind::Coverage(CoverageKind::BlockMarker { id }),
312-
};
313-
self.cfg.push(join_block, statement);
314-
}
315-
_ => {
316-
// Do not generate mcdc mappings and statements for decisions with too many conditions.
317-
for branch in branches.iter_mut().rev().take(conditions_num) {
318-
branch.condition_info = Default::default();
319-
}
320-
321-
self.tcx.dcx().emit_warn(MCDCExceedsConditionNumLimit {
322-
span: self.thir[expr_id].span,
323-
conditions_num,
324-
max_conditions_num: MAX_CONDITIONS_NUM_IN_DECISION,
325-
});
326-
}
327-
}
328-
}
329-
}
330-
331-
pub(crate) fn visit_coverage_branch_operation(&mut self, logical_op: LogicalOp) {
332-
if let Some(mcdc_state) =
333-
self.coverage_branch_info.as_mut().and_then(BranchInfoBuilder::get_mcdc_state_mut)
334-
{
335-
mcdc_state.record_conditions(logical_op);
354+
pub(crate) fn visit_coverage_branch_operation(&mut self, logical_op: LogicalOp, span: Span) {
355+
if let Some(branch_info) = self.coverage_branch_info.as_mut() {
356+
branch_info.record_conditions_operation(logical_op, span);
336357
}
337358
}
338359
}

compiler/rustc_mir_build/src/build/expr/into.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
113113
// The `then` and `else` arms have been lowered into their respective
114114
// blocks, so make both of them meet up in a new block.
115115
let join_block = this.cfg.start_new_block();
116-
this.visit_coverage_decision(cond, join_block);
117116
this.cfg.goto(then_blk, source_info, join_block);
118117
this.cfg.goto(else_blk, source_info, join_block);
119118
join_block.unit()

compiler/rustc_mir_build/src/build/matches/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
7777

7878
match expr.kind {
7979
ExprKind::LogicalOp { op: LogicalOp::And, lhs, rhs } => {
80-
this.visit_coverage_branch_operation(LogicalOp::And);
80+
this.visit_coverage_branch_operation(LogicalOp::And, expr_span);
8181
let lhs_then_block = unpack!(this.then_else_break_inner(block, lhs, args));
8282
let rhs_then_block = unpack!(this.then_else_break_inner(lhs_then_block, rhs, args));
8383
rhs_then_block.unit()
8484
}
8585
ExprKind::LogicalOp { op: LogicalOp::Or, lhs, rhs } => {
86-
this.visit_coverage_branch_operation(LogicalOp::Or);
86+
this.visit_coverage_branch_operation(LogicalOp::Or, expr_span);
8787
let local_scope = this.local_scope();
8888
let (lhs_success_block, failure_block) =
8989
this.in_if_then_scope(local_scope, expr_span, |this| {

compiler/rustc_mir_transform/src/coverage/mod.rs

Lines changed: 49 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -141,28 +141,31 @@ fn create_mappings<'tcx>(
141141
let mut mappings = Vec::new();
142142

143143
mappings.extend(coverage_spans.all_bcb_mappings().filter_map(
144-
|&BcbMapping { kind: bcb_mapping_kind, span }| {
144+
|BcbMapping { kind: bcb_mapping_kind, span }| {
145145
let kind = match bcb_mapping_kind {
146-
BcbMappingKind::Code(bcb) => MappingKind::Code(term_for_bcb(bcb)),
147-
BcbMappingKind::Branch { true_bcb, false_bcb, mcdc_params } => {
148-
if mcdc_params.condition_id == ConditionId::NONE {
146+
BcbMappingKind::Code(bcb) => MappingKind::Code(term_for_bcb(*bcb)),
147+
BcbMappingKind::Branch { true_bcb, false_bcb, condition_info } => {
148+
if condition_info.condition_id == ConditionId::NONE {
149149
MappingKind::Branch {
150-
true_term: term_for_bcb(true_bcb),
151-
false_term: term_for_bcb(false_bcb),
150+
true_term: term_for_bcb(*true_bcb),
151+
false_term: term_for_bcb(*false_bcb),
152152
}
153153
} else {
154154
MappingKind::MCDCBranch {
155-
true_term: term_for_bcb(true_bcb),
156-
false_term: term_for_bcb(false_bcb),
157-
mcdc_params,
155+
true_term: term_for_bcb(*true_bcb),
156+
false_term: term_for_bcb(*false_bcb),
157+
mcdc_params: *condition_info,
158158
}
159159
}
160160
}
161161
BcbMappingKind::Decision { bitmap_idx, conditions_num, .. } => {
162-
MappingKind::MCDCDecision(DecisionInfo { bitmap_idx, conditions_num })
162+
MappingKind::MCDCDecision(DecisionInfo {
163+
bitmap_idx: *bitmap_idx,
164+
conditions_num: *conditions_num,
165+
})
163166
}
164167
};
165-
let code_region = make_code_region(source_map, file_name, span, body_span)?;
168+
let code_region = make_code_region(source_map, file_name, *span, body_span)?;
166169
Some(Mapping { kind, code_region })
167170
},
168171
));
@@ -232,36 +235,44 @@ fn inject_mcdc_statements<'tcx>(
232235
if coverage_spans.test_vector_bitmap_bytes() == 0 {
233236
return;
234237
}
235-
for mapping in coverage_spans.all_bcb_mappings() {
236-
match mapping.kind {
237-
BcbMappingKind::Branch { true_bcb, false_bcb, mcdc_params } => {
238-
if mcdc_params.condition_id == ConditionId::NONE {
239-
continue;
240-
}
241-
let true_bb = basic_coverage_blocks[true_bcb].leader_bb();
242-
inject_statement(
243-
mir_body,
244-
CoverageKind::CondBitmapUpdate { id: mcdc_params.condition_id, value: true },
245-
true_bb,
246-
);
247-
let false_bb = basic_coverage_blocks[false_bcb].leader_bb();
248-
inject_statement(
249-
mir_body,
250-
CoverageKind::CondBitmapUpdate { id: mcdc_params.condition_id, value: false },
251-
false_bb,
252-
);
253-
}
254-
BcbMappingKind::Decision { join_bcb, bitmap_idx, .. } => {
255-
let join_bb = basic_coverage_blocks[join_bcb].leader_bb();
256-
inject_statement(
257-
mir_body,
258-
CoverageKind::TestVectorBitmapUpdate { bitmap_idx: bitmap_idx },
259-
join_bb,
260-
);
238+
239+
// Inject test vector update first because inject statement always inject new statement at head.
240+
for (end_bcbs, bitmap_idx) in
241+
coverage_spans.all_bcb_mappings().filter_map(|mapping| match &mapping.kind {
242+
BcbMappingKind::Decision { end_bcbs: end_bcb, bitmap_idx, .. } => {
243+
Some((end_bcb, *bitmap_idx))
261244
}
262-
_ => {}
245+
_ => None,
246+
})
247+
{
248+
for end in end_bcbs {
249+
let end_bb = basic_coverage_blocks[*end].leader_bb();
250+
inject_statement(mir_body, CoverageKind::TestVectorBitmapUpdate { bitmap_idx }, end_bb);
263251
}
264252
}
253+
254+
for (true_bcb, false_bcb, condition_id) in
255+
coverage_spans.all_bcb_mappings().filter_map(|mapping| match mapping.kind {
256+
BcbMappingKind::Branch { true_bcb, false_bcb, condition_info } => (condition_info
257+
.condition_id
258+
!= ConditionId::NONE)
259+
.then_some((true_bcb, false_bcb, condition_info.condition_id)),
260+
_ => None,
261+
})
262+
{
263+
let true_bb = basic_coverage_blocks[true_bcb].leader_bb();
264+
inject_statement(
265+
mir_body,
266+
CoverageKind::CondBitmapUpdate { id: condition_id, value: true },
267+
true_bb,
268+
);
269+
let false_bb = basic_coverage_blocks[false_bcb].leader_bb();
270+
inject_statement(
271+
mir_body,
272+
CoverageKind::CondBitmapUpdate { id: condition_id, value: false },
273+
false_bb,
274+
);
275+
}
265276
}
266277

267278
/// Given two basic blocks that have a control-flow edge between them, creates

0 commit comments

Comments
 (0)