Skip to content

Commit db42ee9

Browse files
adpaco-awsjaisnan
andauthored
Line coverage reports (rust-lang#2609)
Co-authored-by: jaisnan <[email protected]> Co-authored-by: Jaisurya Nanduri <[email protected]>
1 parent a2a1e85 commit db42ee9

File tree

75 files changed

+967
-17
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+967
-17
lines changed

kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use tracing::debug;
2929
/// Classifies the type of CBMC `assert`, as different assertions can have different semantics (e.g. cover)
3030
///
3131
/// Each property class should justify its existence with a note about the special handling it recieves.
32-
#[derive(Debug, Clone, EnumString, AsRefStr)]
32+
#[derive(Debug, Clone, EnumString, AsRefStr, PartialEq)]
3333
#[strum(serialize_all = "snake_case")]
3434
pub enum PropertyClass {
3535
/// Overflow panics that can be generated by Intrisics.
@@ -45,6 +45,19 @@ pub enum PropertyClass {
4545
///
4646
/// SPECIAL BEHAVIOR: "Errors" for this type of assertion just mean "reachable" not failure.
4747
Cover,
48+
/// The class of checks used for code coverage instrumentation. Only needed
49+
/// when working on coverage-related features.
50+
///
51+
/// Do not mistake with `Cover`, they are different:
52+
/// - `CodeCoverage` checks have a fixed condition (`false`) and description.
53+
/// - `CodeCoverage` checks are filtered out from verification results and
54+
/// postprocessed to build coverage reports.
55+
/// - `Cover` checks can be added by users (using the `kani::cover` macro),
56+
/// while `CodeCoverage` checks are not exposed to users (i.e., they are
57+
/// automatically added if running with the coverage option).
58+
///
59+
/// SPECIAL BEHAVIOR: "Errors" for this type of assertion just mean "reachable" not failure.
60+
CodeCoverage,
4861
/// Ordinary (Rust) assertions and panics.
4962
///
5063
/// SPECIAL BEHAVIOR: These assertion failures should be observable during normal execution of Rust code.
@@ -134,6 +147,21 @@ impl<'tcx> GotocCtx<'tcx> {
134147
self.codegen_assert(cond.not(), PropertyClass::Cover, msg, loc)
135148
}
136149

150+
/// Generate a cover statement for code coverage reports.
151+
pub fn codegen_coverage(&self, span: Span) -> Stmt {
152+
let loc = self.codegen_caller_span(&Some(span));
153+
// Should use Stmt::cover, but currently this doesn't work with CBMC
154+
// unless it is run with '--cover cover' (see
155+
// https://github.com/diffblue/cbmc/issues/6613). So for now use
156+
// `assert(false)`.
157+
self.codegen_assert(
158+
Expr::bool_false(),
159+
PropertyClass::CodeCoverage,
160+
"code coverage for location",
161+
loc,
162+
)
163+
}
164+
137165
// The above represent the basic operations we can perform w.r.t. assert/assume/cover
138166
// Below are various helper functions for constructing the above more easily.
139167

kani-compiler/src/codegen_cprover_gotoc/codegen/block.rs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,55 @@ impl<'tcx> GotocCtx<'tcx> {
1616
debug!(?bb, "Codegen basicblock");
1717
self.current_fn_mut().set_current_bb(bb);
1818
let label: String = self.current_fn().find_label(&bb);
19+
let check_coverage = self.queries.check_coverage;
1920
// the first statement should be labelled. if there is no statements, then the
2021
// terminator should be labelled.
2122
match bbd.statements.len() {
2223
0 => {
2324
let term = bbd.terminator();
2425
let tcode = self.codegen_terminator(term);
25-
self.current_fn_mut().push_onto_block(tcode.with_label(label));
26+
// When checking coverage, the `coverage` check should be
27+
// labelled instead.
28+
if check_coverage {
29+
let span = term.source_info.span;
30+
let cover = self.codegen_coverage(span);
31+
self.current_fn_mut().push_onto_block(cover.with_label(label));
32+
self.current_fn_mut().push_onto_block(tcode);
33+
} else {
34+
self.current_fn_mut().push_onto_block(tcode.with_label(label));
35+
}
2636
}
2737
_ => {
2838
let stmt = &bbd.statements[0];
2939
let scode = self.codegen_statement(stmt);
30-
self.current_fn_mut().push_onto_block(scode.with_label(label));
40+
// When checking coverage, the `coverage` check should be
41+
// labelled instead.
42+
if check_coverage {
43+
let span = stmt.source_info.span;
44+
let cover = self.codegen_coverage(span);
45+
self.current_fn_mut().push_onto_block(cover.with_label(label));
46+
self.current_fn_mut().push_onto_block(scode);
47+
} else {
48+
self.current_fn_mut().push_onto_block(scode.with_label(label));
49+
}
3150

3251
for s in &bbd.statements[1..] {
52+
if check_coverage {
53+
let span = s.source_info.span;
54+
let cover = self.codegen_coverage(span);
55+
self.current_fn_mut().push_onto_block(cover);
56+
}
3357
let stmt = self.codegen_statement(s);
3458
self.current_fn_mut().push_onto_block(stmt);
3559
}
36-
let term = self.codegen_terminator(bbd.terminator());
37-
self.current_fn_mut().push_onto_block(term);
60+
let term = bbd.terminator();
61+
if check_coverage {
62+
let span = term.source_info.span;
63+
let cover = self.codegen_coverage(span);
64+
self.current_fn_mut().push_onto_block(cover);
65+
}
66+
let tcode = self.codegen_terminator(term);
67+
self.current_fn_mut().push_onto_block(tcode);
3868
}
3969
}
4070
self.current_fn_mut().reset_current_bb();

kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ impl<'tcx> GotocCtx<'tcx> {
188188
let a = fargs.remove(0);
189189
let b = fargs.remove(0);
190190
let div_does_not_overflow = self.div_does_not_overflow(a.clone(), b.clone());
191-
let div_overflow_check = self.codegen_assert(
191+
let div_overflow_check = self.codegen_assert_assume(
192192
div_does_not_overflow,
193193
PropertyClass::ArithmeticOverflow,
194194
format!("attempt to compute {} which would overflow", intrinsic).as_str(),

kani-compiler/src/kani_compiler.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ impl Callbacks for KaniCompiler {
349349
let queries = &mut (*self.queries.lock().unwrap());
350350
queries.emit_vtable_restrictions = matches.get_flag(parser::RESTRICT_FN_PTRS);
351351
queries.check_assertion_reachability = matches.get_flag(parser::ASSERTION_REACH_CHECKS);
352+
queries.check_coverage = matches.get_flag(parser::COVERAGE_CHECKS);
352353
queries.output_pretty_json = matches.get_flag(parser::PRETTY_OUTPUT_FILES);
353354
queries.ignore_global_asm = matches.get_flag(parser::IGNORE_GLOBAL_ASM);
354355
queries.write_json_symtab =
@@ -364,6 +365,7 @@ impl Callbacks for KaniCompiler {
364365
{
365366
queries.stubbing_enabled = true;
366367
}
368+
367369
debug!(?queries, "config end");
368370
}
369371
}

kani-compiler/src/kani_queries/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub enum ReachabilityType {
2828
#[derive(Debug, Default, Clone)]
2929
pub struct QueryDb {
3030
pub check_assertion_reachability: bool,
31+
pub check_coverage: bool,
3132
pub emit_vtable_restrictions: bool,
3233
pub output_pretty_json: bool,
3334
pub ignore_global_asm: bool,

kani-compiler/src/parser.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ pub const RESTRICT_FN_PTRS: &str = "restrict-vtable-fn-ptrs";
2828
/// Option name used to enable assertion reachability checks.
2929
pub const ASSERTION_REACH_CHECKS: &str = "assertion-reach-checks";
3030

31+
/// Option name used to enable coverage checks.
32+
pub const COVERAGE_CHECKS: &str = "coverage-checks";
33+
3134
/// Option name used to use json pretty-print for output files.
3235
pub const PRETTY_OUTPUT_FILES: &str = "pretty-json-files";
3336

@@ -98,6 +101,12 @@ pub fn parser() -> Command {
98101
.help("Check the reachability of every assertion.")
99102
.action(ArgAction::SetTrue),
100103
)
104+
.arg(
105+
Arg::new(COVERAGE_CHECKS)
106+
.long(COVERAGE_CHECKS)
107+
.help("Check the reachability of every statement.")
108+
.action(ArgAction::SetTrue),
109+
)
101110
.arg(
102111
Arg::new(REACHABILITY)
103112
.long(REACHABILITY)

kani-driver/src/args/common.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ pub enum UnstableFeatures {
4444
ConcretePlayback,
4545
/// Enable Kani's unstable async library.
4646
AsyncLib,
47+
/// Enable line coverage instrumentation/reports
48+
LineCoverage,
4749
}
4850

4951
impl ValidateArgs for CommonArgs {

kani-driver/src/args/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,10 @@ pub struct VerificationArgs {
300300
)]
301301
pub enable_stubbing: bool,
302302

303+
/// Enable Kani coverage output alongside verification result
304+
#[arg(long, hide_short_help = true)]
305+
pub coverage: bool,
306+
303307
/// Arguments to pass down to Cargo
304308
#[command(flatten)]
305309
pub cargo: CargoCommonArgs,
@@ -640,6 +644,16 @@ impl ValidateArgs for VerificationArgs {
640644
}
641645
}
642646

647+
if self.coverage
648+
&& !self.common_args.unstable_features.contains(&UnstableFeatures::LineCoverage)
649+
{
650+
return Err(Error::raw(
651+
ErrorKind::MissingRequiredArgument,
652+
"The `--coverage` argument is unstable and requires `-Z \
653+
line-coverage` to be used.",
654+
));
655+
}
656+
643657
Ok(())
644658
}
645659
}

kani-driver/src/call_cbmc.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::args::{OutputFormat, VerificationArgs};
1313
use crate::cbmc_output_parser::{
1414
extract_results, process_cbmc_output, CheckStatus, ParserItem, Property, VerificationOutput,
1515
};
16-
use crate::cbmc_property_renderer::{format_result, kani_cbmc_output_filter};
16+
use crate::cbmc_property_renderer::{format_coverage, format_result, kani_cbmc_output_filter};
1717
use crate::session::KaniSession;
1818

1919
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -303,14 +303,23 @@ impl VerificationResult {
303303
}
304304
}
305305

306-
pub fn render(&self, output_format: &OutputFormat, should_panic: bool) -> String {
306+
pub fn render(
307+
&self,
308+
output_format: &OutputFormat,
309+
should_panic: bool,
310+
coverage_mode: bool,
311+
) -> String {
307312
match &self.results {
308313
Ok(results) => {
309314
let status = self.status;
310315
let failed_properties = self.failed_properties;
311316
let show_checks = matches!(output_format, OutputFormat::Regular);
312-
let mut result =
313-
format_result(results, status, should_panic, failed_properties, show_checks);
317+
318+
let mut result = if coverage_mode {
319+
format_coverage(results, status, should_panic, failed_properties, show_checks)
320+
} else {
321+
format_result(results, status, should_panic, failed_properties, show_checks)
322+
};
314323
writeln!(result, "Verification Time: {}s", self.runtime.as_secs_f32()).unwrap();
315324
result
316325
}

kani-driver/src/call_single_file.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ impl KaniSession {
9696
flags.push("--enable-stubbing".into());
9797
}
9898

99+
if self.args.coverage {
100+
flags.push("--coverage-checks".into());
101+
}
102+
99103
flags.extend(
100104
self.args
101105
.common_args

kani-driver/src/cbmc_output_parser.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,17 @@ pub struct PropertyId {
9696

9797
impl Property {
9898
const COVER_PROPERTY_CLASS: &str = "cover";
99+
const COVERAGE_PROPERTY_CLASS: &str = "code_coverage";
99100

100101
pub fn property_class(&self) -> String {
101102
self.property_id.class.clone()
102103
}
103104

105+
// Returns true if this is a code_coverage check
106+
pub fn is_code_coverage_property(&self) -> bool {
107+
self.property_id.class == Self::COVERAGE_PROPERTY_CLASS
108+
}
109+
104110
/// Returns true if this is a cover property
105111
pub fn is_cover_property(&self) -> bool {
106112
self.property_id.class == Self::COVER_PROPERTY_CLASS
@@ -322,18 +328,22 @@ impl std::fmt::Display for TraceData {
322328
#[serde(rename_all = "UPPERCASE")]
323329
pub enum CheckStatus {
324330
Failure,
325-
Satisfied, // for cover properties only
331+
Covered, // for `code_coverage` properties only
332+
Satisfied, // for `cover` properties only
326333
Success,
327334
Undetermined,
328335
Unreachable,
329-
Unsatisfiable, // for cover properties only
336+
Uncovered, // for `code_coverage` properties only
337+
Unsatisfiable, // for `cover` properties only
330338
}
331339

332340
impl std::fmt::Display for CheckStatus {
333341
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
334342
let check_str = match self {
335343
CheckStatus::Satisfied => style("SATISFIED").green(),
336344
CheckStatus::Success => style("SUCCESS").green(),
345+
CheckStatus::Covered => style("COVERED").green(),
346+
CheckStatus::Uncovered => style("UNCOVERED").red(),
337347
CheckStatus::Failure => style("FAILURE").red(),
338348
CheckStatus::Unreachable => style("UNREACHABLE").yellow(),
339349
CheckStatus::Undetermined => style("UNDETERMINED").yellow(),

0 commit comments

Comments
 (0)