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::{Builder, CFG};
12
13/// Collects coverage-related information during MIR building, to eventually be
14/// turned into a function's [`CoverageInfoHi`] when MIR building is complete.
15pub(crate) struct CoverageInfoBuilder {
16    /// Maps condition expressions to their enclosing `!`, for better instrumentation.
17    nots: FxHashMap<ExprId, NotInfo>,
18
19    markers: BlockMarkerGen,
20
21    /// Present if branch coverage is enabled.
22    branch_info: Option<BranchInfo>,
23}
24
25#[derive(Default)]
26struct BranchInfo {
27    branch_spans: Vec<BranchSpan>,
28}
29
30#[derive(Clone, Copy)]
31struct NotInfo {
32    /// When visiting the associated expression as a branch condition, treat this
33    /// enclosing `!` as the branch condition instead.
34    enclosing_not: ExprId,
35    /// True if the associated expression is nested within an odd number of `!`
36    /// expressions relative to `enclosing_not` (inclusive of `enclosing_not`).
37    is_flipped: bool,
38}
39
40#[derive(Default)]
41struct BlockMarkerGen {
42    num_block_markers: usize,
43}
44
45impl BlockMarkerGen {
46    fn next_block_marker_id(&mut self) -> BlockMarkerId {
47        let id = BlockMarkerId::from_usize(self.num_block_markers);
48        self.num_block_markers += 1;
49        id
50    }
51
52    fn inject_block_marker(
53        &mut self,
54        cfg: &mut CFG<'_>,
55        source_info: SourceInfo,
56        block: BasicBlock,
57    ) -> BlockMarkerId {
58        let id = self.next_block_marker_id();
59        let marker_statement = mir::Statement::new(
60            source_info,
61            mir::StatementKind::Coverage(CoverageKind::BlockMarker { id }),
62        );
63        cfg.push(block, marker_statement);
64
65        id
66    }
67}
68
69impl CoverageInfoBuilder {
70    /// Creates a new coverage info builder, but only if coverage instrumentation
71    /// is enabled and `def_id` represents a function that is eligible for coverage.
72    pub(crate) fn new_if_enabled(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<Self> {
73        if !tcx.sess.instrument_coverage() || !tcx.is_eligible_for_coverage(def_id) {
74            return None;
75        }
76
77        Some(Self {
78            nots: FxHashMap::default(),
79            markers: BlockMarkerGen::default(),
80            branch_info: tcx.sess.instrument_coverage_branch().then(BranchInfo::default),
81        })
82    }
83
84    /// Unary `!` expressions inside an `if` condition are lowered by lowering
85    /// their argument instead, and then reversing the then/else arms of that `if`.
86    ///
87    /// That's awkward for branch coverage instrumentation, so to work around that
88    /// we pre-emptively visit any affected `!` expressions, and record extra
89    /// information that [`Builder::visit_coverage_branch_condition`] can use to
90    /// synthesize branch instrumentation for the enclosing `!`.
91    pub(crate) fn visit_unary_not(&mut self, thir: &Thir<'_>, unary_not: ExprId) {
92        assert_matches!(thir[unary_not].kind, ExprKind::Unary { op: UnOp::Not, .. });
93
94        // The information collected by this visitor is only needed when branch
95        // coverage or higher is enabled.
96        if self.branch_info.is_none() {
97            return;
98        }
99
100        self.visit_with_not_info(
101            thir,
102            unary_not,
103            // Set `is_flipped: false` for the `!` itself, so that its enclosed
104            // expression will have `is_flipped: true`.
105            NotInfo { enclosing_not: unary_not, is_flipped: false },
106        );
107    }
108
109    fn visit_with_not_info(&mut self, thir: &Thir<'_>, expr_id: ExprId, not_info: NotInfo) {
110        match self.nots.entry(expr_id) {
111            // This expression has already been marked by an enclosing `!`.
112            Entry::Occupied(_) => return,
113            Entry::Vacant(entry) => entry.insert(not_info),
114        };
115
116        match thir[expr_id].kind {
117            ExprKind::Unary { op: UnOp::Not, arg } => {
118                // Invert the `is_flipped` flag for the contents of this `!`.
119                let not_info = NotInfo { is_flipped: !not_info.is_flipped, ..not_info };
120                self.visit_with_not_info(thir, arg, not_info);
121            }
122            ExprKind::Scope { value, .. } => self.visit_with_not_info(thir, value, not_info),
123            ExprKind::Use { source } => self.visit_with_not_info(thir, source, not_info),
124            // All other expressions (including `&&` and `||`) don't need any
125            // special handling of their contents, so stop visiting.
126            _ => {}
127        }
128    }
129
130    fn register_two_way_branch<'tcx>(
131        &mut self,
132        cfg: &mut CFG<'tcx>,
133        source_info: SourceInfo,
134        true_block: BasicBlock,
135        false_block: BasicBlock,
136    ) {
137        // Bail out if branch coverage is not enabled.
138        let Some(branch_info) = self.branch_info.as_mut() else { return };
139
140        let true_marker = self.markers.inject_block_marker(cfg, source_info, true_block);
141        let false_marker = self.markers.inject_block_marker(cfg, source_info, false_block);
142
143        branch_info.branch_spans.push(BranchSpan {
144            span: source_info.span,
145            true_marker,
146            false_marker,
147        });
148    }
149
150    pub(crate) fn into_done(self) -> Box<CoverageInfoHi> {
151        let Self { nots: _, markers: BlockMarkerGen { num_block_markers }, branch_info } = self;
152
153        let branch_spans =
154            branch_info.map(|branch_info| branch_info.branch_spans).unwrap_or_default();
155
156        // For simplicity, always return an info struct (without Option), even
157        // if there's nothing interesting in it.
158        Box::new(CoverageInfoHi { num_block_markers, branch_spans })
159    }
160
161    pub(crate) fn as_done(&self) -> Box<CoverageInfoHi> {
162        let &Self { nots: _, markers: BlockMarkerGen { num_block_markers }, ref branch_info } =
163            self;
164
165        let branch_spans = branch_info
166            .as_ref()
167            .map(|branch_info| branch_info.branch_spans.as_slice())
168            .unwrap_or_default()
169            .to_owned();
170
171        // For simplicity, always return an info struct (without Option), even
172        // if there's nothing interesting in it.
173        Box::new(CoverageInfoHi { num_block_markers, branch_spans })
174    }
175}
176
177impl<'tcx> Builder<'_, 'tcx> {
178    /// If condition coverage is enabled, inject extra blocks and marker statements
179    /// that will let us track the value of the condition in `place`.
180    pub(crate) fn visit_coverage_standalone_condition(
181        &mut self,
182        mut expr_id: ExprId,     // Expression giving the span of the condition
183        place: mir::Place<'tcx>, // Already holds the boolean condition value
184        block: &mut BasicBlock,
185    ) {
186        // Bail out if condition coverage is not enabled for this function.
187        let Some(coverage_info) = self.coverage_info.as_mut() else { return };
188        if !self.tcx.sess.instrument_coverage_condition() {
189            return;
190        };
191
192        // Remove any wrappers, so that we can inspect the real underlying expression.
193        while let ExprKind::Use { source: inner } | ExprKind::Scope { value: inner, .. } =
194            self.thir[expr_id].kind
195        {
196            expr_id = inner;
197        }
198        // If the expression is a lazy logical op, it will naturally get branch
199        // coverage as part of its normal lowering, so we can disregard it here.
200        if let ExprKind::LogicalOp { .. } = self.thir[expr_id].kind {
201            return;
202        }
203
204        let source_info = SourceInfo { span: self.thir[expr_id].span, scope: self.source_scope };
205
206        // Using the boolean value that has already been stored in `place`, set up
207        // control flow in the shape of a diamond, so that we can place separate
208        // marker statements in the true and false blocks. The coverage MIR pass
209        // will use those markers to inject coverage counters as appropriate.
210        //
211        //          block
212        //         /     \
213        // true_block   false_block
214        //  (marker)     (marker)
215        //         \     /
216        //        join_block
217
218        let true_block = self.cfg.start_new_block();
219        let false_block = self.cfg.start_new_block();
220        self.cfg.terminate(
221            *block,
222            source_info,
223            mir::TerminatorKind::if_(mir::Operand::Copy(place), true_block, false_block),
224        );
225
226        coverage_info.register_two_way_branch(&mut self.cfg, source_info, true_block, false_block);
227
228        let join_block = self.cfg.start_new_block();
229        self.cfg.goto(true_block, source_info, join_block);
230        self.cfg.goto(false_block, source_info, join_block);
231        // Any subsequent codegen in the caller should use the new join block.
232        *block = join_block;
233    }
234
235    /// If branch coverage is enabled, inject marker statements into `then_block`
236    /// and `else_block`, and record their IDs in the table of branch spans.
237    pub(crate) fn visit_coverage_branch_condition(
238        &mut self,
239        mut expr_id: ExprId,
240        mut then_block: BasicBlock,
241        mut else_block: BasicBlock,
242    ) {
243        // Bail out if coverage is not enabled for this function.
244        let Some(coverage_info) = self.coverage_info.as_mut() else { return };
245
246        // If this condition expression is nested within one or more `!` expressions,
247        // replace it with the enclosing `!` collected by `visit_unary_not`.
248        if let Some(&NotInfo { enclosing_not, is_flipped }) = coverage_info.nots.get(&expr_id) {
249            expr_id = enclosing_not;
250            if is_flipped {
251                std::mem::swap(&mut then_block, &mut else_block);
252            }
253        }
254
255        let source_info = SourceInfo { span: self.thir[expr_id].span, scope: self.source_scope };
256
257        coverage_info.register_two_way_branch(&mut self.cfg, source_info, then_block, else_block);
258    }
259
260    /// If branch coverage is enabled, inject marker statements into `true_block`
261    /// and `false_block`, and record their IDs in the table of branches.
262    ///
263    /// Used to instrument let-else and if-let (including let-chains) for branch coverage.
264    pub(crate) fn visit_coverage_conditional_let(
265        &mut self,
266        pattern: &Pat<'tcx>, // Pattern that has been matched when the true path is taken
267        true_block: BasicBlock,
268        false_block: BasicBlock,
269    ) {
270        // Bail out if coverage is not enabled for this function.
271        let Some(coverage_info) = self.coverage_info.as_mut() else { return };
272
273        let source_info = SourceInfo { span: pattern.span, scope: self.source_scope };
274        coverage_info.register_two_way_branch(&mut self.cfg, source_info, true_block, false_block);
275    }
276}