rustc_mir_build/builder/
coverageinfo.rs

1use std::assert_matches::assert_matches;
2use std::collections::hash_map::Entry;
3
4use rustc_data_structures::fx::FxHashMap;
5use rustc_middle::mir::coverage::{BlockMarkerId, BranchSpan, CoverageInfoHi, CoverageKind};
6use rustc_middle::mir::{self, BasicBlock, SourceInfo, UnOp};
7use rustc_middle::thir::{ExprId, ExprKind, Pat, Thir};
8use rustc_middle::ty::TyCtxt;
9use rustc_span::def_id::LocalDefId;
10
11use crate::builder::coverageinfo::mcdc::MCDCInfoBuilder;
12use crate::builder::{Builder, CFG};
13
14mod 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.
18pub(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)]
31struct BranchInfo {
32    branch_spans: Vec<BranchSpan>,
33}
34
35#[derive(Clone, Copy)]
36struct 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)]
46struct BlockMarkerGen {
47    num_block_markers: usize,
48}
49
50impl 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 = 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
74impl 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
192impl<'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}