1use std::assert_matches;
2use std::collections::hash_map::Entry;
34use 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;
1011use crate::builder::{Builder, CFG};
1213/// 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.
17nots: FxHashMap<ExprId, NotInfo>,
1819 markers: BlockMarkerGen,
2021/// Present if branch coverage is enabled.
22branch_info: Option<BranchInfo>,
23}
2425#[derive(#[automatically_derived]
impl ::core::default::Default for BranchInfo {
#[inline]
fn default() -> BranchInfo {
BranchInfo { branch_spans: ::core::default::Default::default() }
}
}Default)]
26struct BranchInfo {
27 branch_spans: Vec<BranchSpan>,
28}
2930#[derive(#[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)]
31struct NotInfo {
32/// When visiting the associated expression as a branch condition, treat this
33 /// enclosing `!` as the branch condition instead.
34enclosing_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`).
37is_flipped: bool,
38}
3940#[derive(#[automatically_derived]
impl ::core::default::Default for BlockMarkerGen {
#[inline]
fn default() -> BlockMarkerGen {
BlockMarkerGen {
num_block_markers: ::core::default::Default::default(),
}
}
}Default)]
41struct BlockMarkerGen {
42 num_block_markers: usize,
43}
4445impl BlockMarkerGen {
46fn next_block_marker_id(&mut self) -> BlockMarkerId {
47let id = BlockMarkerId::from_usize(self.num_block_markers);
48self.num_block_markers += 1;
49id50 }
5152fn inject_block_marker(
53&mut self,
54 cfg: &mut CFG<'_>,
55 source_info: SourceInfo,
56 block: BasicBlock,
57 ) -> BlockMarkerId {
58let id = self.next_block_marker_id();
59let marker_statement = mir::Statement::new(
60source_info,
61 mir::StatementKind::Coverage(CoverageKind::BlockMarker { id }),
62 );
63cfg.push(block, marker_statement);
6465id66 }
67}
6869impl 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.
72pub(crate) fn new_if_enabled(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<Self> {
73if !tcx.sess.instrument_coverage() || !tcx.is_eligible_for_coverage(def_id) {
74return None;
75 }
7677Some(Self {
78 nots: FxHashMap::default(),
79 markers: BlockMarkerGen::default(),
80 branch_info: tcx.sess.instrument_coverage_branch().then(BranchInfo::default),
81 })
82 }
8384/// 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 `!`.
91pub(crate) fn visit_unary_not(&mut self, thir: &Thir<'_>, unary_not: ExprId) {
92{
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, .. });
9394// The information collected by this visitor is only needed when branch
95 // coverage or higher is enabled.
96if self.branch_info.is_none() {
97return;
98 }
99100self.visit_with_not_info(
101thir,
102unary_not,
103// Set `is_flipped: false` for the `!` itself, so that its enclosed
104 // expression will have `is_flipped: true`.
105NotInfo { enclosing_not: unary_not, is_flipped: false },
106 );
107 }
108109fn visit_with_not_info(&mut self, thir: &Thir<'_>, expr_id: ExprId, not_info: NotInfo) {
110match self.nots.entry(expr_id) {
111// This expression has already been marked by an enclosing `!`.
112Entry::Occupied(_) => return,
113 Entry::Vacant(entry) => entry.insert(not_info),
114 };
115116match thir[expr_id].kind {
117 ExprKind::Unary { op: UnOp::Not, arg } => {
118// Invert the `is_flipped` flag for the contents of this `!`.
119let not_info = NotInfo { is_flipped: !not_info.is_flipped, ..not_info };
120self.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 }
129130fn 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.
138let Some(branch_info) = self.branch_info.as_mut() else { return };
139140let true_marker = self.markers.inject_block_marker(cfg, source_info, true_block);
141let false_marker = self.markers.inject_block_marker(cfg, source_info, false_block);
142143branch_info.branch_spans.push(BranchSpan {
144 span: source_info.span,
145true_marker,
146false_marker,
147 });
148 }
149150pub(crate) fn into_done(self) -> Box<CoverageInfoHi> {
151let Self { nots: _, markers: BlockMarkerGen { num_block_markers }, branch_info } = self;
152153let branch_spans =
154branch_info.map(|branch_info| branch_info.branch_spans).unwrap_or_default();
155156// For simplicity, always return an info struct (without Option), even
157 // if there's nothing interesting in it.
158Box::new(CoverageInfoHi { num_block_markers, branch_spans })
159 }
160161pub(crate) fn as_done(&self) -> Box<CoverageInfoHi> {
162let &Self { nots: _, markers: BlockMarkerGen { num_block_markers }, ref branch_info } =
163self;
164165let branch_spans = branch_info166 .as_ref()
167 .map(|branch_info| branch_info.branch_spans.as_slice())
168 .unwrap_or_default()
169 .to_owned();
170171// For simplicity, always return an info struct (without Option), even
172 // if there's nothing interesting in it.
173Box::new(CoverageInfoHi { num_block_markers, branch_spans })
174 }
175}
176177impl<'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`.
180pub(crate) fn visit_coverage_standalone_condition(
181&mut self,
182mut expr_id: ExprId, // Expression giving the span of the condition
183place: mir::Place<'tcx>, // Already holds the boolean condition value
184block: &mut BasicBlock,
185 ) {
186// Bail out if condition coverage is not enabled for this function.
187let Some(coverage_info) = self.coverage_info.as_mut() else { return };
188if !self.tcx.sess.instrument_coverage_condition() {
189return;
190 };
191192// Remove any wrappers, so that we can inspect the real underlying expression.
193while let ExprKind::Use { source: inner } | ExprKind::Scope { value: inner, .. } =
194self.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.
200if let ExprKind::LogicalOp { .. } = self.thir[expr_id].kind {
201return;
202 }
203204let source_info = SourceInfo { span: self.thir[expr_id].span, scope: self.source_scope };
205206// 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
217218let true_block = self.cfg.start_new_block();
219let false_block = self.cfg.start_new_block();
220self.cfg.terminate(
221*block,
222source_info,
223 mir::TerminatorKind::if_(mir::Operand::Copy(place), true_block, false_block),
224 );
225226coverage_info.register_two_way_branch(&mut self.cfg, source_info, true_block, false_block);
227228let join_block = self.cfg.start_new_block();
229self.cfg.goto(true_block, source_info, join_block);
230self.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 }
234235/// 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.
237pub(crate) fn visit_coverage_branch_condition(
238&mut self,
239mut expr_id: ExprId,
240mut then_block: BasicBlock,
241mut else_block: BasicBlock,
242 ) {
243// Bail out if coverage is not enabled for this function.
244let Some(coverage_info) = self.coverage_info.as_mut() else { return };
245246// If this condition expression is nested within one or more `!` expressions,
247 // replace it with the enclosing `!` collected by `visit_unary_not`.
248if let Some(&NotInfo { enclosing_not, is_flipped }) = coverage_info.nots.get(&expr_id) {
249expr_id = enclosing_not;
250if is_flipped {
251 std::mem::swap(&mut then_block, &mut else_block);
252 }
253 }
254255let source_info = SourceInfo { span: self.thir[expr_id].span, scope: self.source_scope };
256257coverage_info.register_two_way_branch(&mut self.cfg, source_info, then_block, else_block);
258 }
259260/// 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.
264pub(crate) fn visit_coverage_conditional_let(
265&mut self,
266 pattern: &Pat<'tcx>, // Pattern that has been matched when the true path is taken
267true_block: BasicBlock,
268 false_block: BasicBlock,
269 ) {
270// Bail out if coverage is not enabled for this function.
271let Some(coverage_info) = self.coverage_info.as_mut() else { return };
272273let source_info = SourceInfo { span: pattern.span, scope: self.source_scope };
274coverage_info.register_two_way_branch(&mut self.cfg, source_info, true_block, false_block);
275 }
276}