Skip to content

Commit 63ed597

Browse files
jaisnantedinski
authored andcommitted
Unwind Attribute without Python Changes (#846)
* Unwind Attribute without Python Changes
1 parent 9d27d0f commit 63ed597

File tree

18 files changed

+345
-13
lines changed

18 files changed

+345
-13
lines changed

library/kani_macros/src/lib.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@ pub fn proof(_attr: TokenStream, item: TokenStream) -> TokenStream {
3636

3737
#[cfg(kani)]
3838
#[proc_macro_attribute]
39-
pub fn proof(_attr: TokenStream, item: TokenStream) -> TokenStream {
39+
pub fn proof(attr: TokenStream, item: TokenStream) -> TokenStream {
4040
let mut result = TokenStream::new();
4141

42+
assert!(attr.to_string().len() == 0, "#[kani::proof] does not take any arguments");
4243
result.extend("#[kanitool::proof]".parse::<TokenStream>().unwrap());
4344
// no_mangle is a temporary hack to make the function "public" so it gets codegen'd
4445
result.extend("#[no_mangle]".parse::<TokenStream>().unwrap());
@@ -49,3 +50,26 @@ pub fn proof(_attr: TokenStream, item: TokenStream) -> TokenStream {
4950
// $item
5051
// )
5152
}
53+
54+
#[cfg(not(kani))]
55+
#[proc_macro_attribute]
56+
pub fn unwind(_attr: TokenStream, item: TokenStream) -> TokenStream {
57+
// When the config is not kani, we should leave the function alone
58+
item
59+
}
60+
61+
/// Set Loop unwind limit for proof harnesses
62+
/// The attribute '#[kani::unwind(arg)]' can only be called alongside '#[kani::proof]'.
63+
/// arg - Takes in a integer value (u32) that represents the unwind value for the harness.
64+
#[cfg(kani)]
65+
#[proc_macro_attribute]
66+
pub fn unwind(attr: TokenStream, item: TokenStream) -> TokenStream {
67+
let mut result = TokenStream::new();
68+
69+
// Translate #[kani::unwind(arg)] to #[kanitool::unwind(arg)]
70+
let insert_string = "#[kanitool::unwind(".to_owned() + &attr.clone().to_string() + ")]";
71+
result.extend(insert_string.parse::<TokenStream>().unwrap());
72+
73+
result.extend(item);
74+
result
75+
}

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

Lines changed: 114 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ use crate::codegen_cprover_gotoc::GotocCtx;
88
use cbmc::goto_program::{Expr, Stmt, Symbol};
99
use cbmc::InternString;
1010
use rustc_ast::ast;
11+
use rustc_ast::{Attribute, LitKind};
12+
use rustc_middle::mir::coverage::Op;
1113
use rustc_middle::mir::{HasLocalDecls, Local};
1214
use rustc_middle::ty::{self, Instance};
1315
use std::collections::BTreeMap;
16+
use std::convert::TryInto;
1417
use std::iter::FromIterator;
1518
use tracing::{debug, warn};
1619

@@ -238,21 +241,50 @@ impl<'tcx> GotocCtx<'tcx> {
238241
/// This updates the goto context with any information that should be accumulated from a function's
239242
/// attributes.
240243
///
241-
/// Currently, this is only proof harness annotations.
242-
/// i.e. `#[kani::proof]` (which kani_macros translates to `#[kanitool::proof]` for us to handle here)
244+
/// Handle all attributes i.e. `#[kani::x]` (which kani_macros translates to `#[kanitool::x]` for us to handle here)
243245
fn handle_kanitool_attributes(&mut self) {
244-
let instance = self.current_fn().instance();
246+
let all_attributes = self.tcx.get_attrs(self.current_fn().instance().def_id());
247+
let (proof_attributes, other_attributes) = partition_kanitool_attributes(all_attributes);
245248

246-
for attr in self.tcx.get_attrs(instance.def_id()) {
247-
match kanitool_attr_name(attr).as_deref() {
248-
Some("proof") => self.handle_kanitool_proof(),
249-
_ => {}
249+
if proof_attributes.is_empty() && !other_attributes.is_empty() {
250+
self.tcx.sess.span_err(
251+
other_attributes[0].1.span,
252+
format!(
253+
"The {} attribute also requires the '#[kani::proof]' attribute",
254+
other_attributes[0].0
255+
)
256+
.as_str(),
257+
);
258+
return;
259+
}
260+
if proof_attributes.len() > 1 {
261+
// No return because this only requires a warning
262+
self.tcx.sess.span_warn(proof_attributes[0].span, "Only one '#[kani::proof]' allowed");
263+
}
264+
if !proof_attributes.is_empty() {
265+
self.create_proof_harness(other_attributes);
266+
}
267+
}
268+
269+
/// Create the proof harness struct using the handler methods for various attributes
270+
fn create_proof_harness(&mut self, other_attributes: Vec<(String, &Attribute)>) {
271+
let mut harness = self.default_kanitool_proof();
272+
for attr in other_attributes.iter() {
273+
match attr.0.as_str() {
274+
"unwind" => self.handle_kanitool_unwind(attr.1, &mut harness),
275+
_ => {
276+
self.tcx.sess.span_err(
277+
attr.1.span,
278+
format!("Unsupported Annotation -> {}", attr.0.as_str()).as_str(),
279+
);
280+
}
250281
}
251282
}
283+
self.proof_harnesses.push(harness);
252284
}
253285

254-
/// Update `self` (the goto context) to add the current function as a listed proof harness
255-
fn handle_kanitool_proof(&mut self) {
286+
/// Create the default proof harness for the current function
287+
fn default_kanitool_proof(&mut self) -> HarnessMetadata {
256288
let current_fn = self.current_fn();
257289
let pretty_name = current_fn.readable_name().to_owned();
258290
let mangled_name = current_fn.name();
@@ -263,20 +295,91 @@ impl<'tcx> GotocCtx<'tcx> {
263295
mangled_name,
264296
original_file: loc.filename().unwrap(),
265297
original_line: loc.line().unwrap().to_string(),
298+
unwind_value: None,
266299
};
267300

268-
self.proof_harnesses.push(harness);
301+
harness
302+
}
303+
304+
/// Updates the proof harness with new unwind value
305+
fn handle_kanitool_unwind(&mut self, attr: &Attribute, harness: &mut HarnessMetadata) {
306+
// If some unwind value already exists, then the current unwind being handled is a duplicate
307+
if !harness.unwind_value.is_none() {
308+
self.tcx.sess.span_err(attr.span, "Only one '#[kani::unwind]' allowed");
309+
return;
310+
}
311+
// Get Attribute value and if it's not none, assign it to the metadata
312+
match extract_integer_argument(attr) {
313+
None => {
314+
// There are no integers or too many arguments given to the attribute
315+
self.tcx
316+
.sess
317+
.span_err(attr.span, "Exactly one Unwind Argument as Integer accepted");
318+
return;
319+
}
320+
Some(unwind_integer_value) => {
321+
let val: Result<u32, _> = unwind_integer_value.try_into();
322+
if val.is_err() {
323+
self.tcx
324+
.sess
325+
.span_err(attr.span, "Value above maximum permitted value - u32::MAX");
326+
return;
327+
}
328+
harness.unwind_value = Some(val.unwrap());
329+
}
330+
}
269331
}
270332
}
271333

272334
/// If the attribute is named `kanitool::name`, this extracts `name`
273335
fn kanitool_attr_name(attr: &ast::Attribute) -> Option<String> {
274336
match &attr.kind {
275337
ast::AttrKind::Normal(ast::AttrItem { path: ast::Path { segments, .. }, .. }, _)
276-
if segments.len() == 2 && segments[0].ident.as_str() == "kanitool" =>
338+
if (!segments.is_empty()) && segments[0].ident.as_str() == "kanitool" =>
277339
{
278340
Some(segments[1].ident.as_str().to_string())
279341
}
280342
_ => None,
281343
}
282344
}
345+
346+
/// Partition all the attributes into two buckets, proof_attributes and other_attributes
347+
fn partition_kanitool_attributes(
348+
all_attributes: &[Attribute],
349+
) -> (Vec<&Attribute>, Vec<(String, &Attribute)>) {
350+
let mut proof_attributes = vec![];
351+
let mut other_attributes = vec![];
352+
353+
for attr in all_attributes {
354+
// Get the string the appears after "kanitool::" in each attribute string.
355+
// Ex - "proof" | "unwind" etc.
356+
if let Some(attribute_string) = kanitool_attr_name(attr).as_deref() {
357+
if attribute_string == "proof" {
358+
proof_attributes.push(attr);
359+
} else {
360+
other_attributes.push((attribute_string.to_string(), attr));
361+
}
362+
}
363+
}
364+
365+
(proof_attributes, other_attributes)
366+
}
367+
368+
/// Extracts the integer value argument from the attribute provided
369+
/// For example, `unwind(8)` return `Some(8)`
370+
fn extract_integer_argument(attr: &Attribute) -> Option<u128> {
371+
// Vector of meta items , that contain the arguments given the attribute
372+
let attr_args = attr.meta_item_list()?;
373+
// Only extracts one integer value as argument
374+
if attr_args.len() == 1 {
375+
let x = attr_args[0].literal()?;
376+
match x.kind {
377+
LitKind::Int(y, ..) => Some(y),
378+
_ => None,
379+
}
380+
}
381+
// Return none if there are no attributes or if there's too many attributes
382+
else {
383+
None
384+
}
385+
}

src/kani-compiler/src/codegen_cprover_gotoc/context/metadata.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ pub struct HarnessMetadata {
1717
pub original_file: String,
1818
/// The line in that file where the proof harness begins
1919
pub original_line: String,
20+
/// Optional data to store unwind value
21+
pub unwind_value: Option<u32>,
2022
}
2123

2224
/// The structure of `.kani-metadata.json` files, which are emitted for each crate

tests/cargo-kani/simple-proof-annotation/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ edition = "2021"
99
[dependencies]
1010

1111
[kani.flags]
12-
output-format = "old"
12+
output-format = "old"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
[package]
5+
name = "simple-unwind-annotation"
6+
version = "0.1.0"
7+
edition = "2021"
8+
9+
[dependencies]
10+
11+
[kani.flags]
12+
output-format = "old"
13+
function = "harness"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[harness.assertion.2] line 21 assertion failed: counter < 10: FAILURE
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// kani-flags: --function harness
5+
6+
// The expected file presently looks for "1 == 2" above.
7+
// But eventually this test may start to fail as we might stop regarding 'main'
8+
// as a valid proof harness, since it isn't annotated as such.
9+
// This test should be updated if we go that route.
10+
11+
fn main() {
12+
assert!(1 == 2);
13+
}
14+
15+
#[kani::proof]
16+
#[kani::unwind(9)]
17+
fn harness() {
18+
let mut counter = 0;
19+
loop {
20+
counter += 1;
21+
assert!(counter < 10);
22+
}
23+
}
24+
25+
#[kani::proof]
26+
fn harness_2() {
27+
let mut counter = 0;
28+
loop {
29+
counter += 1;
30+
assert!(counter < 10);
31+
}
32+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// kani-flags: --no-unwinding-checks
5+
6+
// Added to fix me because there are no tests for the annotations themselves.
7+
// TODO : The unwind value needs to be parsed and the unwind needs to be applied to each harness, we do not test this behavior yet.
8+
9+
fn main() {
10+
assert!(1 == 2);
11+
}
12+
13+
#[kani::proof]
14+
#[kani::unwind(10)]
15+
fn harness() {
16+
let mut counter = 0;
17+
loop {
18+
counter += 1;
19+
assert!(counter < 10);
20+
}
21+
}

tests/ui/arguments-proof/expected

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
--> main.rs:13:1
2+
|
3+
13 | #[kani::proof(some, argument2)]
4+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
5+
|
6+
= help: message: #[kani::proof] does not take any arguments

tests/ui/arguments-proof/main.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// kani-flags: --no-unwinding-checks
5+
6+
// This test is to check Kani's error handling for harnesses that have proof attributes
7+
// with arguments when the expected declaration takes no arguments.
8+
9+
fn main() {
10+
assert!(1 == 2);
11+
}
12+
13+
#[kani::proof(some, argument2)]
14+
fn harness() {
15+
let mut counter = 0;
16+
loop {
17+
counter += 1;
18+
assert!(counter < 10);
19+
}
20+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
warning: Only one '#[kani::proof]' allowed
2+
--> main.rs:14:1
3+
|
4+
14 | #[kani::proof]
5+
| ^^^^^^^^^^^^^^
6+
|
7+
= note: this warning originates in the attribute macro `kani::proof` (in Nightly builds, run with -Z macro-backtrace for more info)
8+
9+
warning: 1 warning emitted
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// kani-flags: --no-unwinding-checks --verbose
5+
6+
// This test is to check Kani's error handling for harnesses that have multiple proof annotations
7+
// declared.
8+
9+
fn main() {
10+
assert!(1 == 2);
11+
}
12+
13+
#[kani::proof]
14+
#[kani::proof]
15+
fn harness() {
16+
let mut counter = 0;
17+
loop {
18+
counter += 1;
19+
assert!(counter < 10);
20+
}
21+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
error[E0433]: failed to resolve: could not find `test_annotation` in `kani`
2+
--> main.rs:14:9
3+
|
4+
14 | #[kani::test_annotation]
5+
| ^^^^^^^^^^^^^^^ could not find `test_annotation` in `kani`
6+
7+
error: aborting due to previous error
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// kani-flags: --no-unwinding-checks --verbose
5+
6+
// This test is to check Kani's error handling for harnesses that have multiple proof annotations
7+
// declared.
8+
9+
fn main() {
10+
assert!(1 == 2);
11+
}
12+
13+
#[kani::proof]
14+
#[kani::test_annotation]
15+
fn harness() {
16+
let mut counter = 0;
17+
loop {
18+
counter += 1;
19+
assert!(counter < 10);
20+
}
21+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: Exactly one Unwind Argument as Integer accepted
2+
--> main.rs:14:1
3+
|
4+
14 | #[kani::unwind(10,5)]
5+
| ^^^^^^^^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)