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+ /// Maps condition expressions to their enclosing `!`, for better instrumentation.
15+ inversions : FxHashMap < ExprId , Inversion > ,
16+
717 num_block_markers : usize ,
818 branch_spans : Vec < BranchSpan > ,
919}
1020
21+ #[ derive( Clone , Copy ) ]
22+ struct Inversion {
23+ /// When visiting the associated expression as a branch condition, treat this
24+ /// enclosing `!` as the branch condition instead.
25+ enclosing_not : ExprId ,
26+ /// True if the associated expression is nested within an odd number of `!`
27+ /// expressions relative to `enclosing_not` (inclusive of `enclosing_not`).
28+ is_inverted : bool ,
29+ }
30+
1131impl HirBranchInfoBuilder {
1232 /// Creates a new branch info builder, but only if branch coverage instrumentation
1333 /// is enabled and `def_id` represents a function that is eligible for coverage.
1434 pub ( crate ) fn new_if_enabled ( tcx : TyCtxt < ' _ > , def_id : LocalDefId ) -> Option < Self > {
1535 if tcx. sess . instrument_coverage_branch ( ) && tcx. is_eligible_for_coverage ( def_id) {
1636 Some ( Self {
17- // (placeholder)
37+ inversions : FxHashMap :: default ( ) ,
1838 num_block_markers : 0 ,
1939 branch_spans : vec ! [ ] ,
2040 } )
@@ -23,8 +43,54 @@ impl HirBranchInfoBuilder {
2343 }
2444 }
2545
46+ /// Unary `!` expressions inside an `if` condition are lowered by lowering
47+ /// their argument instead, and then reversing the then/else arms of that `if`.
48+ ///
49+ /// That's awkward for branch coverage instrumentation, so to work around that
50+ /// we pre-emptively visit any affected `!` expressions, and record extra
51+ /// information that [`Builder::visit_coverage_branch_condition`] can use to
52+ /// synthesize branch instrumentation for the enclosing `!`.
53+ pub ( crate ) fn visit_unary_not ( & mut self , thir : & Thir < ' _ > , unary_not : ExprId ) {
54+ assert_matches ! ( thir[ unary_not] . kind, ExprKind :: Unary { op: UnOp :: Not , .. } ) ;
55+
56+ self . visit_inverted (
57+ thir,
58+ unary_not,
59+ // Set `is_inverted: false` for the `!` itself, so that its enclosed
60+ // expression will have `is_inverted: true`.
61+ Inversion { enclosing_not : unary_not, is_inverted : false } ,
62+ ) ;
63+ }
64+
65+ fn visit_inverted ( & mut self , thir : & Thir < ' _ > , expr_id : ExprId , inversion : Inversion ) {
66+ match self . inversions . entry ( expr_id) {
67+ // This expression has already been marked by an enclosing `!`.
68+ Entry :: Occupied ( _) => return ,
69+ Entry :: Vacant ( entry) => entry. insert ( inversion) ,
70+ } ;
71+
72+ match thir[ expr_id] . kind {
73+ ExprKind :: Unary { op : UnOp :: Not , arg } => {
74+ // Flip the `is_inverted` flag for the contents of this `!`.
75+ let inversion = Inversion { is_inverted : !inversion. is_inverted , ..inversion } ;
76+ self . visit_inverted ( thir, arg, inversion) ;
77+ }
78+ ExprKind :: Scope { value, .. } => self . visit_inverted ( thir, value, inversion) ,
79+ ExprKind :: Use { source } => self . visit_inverted ( thir, source, inversion) ,
80+ // All other expressions (including `&&` and `||`) don't need any
81+ // special handling of their contents, so stop visiting.
82+ _ => { }
83+ }
84+ }
85+
86+ fn next_block_marker_id ( & mut self ) -> BlockMarkerId {
87+ let id = BlockMarkerId :: from_usize ( self . num_block_markers ) ;
88+ self . num_block_markers += 1 ;
89+ id
90+ }
91+
2692 pub ( crate ) fn into_done ( self ) -> Option < Box < mir:: coverage:: HirBranchInfo > > {
27- let Self { num_block_markers, branch_spans } = self ;
93+ let Self { inversions : _ , num_block_markers, branch_spans } = self ;
2894
2995 if num_block_markers == 0 {
3096 assert ! ( branch_spans. is_empty( ) ) ;
@@ -34,3 +100,54 @@ impl HirBranchInfoBuilder {
34100 Some ( Box :: new ( mir:: coverage:: HirBranchInfo { num_block_markers, branch_spans } ) )
35101 }
36102}
103+
104+ impl Builder < ' _ , ' _ > {
105+ /// If branch coverage is enabled, inject marker statements into `then_block`
106+ /// and `else_block`, and record their IDs in the table of branch spans.
107+ pub ( crate ) fn visit_coverage_branch_condition (
108+ & mut self ,
109+ expr_id : ExprId ,
110+ then_block : BasicBlock ,
111+ else_block : BasicBlock ,
112+ ) {
113+ // Bail out if branch coverage is not enabled for this function.
114+ let Some ( branch_info) = self . coverage_branch_info . as_ref ( ) else { return } ;
115+
116+ // If this condition expression is nested within one or more `!` expressions,
117+ // replace it with the enclosing `!` collected by `visit_unary_not`.
118+ let ( expr_id, is_inverted) = match branch_info. inversions . get ( & expr_id) {
119+ Some ( & Inversion { enclosing_not, is_inverted } ) => ( enclosing_not, is_inverted) ,
120+ None => ( expr_id, false ) ,
121+ } ;
122+ let source_info = self . source_info ( self . thir [ expr_id] . span ) ;
123+
124+ // Now that we have `source_info`, we can upgrade to a &mut reference.
125+ let branch_info = self . coverage_branch_info . as_mut ( ) . expect ( "upgrading & to &mut" ) ;
126+
127+ let mut inject_branch_marker = |block : BasicBlock | {
128+ let id = branch_info. next_block_marker_id ( ) ;
129+
130+ let marker_statement = mir:: Statement {
131+ source_info,
132+ kind : mir:: StatementKind :: Coverage ( Box :: new ( mir:: Coverage {
133+ kind : CoverageKind :: BlockMarker { id } ,
134+ } ) ) ,
135+ } ;
136+ self . cfg . push ( block, marker_statement) ;
137+
138+ id
139+ } ;
140+
141+ let mut true_marker = inject_branch_marker ( then_block) ;
142+ let mut false_marker = inject_branch_marker ( else_block) ;
143+ if is_inverted {
144+ std:: mem:: swap ( & mut true_marker, & mut false_marker) ;
145+ }
146+
147+ branch_info. branch_spans . push ( BranchSpan {
148+ span : source_info. span ,
149+ true_marker,
150+ false_marker,
151+ } ) ;
152+ }
153+ }
0 commit comments