Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unwind Attribute without Python Changes #846

Merged
merged 23 commits into from
Mar 7, 2022
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9e2e1b1
Unwind Attribute without Python Changes
jaisnan Feb 19, 2022
89092cf
Merge branch 'main' of https://github.com/model-checking/kani into Un…
jaisnan Feb 23, 2022
05f83b0
Fixed metadata schema and addressed overuse issues
jaisnan Feb 28, 2022
da43716
Added user error handling and documentation
jaisnan Mar 1, 2022
5ca9856
Merge branch 'main' of https://github.com/model-checking/kani into Un…
jaisnan Mar 1, 2022
3a2b8b0
Fixed regression tests
jaisnan Mar 1, 2022
f4cd7ee
Fix lib.rs comments
jaisnan Mar 1, 2022
3deac4e
Merge branch 'main' into Unwind-Attribute
jaisnan Mar 1, 2022
d870bbe
Addressing PR documentation and review
jaisnan Mar 1, 2022
52288e3
Removing redundant fixme harnesses
jaisnan Mar 1, 2022
29b2820
Merge branch 'main' of https://github.com/model-checking/kani into Un…
jaisnan Mar 1, 2022
4de691f
Merge branch 'Unwind-Attribute' of https://github.com/jaisnan/kani in…
jaisnan Mar 1, 2022
f09c7f4
Addressing PR concerns and updating comments
jaisnan Mar 2, 2022
e4e69e9
Merge branch 'main' into Unwind-Attribute
jaisnan Mar 2, 2022
70f5685
Update PR with comments and refactoring
jaisnan Mar 4, 2022
192d189
Merge branch 'main' of https://github.com/model-checking/kani into Un…
jaisnan Mar 4, 2022
08a40a3
Merge branch 'Unwind-Attribute' of https://github.com/jaisnan/kani in…
jaisnan Mar 4, 2022
51b86ae
fixed regression tests
jaisnan Mar 4, 2022
5715249
Merge branch 'main' of https://github.com/model-checking/kani into Un…
jaisnan Mar 4, 2022
ddc97dd
Merge branch 'main' of https://github.com/model-checking/kani into Un…
jaisnan Mar 7, 2022
c661d9f
Addressing PR Comments and changing conditional checking and document…
jaisnan Mar 7, 2022
e637963
Update doc comments and error messages
jaisnan Mar 7, 2022
a04b462
fixing typo
jaisnan Mar 7, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion library/kani_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ pub fn proof(_attr: TokenStream, item: TokenStream) -> TokenStream {

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

assert!(attr.to_string().len() == 0, "#[kani::proof] does not take any arguments");
jaisnan marked this conversation as resolved.
Show resolved Hide resolved
result.extend("#[kanitool::proof]".parse::<TokenStream>().unwrap());
// no_mangle is a temporary hack to make the function "public" so it gets codegen'd
result.extend("#[no_mangle]".parse::<TokenStream>().unwrap());
Expand All @@ -49,3 +50,26 @@ pub fn proof(_attr: TokenStream, item: TokenStream) -> TokenStream {
// $item
// )
}

#[cfg(not(kani))]
#[proc_macro_attribute]
pub fn unwind(_attr: TokenStream, _item: TokenStream) -> TokenStream {
// When the config is not kani, we should leave the function alone
_item
jaisnan marked this conversation as resolved.
Show resolved Hide resolved
}

/// Set Loop unwind limit for proof harnesses
/// The attribute '#[kani::unwind(arg)]' can only be called alongside '#[kani::proof]'.
/// arg - Takes in a integer value (u32) that represents the unwind value for the harness.
#[cfg(kani)]
#[proc_macro_attribute]
pub fn unwind(attr: TokenStream, item: TokenStream) -> TokenStream {
celinval marked this conversation as resolved.
Show resolved Hide resolved
let mut result = TokenStream::new();

// Translate #[kani::unwind(arg)] to #[kanitool::unwind(arg)]
let insert_string = "#[kanitool::unwind(".to_owned() + &attr.clone().to_string() + ")]";
result.extend(insert_string.parse::<TokenStream>().unwrap());

result.extend(item);
result
}
125 changes: 114 additions & 11 deletions src/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ use crate::codegen_cprover_gotoc::GotocCtx;
use cbmc::goto_program::{Expr, Stmt, Symbol};
use cbmc::InternString;
use rustc_ast::ast;
use rustc_ast::{Attribute, LitKind};
use rustc_middle::mir::coverage::Op;
use rustc_middle::mir::{HasLocalDecls, Local};
use rustc_middle::ty::{self, Instance};
use std::collections::BTreeMap;
use std::convert::TryInto;
use std::iter::FromIterator;
use tracing::{debug, warn};

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

for attr in self.tcx.get_attrs(instance.def_id()) {
match kanitool_attr_name(attr).as_deref() {
Some("proof") => self.handle_kanitool_proof(),
_ => {}
if proof_attributes.is_empty() && !other_attributes.is_empty() {
self.tcx.sess.span_err(
other_attributes[0].1.span,
format!(
"The {} attribute also requires the '#[kani::proof]' attribute",
other_attributes[0].0
)
.as_str(),
);
return;
}
if proof_attributes.len() > 1 {
// No return because this only requires a warning
self.tcx.sess.span_warn(proof_attributes[0].span, "Only one '#[kani::proof]' allowed");
}
if !proof_attributes.is_empty() {
self.create_proof_harness(other_attributes);
}
}

/// Create the proof harness struct using the handler methods for various attributes
fn create_proof_harness(&mut self, other_attributes: Vec<(String, &Attribute)>) {
let mut harness = self.default_kanitool_proof();
for attr in other_attributes.iter() {
match attr.0.as_str() {
"unwind" => self.handle_kanitool_unwind(attr.1, &mut harness),
_ => {
self.tcx.sess.span_err(
attr.1.span,
format!("Unsupported Annotation -> {}", attr.0.as_str()).as_str(),
);
}
}
}
self.proof_harnesses.push(harness);
}

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

self.proof_harnesses.push(harness);
harness
}

/// Updates the proof harness with new unwind value
fn handle_kanitool_unwind(&mut self, attr: &Attribute, harness: &mut HarnessMetadata) {
// If some unwind value already exists, then the current unwind being handled is a duplicate
if !harness.unwind_value.is_none() {
self.tcx.sess.span_err(attr.span, "Only one '#[kani::unwind]' allowed");
return;
}
// Get Attribute value and if it's not none, assign it to the metadata
match extract_integer_argument(attr) {
None => {
// There are no integers or too many arguments given to the attribute
self.tcx
.sess
.span_err(attr.span, "Exactly one Unwind Argument as Integer accepted");
return;
}
Some(unwind_integer_value) => {
let val: Result<u32, _> = unwind_integer_value.try_into();
if val.is_err() {
self.tcx
.sess
.span_err(attr.span, "Value above maximum permitted value - u32::MAX");
return;
}
harness.unwind_value = Some(val.unwrap());
}
}
}
}

/// If the attribute is named `kanitool::name`, this extracts `name`
fn kanitool_attr_name(attr: &ast::Attribute) -> Option<String> {
match &attr.kind {
ast::AttrKind::Normal(ast::AttrItem { path: ast::Path { segments, .. }, .. }, _)
if segments.len() == 2 && segments[0].ident.as_str() == "kanitool" =>
if (!segments.is_empty()) && segments[0].ident.as_str() == "kanitool" =>
{
Some(segments[1].ident.as_str().to_string())
}
_ => None,
}
}

/// Partition all the attributes into two buckets, proof_attributes and other_attributes
fn partition_kanitool_attributes(
all_attributes: &[Attribute],
) -> (Vec<&Attribute>, Vec<(String, &Attribute)>) {
let mut proof_attributes = vec![];
let mut other_attributes = vec![];

for attr in all_attributes {
// Get the string the appears after "kanitool::" in each attribute string.
// Ex - "proof" | "unwind" etc.
if let Some(attribute_string) = kanitool_attr_name(attr).as_deref() {
if attribute_string == "proof" {
proof_attributes.push(attr);
} else {
other_attributes.push((attribute_string.to_string(), attr));
}
}
}

(proof_attributes, other_attributes)
}

/// Extracts the integer value argument from the attribute provided
/// For example, `unwind(8)` return `Some(8)`
fn extract_integer_argument(attr: &Attribute) -> Option<u128> {
// Vector of meta items , that contain the arguments given the attribute
let attr_args = attr.meta_item_list()?;
// Only extracts one integer value as argument
if attr_args.len() == 1 {
let x = attr_args[0].literal()?;
match x.kind {
LitKind::Int(y, ..) => Some(y),
_ => None,
}
}
// Return none if there are no attributes or if there's too many attributes
else {
None
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pub struct HarnessMetadata {
pub original_file: String,
/// The line in that file where the proof harness begins
pub original_line: String,
/// Optional data to store unwind value
pub unwind_value: Option<u32>,
}

/// The structure of `.kani-metadata.json` files, which are emitted for each crate
Expand Down
2 changes: 1 addition & 1 deletion tests/cargo-kani/simple-proof-annotation/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ edition = "2021"
[dependencies]

[kani.flags]
output-format = "old"
output-format = "old"
13 changes: 13 additions & 0 deletions tests/cargo-kani/simple-unwind-annotation/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0 OR MIT

[package]
name = "simple-unwind-annotation"
version = "0.1.0"
edition = "2021"

[dependencies]

[kani.flags]
output-format = "old"
function = "harness"
1 change: 1 addition & 0 deletions tests/cargo-kani/simple-unwind-annotation/main.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[harness.assertion.2] line 21 assertion failed: counter < 10: FAILURE
32 changes: 32 additions & 0 deletions tests/cargo-kani/simple-unwind-annotation/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR MIT

// kani-flags: --function harness

// The expected file presently looks for "1 == 2" above.
// But eventually this test may start to fail as we might stop regarding 'main'
// as a valid proof harness, since it isn't annotated as such.
// This test should be updated if we go that route.

fn main() {
assert!(1 == 2);
}

#[kani::proof]
#[kani::unwind(9)]
fn harness() {
let mut counter = 0;
loop {
counter += 1;
assert!(counter < 10);
}
}

#[kani::proof]
fn harness_2() {
let mut counter = 0;
loop {
counter += 1;
assert!(counter < 10);
}
}
21 changes: 21 additions & 0 deletions tests/kani/Unwind-Attribute/fixme_lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR MIT

// kani-flags: --no-unwinding-checks

// Added to fix me because there are no tests for the annotations themselves.
// 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.

fn main() {
assert!(1 == 2);
}

#[kani::proof]
#[kani::unwind(10)]
fn harness() {
let mut counter = 0;
loop {
counter += 1;
assert!(counter < 10);
}
}
6 changes: 6 additions & 0 deletions tests/ui/arguments-proof/expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
--> main.rs:13:1
|
13 | #[kani::proof(some, argument2)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: message: #[kani::proof] does not take any arguments
20 changes: 20 additions & 0 deletions tests/ui/arguments-proof/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR MIT

// kani-flags: --no-unwinding-checks

// This test is to check Kani's error handling for harnesses that have proof attributes
// with arguments when the expected declaration takes no arguments.

fn main() {
assert!(1 == 2);
}

#[kani::proof(some, argument2)]
fn harness() {
let mut counter = 0;
loop {
counter += 1;
assert!(counter < 10);
}
}
9 changes: 9 additions & 0 deletions tests/ui/multiple-proof-attributes/expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
warning: Only one '#[kani::proof]' allowed
--> main.rs:14:1
|
14 | #[kani::proof]
| ^^^^^^^^^^^^^^
|
= note: this warning originates in the attribute macro `kani::proof` (in Nightly builds, run with -Z macro-backtrace for more info)

warning: 1 warning emitted
21 changes: 21 additions & 0 deletions tests/ui/multiple-proof-attributes/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR MIT

// kani-flags: --no-unwinding-checks --verbose

// This test is to check Kani's error handling for harnesses that have multiple proof annotations
// declared.

fn main() {
assert!(1 == 2);
}

#[kani::proof]
#[kani::proof]
fn harness() {
let mut counter = 0;
loop {
counter += 1;
assert!(counter < 10);
}
}
7 changes: 7 additions & 0 deletions tests/ui/unsupported-annotation/expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
error[E0433]: failed to resolve: could not find `test_annotation` in `kani`
--> main.rs:14:9
|
14 | #[kani::test_annotation]
| ^^^^^^^^^^^^^^^ could not find `test_annotation` in `kani`

error: aborting due to previous error
21 changes: 21 additions & 0 deletions tests/ui/unsupported-annotation/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR MIT

// kani-flags: --no-unwinding-checks --verbose

// This test is to check Kani's error handling for harnesses that have multiple proof annotations
// declared.

fn main() {
assert!(1 == 2);
}

#[kani::proof]
#[kani::test_annotation]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: this isn't testing your code, you'd have to write kanitool:: for that

fn harness() {
let mut counter = 0;
loop {
counter += 1;
assert!(counter < 10);
}
}
5 changes: 5 additions & 0 deletions tests/ui/unwind-multiple-arguments/expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: Exactly one Unwind Argument as Integer accepted
--> main.rs:14:1
|
14 | #[kani::unwind(10,5)]
| ^^^^^^^^^^^^^^^^^^^^^
Loading