1- use rustc_middle:: mir;
2- use rustc_middle:: mir:: coverage:: BranchSpan ;
1+ use std:: assert_matches:: assert_matches;
2+ use std:: collections:: hash_map:: Entry ;
3+
4+ use rustc_data_structures:: fx:: FxHashMap ;
5+ use rustc_middle:: mir:: coverage:: { BlockMarkerId , BranchSpan , CoverageKind } ;
6+ use rustc_middle:: mir:: { self , BasicBlock , UnOp } ;
7+ use rustc_middle:: thir:: { ExprId , ExprKind , Thir } ;
38use rustc_middle:: ty:: TyCtxt ;
49use rustc_span:: def_id:: LocalDefId ;
510
11+ use crate :: build:: Builder ;
12+
613pub ( crate ) struct HirBranchInfoBuilder {
14+ inversions : FxHashMap < ExprId , Inversion > ,
15+
716 num_block_markers : usize ,
817 branch_spans : Vec < BranchSpan > ,
918}
1019
20+ #[ derive( Clone , Copy ) ]
21+ struct Inversion {
22+ /// When visiting the associated expression as a branch condition, treat this
23+ /// enclosing `!` as the branch condition instead.
24+ enclosing_not : ExprId ,
25+ /// True if the associated expression is nested within an odd number of `!`
26+ /// expressions relative to `enclosing_not` (inclusive of `enclosing_not`).
27+ is_inverted : bool ,
28+ }
29+
1130impl HirBranchInfoBuilder {
1231 pub ( crate ) fn new_if_enabled_and_eligible ( tcx : TyCtxt < ' _ > , def_id : LocalDefId ) -> Option < Self > {
1332 if tcx. sess . instrument_coverage_branch ( ) && tcx. is_eligible_for_coverage ( def_id) {
14- Some ( Self { num_block_markers : 0 , branch_spans : vec ! [ ] } )
33+ Some ( Self {
34+ inversions : FxHashMap :: default ( ) ,
35+ num_block_markers : 0 ,
36+ branch_spans : vec ! [ ] ,
37+ } )
1538 } else {
1639 None
1740 }
1841 }
1942
43+ /// Unary `!` expressions inside an `if` condition are lowered by lowering
44+ /// their argument instead, and then reversing the then/else arms of that `if`.
45+ ///
46+ /// That's awkward for branch coverage instrumentation, so to work around that
47+ /// we pre-emptively visit any affected `!` expressions, and record extra
48+ /// information that [`Builder::visit_coverage_branch_condition`] can use to
49+ /// synthesize branch instrumentation for the enclosing `!`.
50+ pub ( crate ) fn visit_unary_not ( & mut self , thir : & Thir < ' _ > , unary_not : ExprId ) {
51+ assert_matches ! ( thir[ unary_not] . kind, ExprKind :: Unary { op: UnOp :: Not , .. } ) ;
52+
53+ self . visit_inverted (
54+ thir,
55+ unary_not,
56+ // Set `is_inverted: false` for the `!` itself, so that its enclosed
57+ // expression will have `is_inverted: true`.
58+ Inversion { enclosing_not : unary_not, is_inverted : false } ,
59+ ) ;
60+ }
61+
62+ fn visit_inverted ( & mut self , thir : & Thir < ' _ > , expr_id : ExprId , inversion : Inversion ) {
63+ match self . inversions . entry ( expr_id) {
64+ // This expression has already been marked by an enclosing `!`.
65+ Entry :: Occupied ( _) => return ,
66+ Entry :: Vacant ( entry) => entry. insert ( inversion) ,
67+ } ;
68+
69+ match thir[ expr_id] . kind {
70+ ExprKind :: Unary { op : UnOp :: Not , arg } => {
71+ // Flip the `is_inverted` flag for the contents of this `!`.
72+ let inversion = Inversion { is_inverted : !inversion. is_inverted , ..inversion } ;
73+ self . visit_inverted ( thir, arg, inversion) ;
74+ }
75+ ExprKind :: Scope { value, .. } => self . visit_inverted ( thir, value, inversion) ,
76+ ExprKind :: Use { source } => self . visit_inverted ( thir, source, inversion) ,
77+ // All other expressions (including `&&` and `||`) don't need any
78+ // special handling of their contents, so stop visiting.
79+ _ => { }
80+ }
81+ }
82+
83+ fn next_block_marker_id ( & mut self ) -> BlockMarkerId {
84+ let id = BlockMarkerId :: from_usize ( self . num_block_markers ) ;
85+ self . num_block_markers += 1 ;
86+ id
87+ }
88+
2089 pub ( crate ) fn into_done ( self ) -> Option < Box < mir:: coverage:: HirBranchInfo > > {
21- let Self { num_block_markers, branch_spans } = self ;
90+ let Self { inversions : _ , num_block_markers, branch_spans } = self ;
2291
2392 if num_block_markers == 0 {
2493 assert ! ( branch_spans. is_empty( ) ) ;
@@ -28,3 +97,54 @@ impl HirBranchInfoBuilder {
2897 Some ( Box :: new ( mir:: coverage:: HirBranchInfo { num_block_markers, branch_spans } ) )
2998 }
3099}
100+
101+ impl Builder < ' _ , ' _ > {
102+ /// If branch coverage is enabled, inject marker statements into `then_block`
103+ /// and `else_block`, and record their IDs in the table of branch spans.
104+ pub ( crate ) fn visit_coverage_branch_condition (
105+ & mut self ,
106+ expr_id : ExprId ,
107+ then_block : BasicBlock ,
108+ else_block : BasicBlock ,
109+ ) {
110+ // Bail out if branch coverage is not enabled for this function.
111+ let Some ( branch_info) = self . coverage_branch_info . as_ref ( ) else { return } ;
112+
113+ // If this condition expression is nested within one or more `!` expressions,
114+ // replace it with the enclosing `!` collected by `visit_unary_not`.
115+ let ( expr_id, is_inverted) = match branch_info. inversions . get ( & expr_id) {
116+ Some ( & Inversion { enclosing_not, is_inverted } ) => ( enclosing_not, is_inverted) ,
117+ None => ( expr_id, false ) ,
118+ } ;
119+ let source_info = self . source_info ( self . thir [ expr_id] . span ) ;
120+
121+ // Now that we have `source_info`, we can upgrade to a &mut reference.
122+ let branch_info = self . coverage_branch_info . as_mut ( ) . expect ( "upgrading & to &mut" ) ;
123+
124+ let mut inject_branch_marker = |block : BasicBlock | {
125+ let id = branch_info. next_block_marker_id ( ) ;
126+
127+ let marker_statement = mir:: Statement {
128+ source_info,
129+ kind : mir:: StatementKind :: Coverage ( Box :: new ( mir:: Coverage {
130+ kind : CoverageKind :: BlockMarker { id } ,
131+ } ) ) ,
132+ } ;
133+ self . cfg . push ( block, marker_statement) ;
134+
135+ id
136+ } ;
137+
138+ let mut true_marker = inject_branch_marker ( then_block) ;
139+ let mut false_marker = inject_branch_marker ( else_block) ;
140+ if is_inverted {
141+ std:: mem:: swap ( & mut true_marker, & mut false_marker) ;
142+ }
143+
144+ branch_info. branch_spans . push ( BranchSpan {
145+ span : source_info. span ,
146+ true_marker,
147+ false_marker,
148+ } ) ;
149+ }
150+ }
0 commit comments