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
13pub(crate) struct CoverageInfoBuilder {
16 nots: FxHashMap<ExprId, NotInfo>,
18
19 markers: BlockMarkerGen,
20
21 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 enclosing_not: ExprId,
35 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 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 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 if self.branch_info.is_none() {
97 return;
98 }
99
100 self.visit_with_not_info(
101 thir,
102 unary_not,
103 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 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 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 _ => {}
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 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 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 Box::new(CoverageInfoHi { num_block_markers, branch_spans })
174 }
175}
176
177impl<'tcx> Builder<'_, 'tcx> {
178 pub(crate) fn visit_coverage_standalone_condition(
181 &mut self,
182 mut expr_id: ExprId, place: mir::Place<'tcx>, block: &mut BasicBlock,
185 ) {
186 let Some(coverage_info) = self.coverage_info.as_mut() else { return };
188 if !self.tcx.sess.instrument_coverage_condition() {
189 return;
190 };
191
192 while let ExprKind::Use { source: inner } | ExprKind::Scope { value: inner, .. } =
194 self.thir[expr_id].kind
195 {
196 expr_id = inner;
197 }
198 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 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 *block = join_block;
233 }
234
235 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 let Some(coverage_info) = self.coverage_info.as_mut() else { return };
245
246 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 pub(crate) fn visit_coverage_conditional_let(
265 &mut self,
266 pattern: &Pat<'tcx>, true_block: BasicBlock,
268 false_block: BasicBlock,
269 ) {
270 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}