Skip to content

Commit 46fe1e4

Browse files
Add reachability checks for cover properties (rust-lang#2024)
Co-authored-by: Celina G. Val <[email protected]>
1 parent 5cc7355 commit 46fe1e4

File tree

10 files changed

+96
-65
lines changed

10 files changed

+96
-65
lines changed

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

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use crate::codegen_cprover_gotoc::GotocCtx;
2222
use cbmc::goto_program::{Expr, Location, Stmt, Type};
2323
use cbmc::InternedString;
24+
use kani_queries::UserInput;
2425
use rustc_span::Span;
2526
use std::convert::AsRef;
2627
use strum_macros::{AsRefStr, EnumString};
@@ -59,6 +60,9 @@ pub enum PropertyClass {
5960
///
6061
/// SPECIAL BEHAVIOR: None TODO: Why should this exist?
6162
FiniteCheck,
63+
/// Checks added by Kani compiler to determine whether a property (e.g.
64+
/// `PropertyClass::Assertion` or `PropertyClass:Cover`) is reachable
65+
ReachabilityCheck,
6266
/// Checks added by Kani compiler to detect safety conditions violation.
6367
/// E.g., things that trigger UB or unstable behavior.
6468
///
@@ -134,9 +138,41 @@ impl<'tcx> GotocCtx<'tcx> {
134138
// The above represent the basic operations we can perform w.r.t. assert/assume/cover
135139
// Below are various helper functions for constructing the above more easily.
136140

137-
/// A shorthand for cover(true)
138-
pub fn codegen_cover_loc(&self, msg: &str, span: Option<Span>) -> Stmt {
139-
self.codegen_cover(Expr::bool_true(), msg, span)
141+
/// Given the message for a property, generate a reachability check that is
142+
/// meant to check whether the property is reachable. The function returns a
143+
/// modified version of the provided message that should be used for the
144+
/// property to allow the CBMC output parser to pair the property with its
145+
/// reachability check.
146+
/// If reachability checks are disabled, the function returns the message
147+
/// unmodified and an empty (skip) statement.
148+
pub fn codegen_reachability_check(
149+
&mut self,
150+
msg: String,
151+
span: Option<Span>,
152+
) -> (String, Stmt) {
153+
let loc = self.codegen_caller_span(&span);
154+
if self.queries.get_check_assertion_reachability() {
155+
// Generate a unique ID for the assert
156+
let assert_id = self.next_check_id();
157+
// Generate a message for the reachability check that includes the unique ID
158+
let reach_msg = assert_id.clone();
159+
// Also add the unique ID as a prefix to the assert message so that it can be
160+
// easily paired with the reachability check
161+
let msg = GotocCtx::add_prefix_to_msg(&msg, &assert_id);
162+
// inject a reachability check, which is a (non-blocking)
163+
// assert(false) whose failure indicates that this line is reachable.
164+
// The property class (`PropertyClass:ReachabilityCheck`) is used by
165+
// the CBMC output parser to distinguish those checks from others.
166+
let check = self.codegen_assert(
167+
Expr::bool_false(),
168+
PropertyClass::ReachabilityCheck,
169+
&reach_msg,
170+
loc,
171+
);
172+
(msg, check)
173+
} else {
174+
(msg, Stmt::skip(loc))
175+
}
140176
}
141177

142178
/// A shorthand for generating a CBMC assert-assume(false)

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

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use super::PropertyClass;
66
use crate::codegen_cprover_gotoc::{GotocCtx, VtableCtx};
77
use crate::unwrap_or_return_codegen_unimplemented_stmt;
88
use cbmc::goto_program::{Expr, Location, Stmt, Type};
9-
use kani_queries::UserInput;
109
use rustc_hir::def_id::DefId;
1110
use rustc_middle::mir;
1211
use rustc_middle::mir::{
@@ -226,16 +225,8 @@ impl<'tcx> GotocCtx<'tcx> {
226225
msg.description()
227226
};
228227

229-
// TODO: switch to tagging assertions via the property class once CBMC allows that:
230-
// https://github.com/diffblue/cbmc/issues/6692
231-
let (msg_str, reach_stmt) = if self.queries.get_check_assertion_reachability() {
232-
let check_id = self.next_check_id();
233-
let msg_str = GotocCtx::add_prefix_to_msg(msg, &check_id);
234-
let reach_msg = GotocCtx::reachability_check_message(&check_id);
235-
(msg_str, self.codegen_cover_loc(&reach_msg, Some(term.source_info.span)))
236-
} else {
237-
(msg.to_string(), Stmt::skip(loc))
238-
};
228+
let (msg_str, reach_stmt) =
229+
self.codegen_reachability_check(msg.to_owned(), Some(term.source_info.span));
239230

240231
Stmt::block(
241232
vec![

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

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use crate::codegen_cprover_gotoc::codegen::PropertyClass;
1212
use crate::codegen_cprover_gotoc::GotocCtx;
1313
use crate::unwrap_or_return_codegen_unimplemented_stmt;
1414
use cbmc::goto_program::{BuiltinFn, Expr, Location, Stmt, Type};
15-
use kani_queries::UserInput;
1615
use rustc_middle::mir::{BasicBlock, Place};
1716
use rustc_middle::ty::print::with_no_trimmed_paths;
1817
use rustc_middle::ty::{Instance, TyCtxt};
@@ -75,14 +74,16 @@ impl<'tcx> GotocHook<'tcx> for Cover {
7574
let msg = tcx.extract_const_message(&msg).unwrap();
7675
let target = target.unwrap();
7776
let caller_loc = tcx.codegen_caller_span(&span);
78-
let loc = tcx.codegen_span_option(span);
77+
78+
let (msg, reach_stmt) = tcx.codegen_reachability_check(msg, span);
7979

8080
Stmt::block(
8181
vec![
82+
reach_stmt,
8283
tcx.codegen_cover(cond, &msg, span),
8384
Stmt::goto(tcx.current_fn().find_label(&target), caller_loc),
8485
],
85-
loc,
86+
caller_loc,
8687
)
8788
}
8889
}
@@ -139,20 +140,7 @@ impl<'tcx> GotocHook<'tcx> for Assert {
139140
let target = target.unwrap();
140141
let caller_loc = tcx.codegen_caller_span(&span);
141142

142-
// TODO: switch to tagging assertions via the property class once CBMC allows that:
143-
// https://github.com/diffblue/cbmc/issues/6692
144-
let (msg, reach_stmt) = if tcx.queries.get_check_assertion_reachability() {
145-
// Generate a unique ID for the assert
146-
let assert_id = tcx.next_check_id();
147-
// Add this ID as a prefix to the assert message so that it can be
148-
// easily paired with the reachability check
149-
let msg = GotocCtx::add_prefix_to_msg(&msg, &assert_id);
150-
let reach_msg = GotocCtx::reachability_check_message(&assert_id);
151-
// inject a reachability (cover) check to the current location
152-
(msg, tcx.codegen_cover_loc(&reach_msg, span))
153-
} else {
154-
(msg, Stmt::skip(caller_loc))
155-
};
143+
let (msg, reach_stmt) = tcx.codegen_reachability_check(msg, span);
156144

157145
// Since `cond` might have side effects, assign it to a temporary
158146
// variable so that it's evaluated once, then assert and assume it

kani-compiler/src/codegen_cprover_gotoc/utils/names.rs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -117,15 +117,6 @@ impl<'tcx> GotocCtx<'tcx> {
117117
pub fn add_prefix_to_msg(msg: &str, prefix: &str) -> String {
118118
format!("[{prefix}] {msg}")
119119
}
120-
121-
/// Generate a message for the reachability check of an assert with ID
122-
/// `check_id`. The message is of the form:
123-
/// \[KANI_REACHABILITY_CHECK\] `<ID of assert>`
124-
/// The check_id is generated using the GotocCtx::next_check_id method and
125-
/// is a unique string identifier for that check.
126-
pub fn reachability_check_message(check_id: &str) -> String {
127-
format!("[KANI_REACHABILITY_CHECK] {check_id}")
128-
}
129120
}
130121

131122
/// The full crate name should use the Codegen Unit builder to include full name resolution,

kani-driver/src/cbmc_property_renderer.rs

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,6 @@ static CBMC_ALT_DESCRIPTIONS: Lazy<CbmcAltDescriptions> = Lazy::new(|| {
151151
const UNSUPPORTED_CONSTRUCT_DESC: &str = "is not currently supported by Kani";
152152
const UNWINDING_ASSERT_DESC: &str = "unwinding assertion loop";
153153
const DEFAULT_ASSERTION: &str = "assertion";
154-
const REACH_CHECK_DESC: &str = "[KANI_REACHABILITY_CHECK]";
155154

156155
impl ParserItem {
157156
/// Determines if an item must be skipped or not.
@@ -591,6 +590,9 @@ fn update_properties_with_reach_status(
591590
/// fails, then the cover property is satisfied and vice versa.
592591
/// - SUCCESS -> UNSATISFIABLE
593592
/// - FAILURE -> SATISFIED
593+
/// Note that if the cover property was unreachable, its status at this point
594+
/// will be `CheckStatus::Unreachable` and not `CheckStatus::Success` since
595+
/// `update_properties_with_reach_status` is called beforehand
594596
fn update_results_of_cover_checks(mut properties: Vec<Property>) -> Vec<Property> {
595597
for prop in properties.iter_mut() {
596598
if prop.is_cover_property() {
@@ -622,25 +624,12 @@ fn remove_check_ids_from_description(mut properties: Vec<Property>) -> Vec<Prope
622624
properties
623625
}
624626

625-
/// Given a description, this splits properties into two groups:
626-
/// 1. Properties that don't contain the description
627-
/// 2. Properties that contain the description
628-
fn filter_properties(properties: Vec<Property>, message: &str) -> (Vec<Property>, Vec<Property>) {
629-
let mut filtered_properties = Vec::<Property>::new();
630-
let mut removed_properties = Vec::<Property>::new();
631-
for prop in properties {
632-
if prop.description.contains(message) {
633-
removed_properties.push(prop);
634-
} else {
635-
filtered_properties.push(prop);
636-
}
637-
}
638-
(filtered_properties, removed_properties)
639-
}
640-
641-
/// Filters reachability checks with `filter_properties`
627+
/// Partitions `properties` into reachability checks (identified by the
628+
/// "reachability_check" property class) and non-reachability checks
642629
fn filter_reach_checks(properties: Vec<Property>) -> (Vec<Property>, Vec<Property>) {
643-
filter_properties(properties, REACH_CHECK_DESC)
630+
let (reach_checks, other_checks): (Vec<_>, Vec<_>) =
631+
properties.into_iter().partition(|prop| prop.property_class() == "reachability_check");
632+
(other_checks, reach_checks)
644633
}
645634

646635
/// Filters out Kani-generated sanity checks with a `SUCCESS` status
@@ -680,11 +669,11 @@ fn filter_ptr_checks(properties: Vec<Property>) -> Vec<Property> {
680669
/// includes the ID of the assert for which we want to check its reachability.
681670
/// The description of a reachability check uses the following template:
682671
/// ```text
683-
/// [KANI_REACHABILITY_CHECK] <ID of original assert>
672+
/// <ID of original assert>
684673
/// ```
685674
/// e.g.:
686675
/// ```text
687-
/// [KANI_REACHABILITY_CHECK] KANI_CHECK_ID_foo.6875c808::foo_0
676+
/// KANI_CHECK_ID_foo.6875c808::foo_0
688677
/// ```
689678
/// This function first collects all data from reachability checks. Then,
690679
/// it updates the reachability status for all properties accordingly.

library/kani/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ pub const fn assert(_cond: bool, _msg: &'static str) {
7878
/// Cover properties are reported as:
7979
/// - SATISFIED: if Kani found an execution that satisfies the condition
8080
/// - UNSATISFIABLE: if Kani proved that the condition cannot be satisfied
81+
/// - UNREACHABLE: if Kani proved that the cover property itself is unreachable (i.e. it is vacuously UNSATISFIABLE)
8182
///
8283
/// This function is called by the [`cover!`] macro. The macro is more
8384
/// convenient to use.

tests/expected/cover/cover-fail/expected

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Status: UNSATISFIABLE\
2-
Description: "cover location"\
3-
main.rs:29:23 in function cover_overconstrained
2+
Description: "cover condition: x != 0"\
3+
in function cover_overconstrained
44

55
** 0 of 1 cover properties satisfied
66

tests/expected/cover/cover-fail/main.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,10 @@ fn get_sign(x: i32) -> Sign {
2222
#[kani::proof]
2323
fn cover_overconstrained() {
2424
let x: i32 = kani::any();
25-
kani::assume(x != 0);
2625
let sign = get_sign(x);
2726

2827
match sign {
29-
Sign::Zero => kani::cover!(),
28+
Sign::Zero => kani::cover!(x != 0),
3029
_ => {}
3130
}
3231
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Status: UNREACHABLE\
2+
Description: "cover condition: x == 2"\
3+
in function cover_unreachable
4+
5+
Status: UNREACHABLE\
6+
Description: "Unreachable with a message"\
7+
in function cover_unreachable
8+
9+
Status: SATISFIED\
10+
Description: "cover condition: x == 5"\
11+
in function cover_unreachable
12+
13+
14+
** 1 of 3 cover properties satisfied (2 unreachable)
15+
16+
VERIFICATION:- SUCCESSFUL
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
/// Check that Kani reports an unreachable cover property as such
5+
6+
#[kani::proof]
7+
fn cover_unreachable() {
8+
let x: i32 = kani::any();
9+
if x > 10 {
10+
if x < 5 {
11+
kani::cover!(x == 2); // unreachable
12+
}
13+
} else {
14+
if x > 20 {
15+
kani::cover!(x == 30, "Unreachable with a message"); // unreachable
16+
} else {
17+
kani::cover!(x == 5); // satisfiable
18+
}
19+
}
20+
}

0 commit comments

Comments
 (0)