Skip to content

Commit 7667a91

Browse files
authored
Rollup merge of #125756 - Zalathar:branch-on-bool, r=oli-obk
coverage: Optionally instrument the RHS of lazy logical operators (This is an updated version of #124644 and #124402. Fixes #124120.) When `||` or `&&` is used outside of a branching context (such as the condition of an `if`), the rightmost value does not directly influence any branching decision, so branch coverage instrumentation does not treat it as its own true-or-false branch. That is a correct and useful interpretation of “branch coverage”, but might be undesirable in some contexts, as described at #124120. This PR therefore adds a new coverage level `-Zcoverage-options=condition` that behaves like branch coverage, but also adds additional branch instrumentation to the right-hand-side of lazy boolean operators. --- As discussed at #124120 (comment), this is mainly intended as an intermediate step towards fully-featured MC/DC instrumentation. It's likely that we'll eventually want to remove this coverage level (rather than stabilize it), either because it has been incorporated into MC/DC instrumentation, or because it's getting in the way of future MC/DC work. The main appeal of landing it now is so that work on tracking conditions can proceed concurrently with other MC/DC-related work. ````@rustbot```` label +A-code-coverage
2 parents e904660 + 35a8746 commit 7667a91

File tree

11 files changed

+411
-8
lines changed

11 files changed

+411
-8
lines changed

compiler/rustc_mir_build/src/build/coverageinfo.rs

+57
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,63 @@ impl BranchInfoBuilder {
157157
}
158158

159159
impl<'tcx> Builder<'_, 'tcx> {
160+
/// If condition coverage is enabled, inject extra blocks and marker statements
161+
/// that will let us track the value of the condition in `place`.
162+
pub(crate) fn visit_coverage_standalone_condition(
163+
&mut self,
164+
mut expr_id: ExprId, // Expression giving the span of the condition
165+
place: mir::Place<'tcx>, // Already holds the boolean condition value
166+
block: &mut BasicBlock,
167+
) {
168+
// Bail out if condition coverage is not enabled for this function.
169+
let Some(branch_info) = self.coverage_branch_info.as_mut() else { return };
170+
if !self.tcx.sess.instrument_coverage_condition() {
171+
return;
172+
};
173+
174+
// Remove any wrappers, so that we can inspect the real underlying expression.
175+
while let ExprKind::Use { source: inner } | ExprKind::Scope { value: inner, .. } =
176+
self.thir[expr_id].kind
177+
{
178+
expr_id = inner;
179+
}
180+
// If the expression is a lazy logical op, it will naturally get branch
181+
// coverage as part of its normal lowering, so we can disregard it here.
182+
if let ExprKind::LogicalOp { .. } = self.thir[expr_id].kind {
183+
return;
184+
}
185+
186+
let source_info = SourceInfo { span: self.thir[expr_id].span, scope: self.source_scope };
187+
188+
// Using the boolean value that has already been stored in `place`, set up
189+
// control flow in the shape of a diamond, so that we can place separate
190+
// marker statements in the true and false blocks. The coverage MIR pass
191+
// will use those markers to inject coverage counters as appropriate.
192+
//
193+
// block
194+
// / \
195+
// true_block false_block
196+
// (marker) (marker)
197+
// \ /
198+
// join_block
199+
200+
let true_block = self.cfg.start_new_block();
201+
let false_block = self.cfg.start_new_block();
202+
self.cfg.terminate(
203+
*block,
204+
source_info,
205+
mir::TerminatorKind::if_(mir::Operand::Copy(place), true_block, false_block),
206+
);
207+
208+
branch_info.add_two_way_branch(&mut self.cfg, source_info, true_block, false_block);
209+
210+
let join_block = self.cfg.start_new_block();
211+
self.cfg.goto(true_block, source_info, join_block);
212+
self.cfg.goto(false_block, source_info, join_block);
213+
// Any subsequent codegen in the caller should use the new join block.
214+
*block = join_block;
215+
}
216+
160217
/// If branch coverage is enabled, inject marker statements into `then_block`
161218
/// and `else_block`, and record their IDs in the table of branch spans.
162219
pub(crate) fn visit_coverage_branch_condition(

compiler/rustc_mir_build/src/build/expr/into.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
183183
const_: Const::from_bool(this.tcx, constant),
184184
},
185185
);
186-
let rhs = unpack!(this.expr_into_dest(destination, continuation, rhs));
186+
let mut rhs_block = unpack!(this.expr_into_dest(destination, continuation, rhs));
187+
// Instrument the lowered RHS's value for condition coverage.
188+
// (Does nothing if condition coverage is not enabled.)
189+
this.visit_coverage_standalone_condition(rhs, destination, &mut rhs_block);
190+
187191
let target = this.cfg.start_new_block();
188-
this.cfg.goto(rhs, source_info, target);
192+
this.cfg.goto(rhs_block, source_info, target);
189193
this.cfg.goto(short_circuit, source_info, target);
190194
target.unit()
191195
}

compiler/rustc_session/src/config.rs

+17-1
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,23 @@ pub enum CoverageLevel {
159159
Block,
160160
/// Also instrument branch points (includes block coverage).
161161
Branch,
162-
/// Instrument for MC/DC. Mostly a superset of branch coverage, but might
162+
/// Same as branch coverage, but also adds branch instrumentation for
163+
/// certain boolean expressions that are not directly used for branching.
164+
///
165+
/// For example, in the following code, `b` does not directly participate
166+
/// in a branch, but condition coverage will instrument it as its own
167+
/// artificial branch:
168+
/// ```
169+
/// # let (a, b) = (false, true);
170+
/// let x = a && b;
171+
/// // ^ last operand
172+
/// ```
173+
///
174+
/// This level is mainly intended to be a stepping-stone towards full MC/DC
175+
/// instrumentation, so it might be removed in the future when MC/DC is
176+
/// sufficiently complete, or if it is making MC/DC changes difficult.
177+
Condition,
178+
/// Instrument for MC/DC. Mostly a superset of condition coverage, but might
163179
/// differ in some corner cases.
164180
Mcdc,
165181
}

compiler/rustc_session/src/options.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ mod desc {
395395
pub const parse_optimization_fuel: &str = "crate=integer";
396396
pub const parse_dump_mono_stats: &str = "`markdown` (default) or `json`";
397397
pub const parse_instrument_coverage: &str = parse_bool;
398-
pub const parse_coverage_options: &str = "`block` | `branch` | `mcdc`";
398+
pub const parse_coverage_options: &str = "`block` | `branch` | `condition` | `mcdc`";
399399
pub const parse_instrument_xray: &str = "either a boolean (`yes`, `no`, `on`, `off`, etc), or a comma separated list of settings: `always` or `never` (mutually exclusive), `ignore-loops`, `instruction-threshold=N`, `skip-entry`, `skip-exit`";
400400
pub const parse_unpretty: &str = "`string` or `string=string`";
401401
pub const parse_treat_err_as_bug: &str = "either no value or a non-negative number";
@@ -961,6 +961,7 @@ mod parse {
961961
match option {
962962
"block" => slot.level = CoverageLevel::Block,
963963
"branch" => slot.level = CoverageLevel::Branch,
964+
"condition" => slot.level = CoverageLevel::Condition,
964965
"mcdc" => slot.level = CoverageLevel::Mcdc,
965966
_ => return false,
966967
}

compiler/rustc_session/src/session.rs

+5
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,11 @@ impl Session {
353353
&& self.opts.unstable_opts.coverage_options.level >= CoverageLevel::Branch
354354
}
355355

356+
pub fn instrument_coverage_condition(&self) -> bool {
357+
self.instrument_coverage()
358+
&& self.opts.unstable_opts.coverage_options.level >= CoverageLevel::Condition
359+
}
360+
356361
pub fn instrument_coverage_mcdc(&self) -> bool {
357362
self.instrument_coverage()
358363
&& self.opts.unstable_opts.coverage_options.level >= CoverageLevel::Mcdc

src/doc/unstable-book/src/compiler-flags/coverage-options.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ This option controls details of the coverage instrumentation performed by
55

66
Multiple options can be passed, separated by commas. Valid options are:
77

8-
- `block`, `branch`, `mcdc`:
8+
- `block`, `branch`, `condition`, `mcdc`:
99
Sets the level of coverage instrumentation.
1010
Setting the level will override any previously-specified level.
1111
- `block` (default):
1212
Blocks in the control-flow graph will be instrumented for coverage.
1313
- `branch`:
1414
In addition to block coverage, also enables branch coverage instrumentation.
15+
- `condition`:
16+
In addition to branch coverage, also instruments some boolean expressions
17+
as branches, even if they are not directly used as branch conditions.
1518
- `mcdc`:
16-
In addition to block and branch coverage, also enables MC/DC instrumentation.
19+
In addition to condition coverage, also enables MC/DC instrumentation.
1720
(Branch coverage instrumentation may differ in some cases.)
+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
Function name: conditions::assign_3_and_or
2+
Raw bytes (69): 0x[01, 01, 07, 07, 11, 09, 0d, 01, 05, 05, 09, 16, 1a, 05, 09, 01, 05, 09, 01, 1c, 01, 00, 2f, 03, 01, 09, 00, 0a, 01, 00, 0d, 00, 0e, 20, 05, 1a, 00, 0d, 00, 0e, 05, 00, 12, 00, 13, 20, 09, 16, 00, 12, 00, 13, 13, 00, 17, 00, 18, 20, 0d, 11, 00, 17, 00, 18, 03, 01, 05, 01, 02]
3+
Number of files: 1
4+
- file 0 => global file 1
5+
Number of expressions: 7
6+
- expression 0 operands: lhs = Expression(1, Add), rhs = Counter(4)
7+
- expression 1 operands: lhs = Counter(2), rhs = Counter(3)
8+
- expression 2 operands: lhs = Counter(0), rhs = Counter(1)
9+
- expression 3 operands: lhs = Counter(1), rhs = Counter(2)
10+
- expression 4 operands: lhs = Expression(5, Sub), rhs = Expression(6, Sub)
11+
- expression 5 operands: lhs = Counter(1), rhs = Counter(2)
12+
- expression 6 operands: lhs = Counter(0), rhs = Counter(1)
13+
Number of file 0 mappings: 9
14+
- Code(Counter(0)) at (prev + 28, 1) to (start + 0, 47)
15+
- Code(Expression(0, Add)) at (prev + 1, 9) to (start + 0, 10)
16+
= ((c2 + c3) + c4)
17+
- Code(Counter(0)) at (prev + 0, 13) to (start + 0, 14)
18+
- Branch { true: Counter(1), false: Expression(6, Sub) } at (prev + 0, 13) to (start + 0, 14)
19+
true = c1
20+
false = (c0 - c1)
21+
- Code(Counter(1)) at (prev + 0, 18) to (start + 0, 19)
22+
- Branch { true: Counter(2), false: Expression(5, Sub) } at (prev + 0, 18) to (start + 0, 19)
23+
true = c2
24+
false = (c1 - c2)
25+
- Code(Expression(4, Add)) at (prev + 0, 23) to (start + 0, 24)
26+
= ((c1 - c2) + (c0 - c1))
27+
- Branch { true: Counter(3), false: Counter(4) } at (prev + 0, 23) to (start + 0, 24)
28+
true = c3
29+
false = c4
30+
- Code(Expression(0, Add)) at (prev + 1, 5) to (start + 1, 2)
31+
= ((c2 + c3) + c4)
32+
33+
Function name: conditions::assign_3_or_and
34+
Raw bytes (73): 0x[01, 01, 09, 05, 07, 0b, 11, 09, 0d, 01, 05, 01, 05, 22, 11, 01, 05, 22, 11, 01, 05, 09, 01, 17, 01, 00, 2f, 03, 01, 09, 00, 0a, 01, 00, 0d, 00, 0e, 20, 05, 22, 00, 0d, 00, 0e, 22, 00, 12, 00, 13, 20, 1e, 11, 00, 12, 00, 13, 1e, 00, 17, 00, 18, 20, 09, 0d, 00, 17, 00, 18, 03, 01, 05, 01, 02]
35+
Number of files: 1
36+
- file 0 => global file 1
37+
Number of expressions: 9
38+
- expression 0 operands: lhs = Counter(1), rhs = Expression(1, Add)
39+
- expression 1 operands: lhs = Expression(2, Add), rhs = Counter(4)
40+
- expression 2 operands: lhs = Counter(2), rhs = Counter(3)
41+
- expression 3 operands: lhs = Counter(0), rhs = Counter(1)
42+
- expression 4 operands: lhs = Counter(0), rhs = Counter(1)
43+
- expression 5 operands: lhs = Expression(8, Sub), rhs = Counter(4)
44+
- expression 6 operands: lhs = Counter(0), rhs = Counter(1)
45+
- expression 7 operands: lhs = Expression(8, Sub), rhs = Counter(4)
46+
- expression 8 operands: lhs = Counter(0), rhs = Counter(1)
47+
Number of file 0 mappings: 9
48+
- Code(Counter(0)) at (prev + 23, 1) to (start + 0, 47)
49+
- Code(Expression(0, Add)) at (prev + 1, 9) to (start + 0, 10)
50+
= (c1 + ((c2 + c3) + c4))
51+
- Code(Counter(0)) at (prev + 0, 13) to (start + 0, 14)
52+
- Branch { true: Counter(1), false: Expression(8, Sub) } at (prev + 0, 13) to (start + 0, 14)
53+
true = c1
54+
false = (c0 - c1)
55+
- Code(Expression(8, Sub)) at (prev + 0, 18) to (start + 0, 19)
56+
= (c0 - c1)
57+
- Branch { true: Expression(7, Sub), false: Counter(4) } at (prev + 0, 18) to (start + 0, 19)
58+
true = ((c0 - c1) - c4)
59+
false = c4
60+
- Code(Expression(7, Sub)) at (prev + 0, 23) to (start + 0, 24)
61+
= ((c0 - c1) - c4)
62+
- Branch { true: Counter(2), false: Counter(3) } at (prev + 0, 23) to (start + 0, 24)
63+
true = c2
64+
false = c3
65+
- Code(Expression(0, Add)) at (prev + 1, 5) to (start + 1, 2)
66+
= (c1 + ((c2 + c3) + c4))
67+
68+
Function name: conditions::assign_and
69+
Raw bytes (51): 0x[01, 01, 04, 07, 0e, 09, 0d, 01, 05, 01, 05, 07, 01, 0d, 01, 00, 21, 03, 01, 09, 00, 0a, 01, 00, 0d, 00, 0e, 20, 05, 0e, 00, 0d, 00, 0e, 05, 00, 12, 00, 13, 20, 09, 0d, 00, 12, 00, 13, 03, 01, 05, 01, 02]
70+
Number of files: 1
71+
- file 0 => global file 1
72+
Number of expressions: 4
73+
- expression 0 operands: lhs = Expression(1, Add), rhs = Expression(3, Sub)
74+
- expression 1 operands: lhs = Counter(2), rhs = Counter(3)
75+
- expression 2 operands: lhs = Counter(0), rhs = Counter(1)
76+
- expression 3 operands: lhs = Counter(0), rhs = Counter(1)
77+
Number of file 0 mappings: 7
78+
- Code(Counter(0)) at (prev + 13, 1) to (start + 0, 33)
79+
- Code(Expression(0, Add)) at (prev + 1, 9) to (start + 0, 10)
80+
= ((c2 + c3) + (c0 - c1))
81+
- Code(Counter(0)) at (prev + 0, 13) to (start + 0, 14)
82+
- Branch { true: Counter(1), false: Expression(3, Sub) } at (prev + 0, 13) to (start + 0, 14)
83+
true = c1
84+
false = (c0 - c1)
85+
- Code(Counter(1)) at (prev + 0, 18) to (start + 0, 19)
86+
- Branch { true: Counter(2), false: Counter(3) } at (prev + 0, 18) to (start + 0, 19)
87+
true = c2
88+
false = c3
89+
- Code(Expression(0, Add)) at (prev + 1, 5) to (start + 1, 2)
90+
= ((c2 + c3) + (c0 - c1))
91+
92+
Function name: conditions::assign_or
93+
Raw bytes (51): 0x[01, 01, 04, 07, 0d, 05, 09, 01, 05, 01, 05, 07, 01, 12, 01, 00, 20, 03, 01, 09, 00, 0a, 01, 00, 0d, 00, 0e, 20, 05, 0e, 00, 0d, 00, 0e, 0e, 00, 12, 00, 13, 20, 09, 0d, 00, 12, 00, 13, 03, 01, 05, 01, 02]
94+
Number of files: 1
95+
- file 0 => global file 1
96+
Number of expressions: 4
97+
- expression 0 operands: lhs = Expression(1, Add), rhs = Counter(3)
98+
- expression 1 operands: lhs = Counter(1), rhs = Counter(2)
99+
- expression 2 operands: lhs = Counter(0), rhs = Counter(1)
100+
- expression 3 operands: lhs = Counter(0), rhs = Counter(1)
101+
Number of file 0 mappings: 7
102+
- Code(Counter(0)) at (prev + 18, 1) to (start + 0, 32)
103+
- Code(Expression(0, Add)) at (prev + 1, 9) to (start + 0, 10)
104+
= ((c1 + c2) + c3)
105+
- Code(Counter(0)) at (prev + 0, 13) to (start + 0, 14)
106+
- Branch { true: Counter(1), false: Expression(3, Sub) } at (prev + 0, 13) to (start + 0, 14)
107+
true = c1
108+
false = (c0 - c1)
109+
- Code(Expression(3, Sub)) at (prev + 0, 18) to (start + 0, 19)
110+
= (c0 - c1)
111+
- Branch { true: Counter(2), false: Counter(3) } at (prev + 0, 18) to (start + 0, 19)
112+
true = c2
113+
false = c3
114+
- Code(Expression(0, Add)) at (prev + 1, 5) to (start + 1, 2)
115+
= ((c1 + c2) + c3)
116+
117+
Function name: conditions::foo
118+
Raw bytes (9): 0x[01, 01, 00, 01, 01, 21, 01, 02, 02]
119+
Number of files: 1
120+
- file 0 => global file 1
121+
Number of expressions: 0
122+
Number of file 0 mappings: 1
123+
- Code(Counter(0)) at (prev + 33, 1) to (start + 2, 2)
124+
125+
Function name: conditions::func_call
126+
Raw bytes (39): 0x[01, 01, 03, 01, 05, 0b, 02, 09, 0d, 05, 01, 25, 01, 01, 0a, 20, 05, 02, 01, 09, 00, 0a, 05, 00, 0e, 00, 0f, 20, 09, 0d, 00, 0e, 00, 0f, 07, 01, 01, 00, 02]
127+
Number of files: 1
128+
- file 0 => global file 1
129+
Number of expressions: 3
130+
- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
131+
- expression 1 operands: lhs = Expression(2, Add), rhs = Expression(0, Sub)
132+
- expression 2 operands: lhs = Counter(2), rhs = Counter(3)
133+
Number of file 0 mappings: 5
134+
- Code(Counter(0)) at (prev + 37, 1) to (start + 1, 10)
135+
- Branch { true: Counter(1), false: Expression(0, Sub) } at (prev + 1, 9) to (start + 0, 10)
136+
true = c1
137+
false = (c0 - c1)
138+
- Code(Counter(1)) at (prev + 0, 14) to (start + 0, 15)
139+
- Branch { true: Counter(2), false: Counter(3) } at (prev + 0, 14) to (start + 0, 15)
140+
true = c2
141+
false = c3
142+
- Code(Expression(1, Add)) at (prev + 1, 1) to (start + 0, 2)
143+
= ((c2 + c3) + (c0 - c1))
144+
145+
Function name: conditions::simple_assign
146+
Raw bytes (9): 0x[01, 01, 00, 01, 01, 08, 01, 03, 02]
147+
Number of files: 1
148+
- file 0 => global file 1
149+
Number of expressions: 0
150+
Number of file 0 mappings: 1
151+
- Code(Counter(0)) at (prev + 8, 1) to (start + 3, 2)
152+

0 commit comments

Comments
 (0)