Skip to content

Commit bd1ac2d

Browse files
authoredMar 24, 2023
Add implementation for the #[kani::should_panic] attribute (rust-lang#2315)
1 parent 38d1f6f commit bd1ac2d

File tree

19 files changed

+236
-19
lines changed

19 files changed

+236
-19
lines changed
 

‎kani-compiler/src/kani_middle/attributes.rs

+5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use super::resolve;
2323
#[strum(serialize_all = "snake_case")]
2424
enum KaniAttributeKind {
2525
Proof,
26+
ShouldPanic,
2627
Solver,
2728
Stub,
2829
Unwind,
@@ -96,6 +97,10 @@ pub fn extract_harness_attributes(tcx: TyCtxt, def_id: DefId) -> Option<HarnessA
9697
HarnessAttributes::default(),
9798
|mut harness, (kind, attributes)| {
9899
match kind {
100+
KaniAttributeKind::ShouldPanic => {
101+
expect_single(tcx, kind, &attributes);
102+
harness.should_panic = true
103+
}
99104
KaniAttributeKind::Solver => {
100105
// Make sure the solver is not already set
101106
harness.solver = parse_solver(tcx, expect_single(tcx, kind, &attributes));

‎kani-driver/src/call_cbmc.rs

+68-12
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,31 @@ use crate::cbmc_output_parser::{
1616
use crate::cbmc_property_renderer::{format_result, kani_cbmc_output_filter};
1717
use crate::session::KaniSession;
1818

19-
#[derive(Debug, PartialEq, Eq)]
19+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2020
pub enum VerificationStatus {
2121
Success,
2222
Failure,
2323
}
2424

25+
/// Represents failed properties in three different categories.
26+
/// This simplifies the process to determine and format verification results.
27+
#[derive(Clone, Copy, Debug)]
28+
pub enum FailedProperties {
29+
// No failures
30+
None,
31+
// One or more panic-related failures
32+
PanicsOnly,
33+
// One or more failures that aren't panic-related
34+
Other,
35+
}
36+
2537
/// Our (kani-driver) notions of CBMC results.
2638
#[derive(Debug)]
2739
pub struct VerificationResult {
2840
/// Whether verification should be considered to have succeeded, or have failed.
2941
pub status: VerificationStatus,
42+
/// The compact representation for failed properties
43+
pub failed_properties: FailedProperties,
3044
/// The parsed output, message by message, of CBMC. However, the `Result` message has been
3145
/// removed and is available in `results` instead.
3246
pub messages: Option<Vec<ParserItem>>,
@@ -76,7 +90,7 @@ impl KaniSession {
7690
)
7791
})?;
7892

79-
VerificationResult::from(output, start_time)
93+
VerificationResult::from(output, harness.attributes.should_panic, start_time)
8094
};
8195

8296
self.gen_and_add_concrete_playback(harness, &mut verification_results)?;
@@ -234,13 +248,20 @@ impl VerificationResult {
234248
/// (CBMC will regularly report "failure" but that's just our cover checks.)
235249
/// 2. Positively checking for the presence of results.
236250
/// (Do not mistake lack of results for success: report it as failure.)
237-
fn from(output: VerificationOutput, start_time: Instant) -> VerificationResult {
251+
fn from(
252+
output: VerificationOutput,
253+
should_panic: bool,
254+
start_time: Instant,
255+
) -> VerificationResult {
238256
let runtime = start_time.elapsed();
239257
let (items, results) = extract_results(output.processed_items);
240258

241259
if let Some(results) = results {
260+
let (status, failed_properties) =
261+
verification_outcome_from_properties(&results, should_panic);
242262
VerificationResult {
243-
status: determine_status_from_properties(&results),
263+
status,
264+
failed_properties,
244265
messages: Some(items),
245266
results: Ok(results),
246267
runtime,
@@ -250,6 +271,7 @@ impl VerificationResult {
250271
// We never got results from CBMC - something went wrong (e.g. crash) so it's failure
251272
VerificationResult {
252273
status: VerificationStatus::Failure,
274+
failed_properties: FailedProperties::Other,
253275
messages: Some(items),
254276
results: Err(output.process_status),
255277
runtime,
@@ -261,6 +283,7 @@ impl VerificationResult {
261283
pub fn mock_success() -> VerificationResult {
262284
VerificationResult {
263285
status: VerificationStatus::Success,
286+
failed_properties: FailedProperties::None,
264287
messages: None,
265288
results: Ok(vec![]),
266289
runtime: Duration::from_secs(0),
@@ -271,6 +294,7 @@ impl VerificationResult {
271294
fn mock_failure() -> VerificationResult {
272295
VerificationResult {
273296
status: VerificationStatus::Failure,
297+
failed_properties: FailedProperties::Other,
274298
messages: None,
275299
// on failure, exit codes in theory might be used,
276300
// but `mock_failure` should never be used in a context where they will,
@@ -281,11 +305,14 @@ impl VerificationResult {
281305
}
282306
}
283307

284-
pub fn render(&self, output_format: &OutputFormat) -> String {
308+
pub fn render(&self, output_format: &OutputFormat, should_panic: bool) -> String {
285309
match &self.results {
286310
Ok(results) => {
311+
let status = self.status;
312+
let failed_properties = self.failed_properties;
287313
let show_checks = matches!(output_format, OutputFormat::Regular);
288-
let mut result = format_result(results, show_checks);
314+
let mut result =
315+
format_result(results, status, should_panic, failed_properties, show_checks);
289316
writeln!(result, "Verification Time: {}s", self.runtime.as_secs_f32()).unwrap();
290317
result
291318
}
@@ -310,13 +337,42 @@ impl VerificationResult {
310337
}
311338

312339
/// We decide if verification succeeded based on properties, not (typically) on exit code
313-
fn determine_status_from_properties(properties: &[Property]) -> VerificationStatus {
314-
let number_failed_properties =
315-
properties.iter().filter(|prop| prop.status == CheckStatus::Failure).count();
316-
if number_failed_properties == 0 {
317-
VerificationStatus::Success
340+
fn verification_outcome_from_properties(
341+
properties: &[Property],
342+
should_panic: bool,
343+
) -> (VerificationStatus, FailedProperties) {
344+
let failed_properties = determine_failed_properties(properties);
345+
let status = if should_panic {
346+
match failed_properties {
347+
FailedProperties::None | FailedProperties::Other => VerificationStatus::Failure,
348+
FailedProperties::PanicsOnly => VerificationStatus::Success,
349+
}
318350
} else {
319-
VerificationStatus::Failure
351+
match failed_properties {
352+
FailedProperties::None => VerificationStatus::Success,
353+
FailedProperties::PanicsOnly | FailedProperties::Other => VerificationStatus::Failure,
354+
}
355+
};
356+
(status, failed_properties)
357+
}
358+
359+
/// Determines the `FailedProperties` variant that corresponds to an array of properties
360+
fn determine_failed_properties(properties: &[Property]) -> FailedProperties {
361+
let failed_properties: Vec<&Property> =
362+
properties.iter().filter(|prop| prop.status == CheckStatus::Failure).collect();
363+
// Return `FAILURE` if there isn't at least one failed property
364+
if failed_properties.is_empty() {
365+
FailedProperties::None
366+
} else {
367+
// Check if all failed properties correspond to the `assertion` class.
368+
// Note: Panics caused by `panic!` and `assert!` fall into this class.
369+
let all_failed_checks_are_panics =
370+
failed_properties.iter().all(|prop| prop.property_class() == "assertion");
371+
if all_failed_checks_are_panics {
372+
FailedProperties::PanicsOnly
373+
} else {
374+
FailedProperties::Other
375+
}
320376
}
321377
}
322378

‎kani-driver/src/cbmc_property_renderer.rs

+25-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

44
use crate::args::OutputFormat;
5+
use crate::call_cbmc::{FailedProperties, VerificationStatus};
56
use crate::cbmc_output_parser::{CheckStatus, ParserItem, Property, TraceItem};
67
use console::style;
78
use once_cell::sync::Lazy;
@@ -241,7 +242,13 @@ fn format_item_terse(_item: &ParserItem) -> Option<String> {
241242
///
242243
/// TODO: We could `write!` to `result_str` instead
243244
/// <https://github.com/model-checking/kani/issues/1480>
244-
pub fn format_result(properties: &Vec<Property>, show_checks: bool) -> String {
245+
pub fn format_result(
246+
properties: &Vec<Property>,
247+
status: VerificationStatus,
248+
should_panic: bool,
249+
failed_properties: FailedProperties,
250+
show_checks: bool,
251+
) -> String {
245252
let mut result_str = String::new();
246253
let mut number_checks_failed = 0;
247254
let mut number_checks_unreachable = 0;
@@ -376,9 +383,23 @@ pub fn format_result(properties: &Vec<Property>, show_checks: bool) -> String {
376383
result_str.push_str(&failure_message);
377384
}
378385

379-
let verification_result =
380-
if number_checks_failed == 0 { style("SUCCESSFUL").green() } else { style("FAILED").red() };
381-
let overall_result = format!("\nVERIFICATION:- {verification_result}\n");
386+
let verification_result = if status == VerificationStatus::Success {
387+
style("SUCCESSFUL").green()
388+
} else {
389+
style("FAILED").red()
390+
};
391+
let should_panic_info = if should_panic {
392+
match failed_properties {
393+
FailedProperties::None => " (encountered no panics, but at least one was expected)",
394+
FailedProperties::PanicsOnly => " (encountered one or more panics as expected)",
395+
FailedProperties::Other => {
396+
" (encountered failures other than panics, which were unexpected)"
397+
}
398+
}
399+
} else {
400+
""
401+
};
402+
let overall_result = format!("\nVERIFICATION:- {verification_result}{should_panic_info}\n");
382403
result_str.push_str(&overall_result);
383404

384405
// Ideally, we should generate two `ParserItem::Message` and push them

‎kani-driver/src/harness_runner.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,10 @@ impl KaniSession {
101101
// When quiet, we don't want to print anything at all.
102102
// When output is old, we also don't have real results to print.
103103
if !self.args.quiet && self.args.output_format != OutputFormat::Old {
104-
println!("{}", result.render(&self.args.output_format));
104+
println!(
105+
"{}",
106+
result.render(&self.args.output_format, harness.attributes.should_panic)
107+
);
105108
}
106109

107110
Ok(result)

‎kani_metadata/src/harness.rs

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ pub struct HarnessMetadata {
3131
pub struct HarnessAttributes {
3232
/// Whether the harness has been annotated with proof.
3333
pub proof: bool,
34+
/// Whether the harness is expected to panic or not.
35+
pub should_panic: bool,
3436
/// Optional data to store solver.
3537
pub solver: Option<CbmcSolver>,
3638
/// Optional data to store unwind value.

‎library/kani_macros/src/lib.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ pub fn proof(attr: TokenStream, item: TokenStream) -> TokenStream {
5252
#[kanitool::proof]
5353
);
5454

55-
assert!(attr.is_empty(), "#[kani::proof] does not take any arguments for now");
55+
assert!(attr.is_empty(), "#[kani::proof] does not take any arguments currently");
5656

5757
if sig.asyncness.is_none() {
5858
// Adds `#[kanitool::proof]` and other attributes
@@ -98,6 +98,25 @@ pub fn proof(attr: TokenStream, item: TokenStream) -> TokenStream {
9898
}
9999
}
100100

101+
#[cfg(not(kani))]
102+
#[proc_macro_attribute]
103+
pub fn should_panic(_attr: TokenStream, item: TokenStream) -> TokenStream {
104+
// No-op in non-kani mode
105+
item
106+
}
107+
108+
#[cfg(kani)]
109+
#[proc_macro_attribute]
110+
pub fn should_panic(attr: TokenStream, item: TokenStream) -> TokenStream {
111+
assert!(attr.is_empty(), "`#[kani::should_panic]` does not take any arguments currently");
112+
let mut result = TokenStream::new();
113+
let insert_string = "#[kanitool::should_panic]";
114+
result.extend(insert_string.parse::<TokenStream>().unwrap());
115+
116+
result.extend(item);
117+
result
118+
}
119+
101120
#[cfg(not(kani))]
102121
#[proc_macro_attribute]
103122
pub fn unwind(_attr: TokenStream, item: TokenStream) -> TokenStream {

‎tests/expected/async_proof/expected

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: custom attribute panicked
2-
#[kani::proof] does not take any arguments for now
2+
#[kani::proof] does not take any arguments currently
33

44
error: custom attribute panicked
55
#[kani::proof] cannot be applied to async functions that take inputs for now
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
** 2 of 2 failed\
2+
Failed Checks: panicked on the `if` branch!
3+
Failed Checks: panicked on the `else` branch!
4+
VERIFICATION:- SUCCESSFUL (encountered one or more panics as expected)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! Checks that verfication passes when `#[kani::should_panic]` is used and all
5+
//! failures encountered are panics.
6+
7+
#[kani::proof]
8+
#[kani::should_panic]
9+
fn check() {
10+
if kani::any() {
11+
panic!("panicked on the `if` branch!");
12+
} else {
13+
panic!("panicked on the `else` branch!");
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
error: only one '#[kani::should_panic]' attribute is allowed per harness
2+
error: aborting due to previous error
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! Checks that `#[kani::should_panic]` can only be used once.
5+
6+
#[kani::proof]
7+
#[kani::should_panic]
8+
#[kani::should_panic]
9+
fn check() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Complete - 3 successfully verified harnesses, 0 failures, 3 total.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! Checks that the verification summary printed at the end considers all
5+
//! harnesses as "successfully verified".
6+
7+
#[kani::proof]
8+
#[kani::should_panic]
9+
fn harness1() {
10+
panic!("panicked on `harness1`!");
11+
}
12+
13+
#[kani::proof]
14+
#[kani::should_panic]
15+
fn harness2() {
16+
panic!("panicked on `harness2`!");
17+
}
18+
19+
#[kani::proof]
20+
#[kani::should_panic]
21+
fn harness3() {
22+
panic!("panicked on `harness3`!");
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- Status: SUCCESS\
2+
- Description: "assertion failed: 1 + 1 == 2"
3+
** 0 of 1 failed
4+
VERIFICATION:- FAILED (encountered no panics, but at least one was expected)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! Checks that verfication fails when `#[kani::should_panic]` is used and no
5+
//! panics are encountered.
6+
7+
#[kani::proof]
8+
#[kani::should_panic]
9+
fn check() {
10+
assert!(1 + 1 == 2);
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
overflow.undefined-shift.1\
2+
- Status: FAILURE\
3+
- Description: "shift distance too large"
4+
Failed Checks: attempt to shift left with overflow
5+
Failed Checks: panicked on the `1` arm!
6+
Failed Checks: panicked on the `0` arm!
7+
Failed Checks: shift distance too large
8+
VERIFICATION:- FAILED (encountered failures other than panics, which were unexpected)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! Checks that verfication fails when `#[kani::should_panic]` is used but not
5+
//! all failures encountered are panics.
6+
7+
fn trigger_overflow() {
8+
let x: u32 = kani::any();
9+
let _ = 42 << x;
10+
}
11+
12+
#[kani::proof]
13+
#[kani::should_panic]
14+
fn check() {
15+
match kani::any() {
16+
0 => panic!("panicked on the `0` arm!"),
17+
1 => panic!("panicked on the `1` arm!"),
18+
_ => {
19+
trigger_overflow();
20+
()
21+
}
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
error: custom attribute panicked
2+
help: message: `#[kani::should_panic]` does not take any arguments currently
3+
error: aborting due to previous error
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! Checks that `#[kani::should_panic]` doesn't accept arguments.
5+
6+
#[kani::proof]
7+
#[kani::should_panic(arg)]
8+
fn check() {}

0 commit comments

Comments
 (0)
Please sign in to comment.