Skip to content

Commit 81a8f8f

Browse files
authored
Introduce a kani macro for checking coverage (rust-lang#2011)
1 parent 1d94b6b commit 81a8f8f

File tree

14 files changed

+365
-15
lines changed

14 files changed

+365
-15
lines changed

kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,47 @@ fn matches_function(tcx: TyCtxt, instance: Instance, attr_name: &str) -> bool {
4646
false
4747
}
4848

49+
/// A hook for Kani's `cover` function (declared in `library/kani/src/lib.rs`).
50+
/// The function takes two arguments: a condition expression (bool) and a
51+
/// message (&'static str).
52+
/// The hook codegens the function as a cover property that checks whether the
53+
/// condition is satisfiable. Unlike assertions, cover properties currently do
54+
/// not have an impact on verification success or failure. See
55+
/// <https://github.com/model-checking/kani/blob/main/rfc/src/rfcs/0003-cover-statement.md>
56+
/// for more details.
57+
struct Cover;
58+
impl<'tcx> GotocHook<'tcx> for Cover {
59+
fn hook_applies(&self, tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> bool {
60+
matches_function(tcx, instance, "KaniCover")
61+
}
62+
63+
fn handle(
64+
&self,
65+
tcx: &mut GotocCtx<'tcx>,
66+
_instance: Instance<'tcx>,
67+
mut fargs: Vec<Expr>,
68+
_assign_to: Place<'tcx>,
69+
target: Option<BasicBlock>,
70+
span: Option<Span>,
71+
) -> Stmt {
72+
assert_eq!(fargs.len(), 2);
73+
let cond = fargs.remove(0).cast_to(Type::bool());
74+
let msg = fargs.remove(0);
75+
let msg = tcx.extract_const_message(&msg).unwrap();
76+
let target = target.unwrap();
77+
let caller_loc = tcx.codegen_caller_span(&span);
78+
let loc = tcx.codegen_span_option(span);
79+
80+
Stmt::block(
81+
vec![
82+
tcx.codegen_cover(cond, &msg, span),
83+
Stmt::goto(tcx.current_fn().find_label(&target), caller_loc),
84+
],
85+
loc,
86+
)
87+
}
88+
}
89+
4990
struct ExpectFail;
5091
impl<'tcx> GotocHook<'tcx> for ExpectFail {
5192
fn hook_applies(&self, tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> bool {
@@ -369,6 +410,7 @@ pub fn fn_hooks<'tcx>() -> GotocHooks<'tcx> {
369410
Rc::new(Panic),
370411
Rc::new(Assume),
371412
Rc::new(Assert),
413+
Rc::new(Cover),
372414
Rc::new(ExpectFail),
373415
Rc::new(Nondet),
374416
Rc::new(RustAlloc),

kani-driver/src/cbmc_output_parser.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,17 @@ pub struct PropertyId {
8282
}
8383

8484
impl Property {
85+
const COVER_PROPERTY_CLASS: &str = "cover";
86+
8587
pub fn property_class(&self) -> String {
8688
self.property_id.class.clone()
8789
}
8890

91+
/// Returns true if this is a cover property
92+
pub fn is_cover_property(&self) -> bool {
93+
self.property_id.class == Self::COVER_PROPERTY_CLASS
94+
}
95+
8996
pub fn property_name(&self) -> String {
9097
let class = &self.property_id.class;
9198
let id = self.property_id.id;
@@ -293,18 +300,22 @@ impl std::fmt::Display for TraceData {
293300
#[serde(rename_all = "UPPERCASE")]
294301
pub enum CheckStatus {
295302
Failure,
303+
Satisfied, // for cover properties only
296304
Success,
297305
Undetermined,
298306
Unreachable,
307+
Unsatisfiable, // for cover properties only
299308
}
300309

301310
impl std::fmt::Display for CheckStatus {
302311
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
303312
let check_str = match self {
313+
CheckStatus::Satisfied => style("SATISFIED").green(),
304314
CheckStatus::Success => style("SUCCESS").green(),
305315
CheckStatus::Failure => style("FAILURE").red(),
306316
CheckStatus::Unreachable => style("UNREACHABLE").yellow(),
307317
CheckStatus::Undetermined => style("UNDETERMINED").yellow(),
318+
CheckStatus::Unsatisfiable => style("UNSATISFIABLE").yellow(),
308319
};
309320
write!(f, "{check_str}")
310321
}

kani-driver/src/cbmc_property_renderer.rs

Lines changed: 93 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -231,15 +231,26 @@ fn format_item_terse(_item: &ParserItem) -> Option<String> {
231231
/// This could be split into two functions for clarity, but at the moment
232232
/// it uses the flag `show_checks` which depends on the output format.
233233
///
234+
/// This function reports the results of normal checks (e.g. assertions and
235+
/// arithmetic overflow checks) and cover properties (specified using the
236+
/// `kani::cover` macro) separately. Cover properties currently do not impact
237+
/// the overall verification success or failure.
238+
///
234239
/// TODO: We could `write!` to `result_str` instead
235240
/// <https://github.com/model-checking/kani/issues/1480>
236241
pub fn format_result(properties: &Vec<Property>, show_checks: bool) -> String {
237242
let mut result_str = String::new();
238-
let mut number_tests_failed = 0;
239-
let mut number_tests_unreachable = 0;
240-
let mut number_tests_undetermined = 0;
243+
let mut number_checks_failed = 0;
244+
let mut number_checks_unreachable = 0;
245+
let mut number_checks_undetermined = 0;
241246
let mut failed_tests: Vec<&Property> = vec![];
242247

248+
// cover checks
249+
let mut number_covers_satisfied = 0;
250+
let mut number_covers_undetermined = 0;
251+
let mut number_covers_unreachable = 0;
252+
let mut number_covers_unsatisfiable = 0;
253+
243254
let mut index = 1;
244255

245256
if show_checks {
@@ -254,14 +265,30 @@ pub fn format_result(properties: &Vec<Property>, show_checks: bool) -> String {
254265

255266
match status {
256267
CheckStatus::Failure => {
257-
number_tests_failed += 1;
268+
number_checks_failed += 1;
258269
failed_tests.push(prop);
259270
}
260271
CheckStatus::Undetermined => {
261-
number_tests_undetermined += 1;
272+
if prop.is_cover_property() {
273+
number_covers_undetermined += 1;
274+
} else {
275+
number_checks_undetermined += 1;
276+
}
262277
}
263278
CheckStatus::Unreachable => {
264-
number_tests_unreachable += 1;
279+
if prop.is_cover_property() {
280+
number_covers_unreachable += 1;
281+
} else {
282+
number_checks_unreachable += 1;
283+
}
284+
}
285+
CheckStatus::Satisfied => {
286+
assert!(prop.is_cover_property());
287+
number_covers_satisfied += 1;
288+
}
289+
CheckStatus::Unsatisfiable => {
290+
assert!(prop.is_cover_property());
291+
number_covers_unsatisfiable += 1;
265292
}
266293
_ => (),
267294
}
@@ -291,16 +318,23 @@ pub fn format_result(properties: &Vec<Property>, show_checks: bool) -> String {
291318
result_str.push_str("\nVERIFICATION RESULT:");
292319
}
293320

294-
let summary = format!("\n ** {number_tests_failed} of {} failed", properties.len());
321+
let number_cover_properties = number_covers_satisfied
322+
+ number_covers_unreachable
323+
+ number_covers_unsatisfiable
324+
+ number_covers_undetermined;
325+
326+
let number_properties = properties.len() - number_cover_properties;
327+
328+
let summary = format!("\n ** {number_checks_failed} of {} failed", number_properties);
295329
result_str.push_str(&summary);
296330

297331
let mut other_status = Vec::<String>::new();
298-
if number_tests_undetermined > 0 {
299-
let undetermined_str = format!("{number_tests_undetermined} undetermined");
332+
if number_checks_undetermined > 0 {
333+
let undetermined_str = format!("{number_checks_undetermined} undetermined");
300334
other_status.push(undetermined_str);
301335
}
302-
if number_tests_unreachable > 0 {
303-
let unreachable_str = format!("{number_tests_unreachable} unreachable");
336+
if number_checks_unreachable > 0 {
337+
let unreachable_str = format!("{number_checks_unreachable} unreachable");
304338
other_status.push(unreachable_str);
305339
}
306340
if !other_status.is_empty() {
@@ -310,13 +344,38 @@ pub fn format_result(properties: &Vec<Property>, show_checks: bool) -> String {
310344
}
311345
result_str.push('\n');
312346

347+
if number_cover_properties > 0 {
348+
// Print a summary line for cover properties
349+
let summary = format!(
350+
"\n ** {number_covers_satisfied} of {} cover properties satisfied",
351+
number_cover_properties
352+
);
353+
result_str.push_str(&summary);
354+
let mut other_status = Vec::<String>::new();
355+
if number_covers_undetermined > 0 {
356+
let undetermined_str = format!("{number_covers_undetermined} undetermined");
357+
other_status.push(undetermined_str);
358+
}
359+
if number_covers_unreachable > 0 {
360+
let unreachable_str = format!("{number_covers_unreachable} unreachable");
361+
other_status.push(unreachable_str);
362+
}
363+
if !other_status.is_empty() {
364+
result_str.push_str(" (");
365+
result_str.push_str(&other_status.join(","));
366+
result_str.push(')');
367+
}
368+
result_str.push('\n');
369+
result_str.push('\n');
370+
}
371+
313372
for prop in failed_tests {
314373
let failure_message = build_failure_message(prop.description.clone(), &prop.trace.clone());
315374
result_str.push_str(&failure_message);
316375
}
317376

318377
let verification_result =
319-
if number_tests_failed == 0 { style("SUCCESSFUL").green() } else { style("FAILED").red() };
378+
if number_checks_failed == 0 { style("SUCCESSFUL").green() } else { style("FAILED").red() };
320379
let overall_result = format!("\nVERIFICATION:- {verification_result}\n");
321380
result_str.push_str(&overall_result);
322381

@@ -413,7 +472,7 @@ pub fn postprocess_result(properties: Vec<Property>, extra_ptr_checks: bool) ->
413472
let (properties_without_reachs, reach_checks) = filter_reach_checks(properties_with_undefined);
414473
// Filter out successful sanity checks introduced during compilation
415474
let properties_without_sanity_checks = filter_sanity_checks(properties_without_reachs);
416-
// Annotate properties with the results from reachability checks
475+
// Annotate properties with the results of reachability checks
417476
let properties_annotated =
418477
annotate_properties_with_reach_results(properties_without_sanity_checks, reach_checks);
419478
// Remove reachability check IDs from regular property descriptions
@@ -429,7 +488,9 @@ pub fn postprocess_result(properties: Vec<Property>, extra_ptr_checks: bool) ->
429488
|| has_failed_unwinding_asserts
430489
|| has_reachable_undefined_functions;
431490

432-
update_properties_with_reach_status(properties_filtered, has_fundamental_failures)
491+
let updated_properties =
492+
update_properties_with_reach_status(properties_filtered, has_fundamental_failures);
493+
update_results_of_cover_checks(updated_properties)
433494
}
434495

435496
/// Determines if there is property with status `FAILURE` and the given description
@@ -495,7 +556,7 @@ fn get_readable_description(property: &Property) -> String {
495556
original
496557
}
497558

498-
/// Performs a last pass to update all properties as follows:
559+
/// Performs a pass to update all properties as follows:
499560
/// 1. Descriptions are replaced with more readable ones.
500561
/// 2. If there were failures that made the verification result unreliable
501562
/// (e.g., a reachable unsupported construct), changes all `SUCCESS` results
@@ -525,6 +586,23 @@ fn update_properties_with_reach_status(
525586
properties
526587
}
527588

589+
/// Update the results of cover properties.
590+
/// We encode cover(cond) as assert(!cond), so if the assertion
591+
/// fails, then the cover property is satisfied and vice versa.
592+
/// - SUCCESS -> UNSATISFIABLE
593+
/// - FAILURE -> SATISFIED
594+
fn update_results_of_cover_checks(mut properties: Vec<Property>) -> Vec<Property> {
595+
for prop in properties.iter_mut() {
596+
if prop.is_cover_property() {
597+
if prop.status == CheckStatus::Success {
598+
prop.status = CheckStatus::Unsatisfiable;
599+
} else if prop.status == CheckStatus::Failure {
600+
prop.status = CheckStatus::Satisfied;
601+
}
602+
}
603+
}
604+
properties
605+
}
528606
/// Some Kani-generated asserts have a unique ID in their description of the form:
529607
/// ```text
530608
/// [KANI_CHECK_ID_<crate-fn-name>_<index>]

library/kani/src/lib.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,28 @@ pub const fn assert(_cond: bool, _msg: &'static str) {
6464
}
6565
}
6666

67+
/// Creates a cover property with the specified condition and message.
68+
///
69+
/// # Example:
70+
///
71+
/// ```rust
72+
/// kani::cover(slice.len() == 0, "The slice may have a length of 0");
73+
/// ```
74+
///
75+
/// A cover property checks if there is at least one execution that satisfies
76+
/// the specified condition at the location in which the function is called.
77+
///
78+
/// Cover properties are reported as:
79+
/// - SATISFIED: if Kani found an execution that satisfies the condition
80+
/// - UNSATISFIABLE: if Kani proved that the condition cannot be satisfied
81+
///
82+
/// This function is called by the [`cover!`] macro. The macro is more
83+
/// convenient to use.
84+
///
85+
#[inline(never)]
86+
#[rustc_diagnostic_item = "KaniCover"]
87+
pub fn cover(_cond: bool, _msg: &'static str) {}
88+
6789
/// This creates an symbolic *valid* value of type `T`. You can assign the return value of this
6890
/// function to a variable that you want to make symbolic.
6991
///
@@ -138,5 +160,54 @@ pub const fn panic(message: &'static str) -> ! {
138160
panic!("{}", message)
139161
}
140162

163+
/// A macro to check if a condition is satisfiable at a specific location in the
164+
/// code.
165+
///
166+
/// # Example 1:
167+
///
168+
/// ```rust
169+
/// let mut set: BTreeSet<i32> = BTreeSet::new();
170+
/// set.insert(kani::any());
171+
/// set.insert(kani::any());
172+
/// // check if the set can end up with a single element (if both elements
173+
/// // inserted were the same)
174+
/// kani::cover!(set.len() == 1);
175+
/// ```
176+
/// The macro can also be called without any arguments to check if a location is
177+
/// reachable.
178+
///
179+
/// # Example 2:
180+
///
181+
/// ```rust
182+
/// match e {
183+
/// MyEnum::A => { /* .. */ }
184+
/// MyEnum::B => {
185+
/// // make sure the `MyEnum::B` variant is possible
186+
/// kani::cover!();
187+
/// // ..
188+
/// }
189+
/// }
190+
/// ```
191+
///
192+
/// A custom message can also be passed to the macro.
193+
///
194+
/// # Example 3:
195+
///
196+
/// ```rust
197+
/// kani::cover!(x > y, "x can be greater than y")
198+
/// ```
199+
#[macro_export]
200+
macro_rules! cover {
201+
() => {
202+
kani::cover(true, "cover location");
203+
};
204+
($cond:expr $(,)?) => {
205+
kani::cover($cond, concat!("cover condition: ", stringify!($cond)));
206+
};
207+
($cond:expr, $msg:literal) => {
208+
kani::cover($cond, $msg);
209+
};
210+
}
211+
141212
/// Kani proc macros must be in a separate crate
142213
pub use kani_macros::*;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Status: UNSATISFIABLE\
2+
Description: "cover location"\
3+
main.rs:29:23 in function cover_overconstrained
4+
5+
** 0 of 1 cover properties satisfied
6+
7+
VERIFICATION:- SUCCESSFUL

0 commit comments

Comments
 (0)