rustc_mir_build/builder/coverageinfo/
mcdc.rs
1use std::collections::VecDeque;
2
3use rustc_middle::bug;
4use rustc_middle::mir::coverage::{
5 BlockMarkerId, ConditionId, ConditionInfo, MCDCBranchSpan, MCDCDecisionSpan,
6};
7use rustc_middle::mir::{BasicBlock, SourceInfo};
8use rustc_middle::thir::LogicalOp;
9use rustc_middle::ty::TyCtxt;
10use rustc_span::Span;
11
12use crate::builder::Builder;
13use crate::errors::MCDCExceedsConditionLimit;
14
15const MAX_CONDITIONS_IN_DECISION: usize = i16::MAX as usize;
18
19#[derive(Default)]
20struct MCDCDecisionCtx {
21 decision_stack: VecDeque<ConditionInfo>,
23 processing_decision: Option<MCDCDecisionSpan>,
24 conditions: Vec<MCDCBranchSpan>,
25}
26
27struct MCDCState {
28 decision_ctx_stack: Vec<MCDCDecisionCtx>,
29}
30
31impl MCDCState {
32 fn new() -> Self {
33 Self { decision_ctx_stack: vec![MCDCDecisionCtx::default()] }
34 }
35
36 #[inline]
39 fn decision_depth(&self) -> u16 {
40 match u16::try_from(self.decision_ctx_stack.len())
41 .expect(
42 "decision depth did not fit in u16, this is likely to be an instrumentation error",
43 )
44 .checked_sub(1)
45 {
46 Some(d) => d,
47 None => bug!("Unexpected empty decision stack"),
48 }
49 }
50
51 fn record_conditions(&mut self, op: LogicalOp, span: Span) {
92 let decision_depth = self.decision_depth();
93 let Some(decision_ctx) = self.decision_ctx_stack.last_mut() else {
94 bug!("Unexpected empty decision_ctx_stack")
95 };
96 let decision = match decision_ctx.processing_decision.as_mut() {
97 Some(decision) => {
98 decision.span = decision.span.to(span);
99 decision
100 }
101 None => decision_ctx.processing_decision.insert(MCDCDecisionSpan {
102 span,
103 num_conditions: 0,
104 end_markers: vec![],
105 decision_depth,
106 }),
107 };
108
109 let parent_condition = decision_ctx.decision_stack.pop_back().unwrap_or_else(|| {
110 assert_eq!(
111 decision.num_conditions, 0,
112 "decision stack must be empty only for empty decision"
113 );
114 decision.num_conditions += 1;
115 ConditionInfo {
116 condition_id: ConditionId::START,
117 true_next_id: None,
118 false_next_id: None,
119 }
120 });
121 let lhs_id = parent_condition.condition_id;
122
123 let rhs_condition_id = ConditionId::from(decision.num_conditions);
124 decision.num_conditions += 1;
125 let (lhs, rhs) = match op {
126 LogicalOp::And => {
127 let lhs = ConditionInfo {
128 condition_id: lhs_id,
129 true_next_id: Some(rhs_condition_id),
130 false_next_id: parent_condition.false_next_id,
131 };
132 let rhs = ConditionInfo {
133 condition_id: rhs_condition_id,
134 true_next_id: parent_condition.true_next_id,
135 false_next_id: parent_condition.false_next_id,
136 };
137 (lhs, rhs)
138 }
139 LogicalOp::Or => {
140 let lhs = ConditionInfo {
141 condition_id: lhs_id,
142 true_next_id: parent_condition.true_next_id,
143 false_next_id: Some(rhs_condition_id),
144 };
145 let rhs = ConditionInfo {
146 condition_id: rhs_condition_id,
147 true_next_id: parent_condition.true_next_id,
148 false_next_id: parent_condition.false_next_id,
149 };
150 (lhs, rhs)
151 }
152 };
153 decision_ctx.decision_stack.push_back(rhs);
155 decision_ctx.decision_stack.push_back(lhs);
156 }
157
158 fn try_finish_decision(
159 &mut self,
160 span: Span,
161 true_marker: BlockMarkerId,
162 false_marker: BlockMarkerId,
163 degraded_branches: &mut Vec<MCDCBranchSpan>,
164 ) -> Option<(MCDCDecisionSpan, Vec<MCDCBranchSpan>)> {
165 let Some(decision_ctx) = self.decision_ctx_stack.last_mut() else {
166 bug!("Unexpected empty decision_ctx_stack")
167 };
168 let Some(condition_info) = decision_ctx.decision_stack.pop_back() else {
169 let branch = MCDCBranchSpan {
170 span,
171 condition_info: ConditionInfo {
172 condition_id: ConditionId::START,
173 true_next_id: None,
174 false_next_id: None,
175 },
176 true_marker,
177 false_marker,
178 };
179 degraded_branches.push(branch);
180 return None;
181 };
182 let Some(decision) = decision_ctx.processing_decision.as_mut() else {
183 bug!("Processing decision should have been created before any conditions are taken");
184 };
185 if condition_info.true_next_id.is_none() {
186 decision.end_markers.push(true_marker);
187 }
188 if condition_info.false_next_id.is_none() {
189 decision.end_markers.push(false_marker);
190 }
191 decision_ctx.conditions.push(MCDCBranchSpan {
192 span,
193 condition_info,
194 true_marker,
195 false_marker,
196 });
197
198 if decision_ctx.decision_stack.is_empty() {
199 let conditions = std::mem::take(&mut decision_ctx.conditions);
200 decision_ctx.processing_decision.take().map(|decision| (decision, conditions))
201 } else {
202 None
203 }
204 }
205}
206
207pub(crate) struct MCDCInfoBuilder {
208 degraded_spans: Vec<MCDCBranchSpan>,
209 mcdc_spans: Vec<(MCDCDecisionSpan, Vec<MCDCBranchSpan>)>,
210 state: MCDCState,
211}
212
213impl MCDCInfoBuilder {
214 pub(crate) fn new() -> Self {
215 Self { degraded_spans: vec![], mcdc_spans: vec![], state: MCDCState::new() }
216 }
217
218 pub(crate) fn visit_evaluated_condition(
219 &mut self,
220 tcx: TyCtxt<'_>,
221 source_info: SourceInfo,
222 true_block: BasicBlock,
223 false_block: BasicBlock,
224 mut inject_block_marker: impl FnMut(SourceInfo, BasicBlock) -> BlockMarkerId,
225 ) {
226 let true_marker = inject_block_marker(source_info, true_block);
227 let false_marker = inject_block_marker(source_info, false_block);
228
229 if let Some((decision, conditions)) = self.state.try_finish_decision(
233 source_info.span,
234 true_marker,
235 false_marker,
236 &mut self.degraded_spans,
237 ) {
238 let num_conditions = conditions.len();
239 assert_eq!(
240 num_conditions, decision.num_conditions,
241 "final number of conditions is not correct"
242 );
243 match num_conditions {
244 0 => {
245 unreachable!("Decision with no condition is not expected");
246 }
247 1..=MAX_CONDITIONS_IN_DECISION => {
248 self.mcdc_spans.push((decision, conditions));
249 }
250 _ => {
251 self.degraded_spans.extend(conditions);
252
253 tcx.dcx().emit_warn(MCDCExceedsConditionLimit {
254 span: decision.span,
255 num_conditions,
256 max_conditions: MAX_CONDITIONS_IN_DECISION,
257 });
258 }
259 }
260 }
261 }
262
263 pub(crate) fn into_done(
264 self,
265 ) -> (Vec<(MCDCDecisionSpan, Vec<MCDCBranchSpan>)>, Vec<MCDCBranchSpan>) {
266 (self.mcdc_spans, self.degraded_spans)
267 }
268}
269
270impl Builder<'_, '_> {
271 pub(crate) fn visit_coverage_branch_operation(&mut self, logical_op: LogicalOp, span: Span) {
272 if let Some(coverage_info) = self.coverage_info.as_mut()
273 && let Some(mcdc_info) = coverage_info.mcdc_info.as_mut()
274 {
275 mcdc_info.state.record_conditions(logical_op, span);
276 }
277 }
278
279 pub(crate) fn mcdc_increment_depth_if_enabled(&mut self) {
280 if let Some(coverage_info) = self.coverage_info.as_mut()
281 && let Some(mcdc_info) = coverage_info.mcdc_info.as_mut()
282 {
283 mcdc_info.state.decision_ctx_stack.push(MCDCDecisionCtx::default());
284 };
285 }
286
287 pub(crate) fn mcdc_decrement_depth_if_enabled(&mut self) {
288 if let Some(coverage_info) = self.coverage_info.as_mut()
289 && let Some(mcdc_info) = coverage_info.mcdc_info.as_mut()
290 && mcdc_info.state.decision_ctx_stack.pop().is_none()
291 {
292 bug!("Unexpected empty decision stack");
293 };
294 }
295}