Skip to main content

rustc_mir_build/builder/
coverageinfo.rs

1use std::assert_matches;
2use std::collections::hash_map::Entry;
3
4use rustc_data_structures::fx::FxHashMap;
5use rustc_hir::HirId;
6use rustc_middle::mir::coverage::{
7    BlockMarkerId, BranchSpan, CoverageEarlyInfo, CoverageKind, PointKind,
8};
9use rustc_middle::mir::{self, BasicBlock, SourceInfo, Statement, UnOp};
10use rustc_middle::thir::{self, ExprId, ExprKind, Pat, Thir};
11use rustc_middle::ty::TyCtxt;
12use rustc_span::def_id::LocalDefId;
13
14use crate::builder::{Builder, CFG};
15
16/// Collects coverage-related information during MIR building, to eventually be
17/// turned into a function's [`CoverageEarlyInfo`] when MIR building is complete.
18///
19/// FIXME(Zalathar): Now that we have [`CoverageKind::Point`], we should be able
20/// to remove this and perform HIR-aware analysis during instrumentation instead.
21pub(crate) struct CoverageInfoBuilder {
22    /// Maps condition expressions to their enclosing `!`, for better instrumentation.
23    nots: FxHashMap<ExprId, NotInfo>,
24
25    markers: BlockMarkerGen,
26
27    /// Present if branch coverage is enabled.
28    branch_info: Option<BranchInfo>,
29}
30
31#[derive(#[automatically_derived]
impl ::core::default::Default for BranchInfo {
    #[inline]
    fn default() -> BranchInfo {
        BranchInfo { branch_spans: ::core::default::Default::default() }
    }
}Default)]
32struct BranchInfo {
33    branch_spans: Vec<BranchSpan>,
34}
35
36#[derive(#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for NotInfo { }
#[automatically_derived]
impl ::core::clone::Clone for NotInfo {
    #[inline]
    fn clone(&self) -> NotInfo {
        let _: ::core::clone::AssertParamIsClone<ExprId>;
        let _: ::core::clone::AssertParamIsClone<bool>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for NotInfo { }Copy)]
37struct NotInfo {
38    /// When visiting the associated expression as a branch condition, treat this
39    /// enclosing `!` as the branch condition instead.
40    enclosing_not: ExprId,
41    /// True if the associated expression is nested within an odd number of `!`
42    /// expressions relative to `enclosing_not` (inclusive of `enclosing_not`).
43    is_flipped: bool,
44}
45
46#[derive(#[automatically_derived]
impl ::core::default::Default for BlockMarkerGen {
    #[inline]
    fn default() -> BlockMarkerGen {
        BlockMarkerGen {
            num_block_markers: ::core::default::Default::default(),
        }
    }
}Default)]
47struct BlockMarkerGen {
48    num_block_markers: usize,
49}
50
51impl BlockMarkerGen {
52    fn next_block_marker_id(&mut self) -> BlockMarkerId {
53        let id = BlockMarkerId::from_usize(self.num_block_markers);
54        self.num_block_markers += 1;
55        id
56    }
57
58    fn inject_block_marker(
59        &mut self,
60        cfg: &mut CFG<'_>,
61        source_info: SourceInfo,
62        block: BasicBlock,
63    ) -> BlockMarkerId {
64        let id = self.next_block_marker_id();
65        let marker_statement = mir::Statement::new(
66            source_info,
67            mir::StatementKind::Coverage(CoverageKind::BlockMarker { id }),
68        );
69        cfg.push(block, marker_statement);
70
71        id
72    }
73}
74
75impl CoverageInfoBuilder {
76    /// Creates a new coverage info builder, but only if coverage instrumentation
77    /// is enabled and `def_id` represents a function that is eligible for coverage.
78    pub(crate) fn new_if_enabled(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<Self> {
79        if !tcx.sess.instrument_coverage() || !tcx.is_eligible_for_coverage(def_id) {
80            return None;
81        }
82
83        Some(Self {
84            nots: FxHashMap::default(),
85            markers: BlockMarkerGen::default(),
86            branch_info: tcx.sess.instrument_coverage_branch().then(BranchInfo::default),
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        {
    match thir[unary_not].kind {
        ExprKind::Unary { op: UnOp::Not, .. } => {}
        ref left_val => {
            ::core::panicking::assert_matches_failed(left_val,
                "ExprKind::Unary { op: UnOp::Not, .. }",
                ::core::option::Option::None);
        }
    }
};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::ValueExpr { 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        cfg: &mut CFG<'tcx>,
139        source_info: SourceInfo,
140        true_block: BasicBlock,
141        false_block: BasicBlock,
142    ) {
143        // Bail out if branch coverage is not enabled.
144        let Some(branch_info) = self.branch_info.as_mut() else { return };
145
146        let true_marker = self.markers.inject_block_marker(cfg, source_info, true_block);
147        let false_marker = self.markers.inject_block_marker(cfg, source_info, false_block);
148
149        branch_info.branch_spans.push(BranchSpan {
150            span: source_info.span,
151            true_marker,
152            false_marker,
153        });
154    }
155
156    pub(crate) fn into_done(self) -> Box<CoverageEarlyInfo> {
157        let Self { nots: _, markers: BlockMarkerGen { num_block_markers }, branch_info } = self;
158
159        let branch_spans =
160            branch_info.map(|branch_info| branch_info.branch_spans).unwrap_or_default();
161
162        // For simplicity, always return an info struct (without Option), even
163        // if there's nothing interesting in it.
164        Box::new(CoverageEarlyInfo { num_block_markers, branch_spans })
165    }
166
167    pub(crate) fn as_done(&self) -> Box<CoverageEarlyInfo> {
168        let &Self { nots: _, markers: BlockMarkerGen { num_block_markers }, ref branch_info } =
169            self;
170
171        let branch_spans = branch_info
172            .as_ref()
173            .map(|branch_info| branch_info.branch_spans.as_slice())
174            .unwrap_or_default()
175            .to_owned();
176
177        // For simplicity, always return an info struct (without Option), even
178        // if there's nothing interesting in it.
179        Box::new(CoverageEarlyInfo { num_block_markers, branch_spans })
180    }
181}
182
183impl<'tcx> Builder<'_, 'tcx> {
184    /// Does nothing if `-Cinstrument-coverage` is not enabled.
185    ///
186    /// Otherwise, pushes a marker statement to `block` indicating that this is where
187    /// the HIR expression `hir_id` is being evaluated.
188    pub(crate) fn push_coverage_point_for_expr(
189        &mut self,
190        block: BasicBlock,
191        source_info: SourceInfo,
192        hir_id: HirId,
193    ) {
194        if !self.tcx.sess.instrument_coverage() {
195            return;
196        }
197        self.push_coverage_point_inner(block, source_info, PointKind::Expr, hir_id);
198    }
199
200    /// Does nothing if `-Cinstrument-coverage` is not enabled.
201    ///
202    /// Otherwise, pushes a marker statement to `block` indicating that this is where
203    /// the one-sided if-expression `if_expr` will generate its synthetic `else {}`
204    /// path, since it lacks an explicit `else` block.
205    pub(crate) fn push_coverage_point_for_implicit_else(
206        &mut self,
207        block: BasicBlock,
208        source_info: SourceInfo,
209        if_expr: &thir::Expr<'tcx>,
210    ) {
211        if !self.tcx.sess.instrument_coverage() {
212            return;
213        }
214        // Recover the full HirId by combining a local ID with the function's owner ID.
215        let hir_id = HirId { owner: self.hir_id.owner, local_id: if_expr.temp_scope_id };
216        self.push_coverage_point_inner(block, source_info, PointKind::ImplicitElse, hir_id);
217    }
218
219    /// Does nothing if `-Cinstrument-coverage` is not enabled.
220    ///
221    /// Otherwise, pushes a marker statement to `block` indicating that this is where
222    /// the function `fn_hir_id` would implicitly return at the end of its body.
223    pub(crate) fn push_coverage_point_for_fn_end(
224        &mut self,
225        block: BasicBlock,
226        source_info: SourceInfo,
227        fn_hir_id: HirId,
228    ) {
229        if !self.tcx.sess.instrument_coverage() {
230            return;
231        }
232        self.push_coverage_point_inner(block, source_info, PointKind::FunctionEnd, fn_hir_id);
233    }
234
235    fn push_coverage_point_inner(
236        &mut self,
237        block: BasicBlock,
238        source_info: SourceInfo,
239        point_kind: PointKind,
240        hir_id: HirId,
241    ) {
242        if !self.tcx.sess.instrument_coverage() {
    ::core::panicking::panic("assertion failed: self.tcx.sess.instrument_coverage()")
};assert!(self.tcx.sess.instrument_coverage());
243
244        let stmt = Statement::new(
245            source_info,
246            mir::StatementKind::Coverage(CoverageKind::Point { point_kind, hir_id }),
247        );
248        self.cfg.push(block, stmt);
249    }
250
251    /// If condition coverage is enabled, inject extra blocks and marker statements
252    /// that will let us track the value of the condition in `place`.
253    pub(crate) fn visit_coverage_standalone_condition(
254        &mut self,
255        mut expr_id: ExprId,     // Expression giving the span of the condition
256        place: mir::Place<'tcx>, // Already holds the boolean condition value
257        block: &mut BasicBlock,
258    ) {
259        // Bail out if condition coverage is not enabled for this function.
260        let Some(coverage_info) = self.coverage_info.as_mut() else { return };
261        if !self.tcx.sess.instrument_coverage_condition() {
262            return;
263        };
264
265        // Remove any wrappers, so that we can inspect the real underlying expression.
266        while let ExprKind::ValueExpr { source: inner } | ExprKind::Scope { value: inner, .. } =
267            self.thir[expr_id].kind
268        {
269            expr_id = inner;
270        }
271        // If the expression is a lazy logical op, it will naturally get branch
272        // coverage as part of its normal lowering, so we can disregard it here.
273        if let ExprKind::LogicalOp { .. } = self.thir[expr_id].kind {
274            return;
275        }
276
277        let source_info = SourceInfo { span: self.thir[expr_id].span, scope: self.source_scope };
278
279        // Using the boolean value that has already been stored in `place`, set up
280        // control flow in the shape of a diamond, so that we can place separate
281        // marker statements in the true and false blocks. The coverage MIR pass
282        // will use those markers to inject coverage counters as appropriate.
283        //
284        //          block
285        //         /     \
286        // true_block   false_block
287        //  (marker)     (marker)
288        //         \     /
289        //        join_block
290
291        let true_block = self.cfg.start_new_block();
292        let false_block = self.cfg.start_new_block();
293        self.cfg.terminate(
294            *block,
295            source_info,
296            mir::TerminatorKind::if_(mir::Operand::Copy(place), true_block, false_block),
297        );
298
299        coverage_info.register_two_way_branch(&mut self.cfg, source_info, true_block, false_block);
300
301        let join_block = self.cfg.start_new_block();
302        self.cfg.goto(true_block, source_info, join_block);
303        self.cfg.goto(false_block, source_info, join_block);
304        // Any subsequent codegen in the caller should use the new join block.
305        *block = join_block;
306    }
307
308    /// If branch coverage is enabled, inject marker statements into `true_block`
309    /// and `false_block`, and record their IDs in the table of branch spans.
310    pub(crate) fn visit_coverage_branch_condition(
311        &mut self,
312        mut expr_id: ExprId,
313        mut true_block: BasicBlock,
314        mut false_block: BasicBlock,
315    ) {
316        // Bail out if coverage is not enabled for this function.
317        let Some(coverage_info) = self.coverage_info.as_mut() else { return };
318
319        // If this condition expression is nested within one or more `!` expressions,
320        // replace it with the enclosing `!` collected by `visit_unary_not`.
321        if let Some(&NotInfo { enclosing_not, is_flipped }) = coverage_info.nots.get(&expr_id) {
322            expr_id = enclosing_not;
323            if is_flipped {
324                std::mem::swap(&mut true_block, &mut false_block);
325            }
326        }
327
328        let source_info = SourceInfo { span: self.thir[expr_id].span, scope: self.source_scope };
329
330        coverage_info.register_two_way_branch(&mut self.cfg, source_info, true_block, false_block);
331    }
332
333    /// If branch coverage is enabled, inject marker statements into `true_block`
334    /// and `false_block`, and record their IDs in the table of branches.
335    ///
336    /// Used to instrument let-else and if-let (including let-chains) for branch coverage.
337    pub(crate) fn visit_coverage_conditional_let(
338        &mut self,
339        pattern: &Pat<'tcx>, // Pattern that has been matched when the true path is taken
340        true_block: BasicBlock,
341        false_block: BasicBlock,
342    ) {
343        // Bail out if coverage is not enabled for this function.
344        let Some(coverage_info) = self.coverage_info.as_mut() else { return };
345
346        let source_info = SourceInfo { span: pattern.span, scope: self.source_scope };
347        coverage_info.register_two_way_branch(&mut self.cfg, source_info, true_block, false_block);
348    }
349}