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 21 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
27 changes: 26 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,27 @@ pub fn proof(_attr: TokenStream, item: TokenStream) -> TokenStream {
// $item
// )
}

#[cfg(not(kani))]
#[proc_macro_attribute]
pub fn unwind(_attr: TokenStream, _item: TokenStream) -> TokenStream {
// This code is never called by default as the config value is set to kani
TokenStream::new()
}
adpaco-aws marked this conversation as resolved.
Show resolved Hide resolved

// 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.
// Usage is restricted to proof harnesses only currently.
Copy link
Contributor

Choose a reason for hiding this comment

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

Doc-comments in Rust use 3 /s not 2, check all your function comments for this.

#[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() + ")]";
// Add the string that looks like - #[kanitool::unwind(arg)] to the tokenstream
result.extend(insert_string.parse::<TokenStream>().unwrap());

result.extend(item);
result
}
136 changes: 127 additions & 9 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,11 @@ 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::{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 @@ -241,18 +243,54 @@ impl<'tcx> GotocCtx<'tcx> {
/// Currently, this is only proof harness annotations.
/// i.e. `#[kani::proof]` (which kani_macros translates to `#[kanitool::proof]` 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(),
_ => {}
// Early return with empty tuple for instances without any attributes declared
if proof_attributes.is_empty() && other_attributes.is_empty() {
return;
}
// User error handling for user cases where there's more than one proof attribute being called
if proof_attributes.len() > 1 {
self.tcx.sess.span_warn(proof_attributes[0].span, "Only one Proof Attribute allowed");
return;
}
// User error handling for the user case where there's an attribute being called without #kani::tool
if proof_attributes.is_empty() && !other_attributes.is_empty() {
self.tcx.sess.span_err(
other_attributes[0].1.span,
"Please use '#kani[proof]' above the annotation",
Copy link
Contributor

Choose a reason for hiding this comment

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

typo

);
return;
}
// Create a proof harness if no user error has been triggered
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)>) {
// In the case when there's only one proof attribute (correct behavior), create harness and modify it
// depending on each subsequent attribute that's being called by the user.
let mut harness = self.default_kanitool_proof();
// loop through all subsequent attributes
for attr in other_attributes.iter() {
// match with "unwind" attribute and provide the harness for modification
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(),
);
}
}
}
// Update `self` (the goto context) to add the current function as a listed proof harness
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) {
/// Handler to create the default proof harness and return
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 +301,100 @@ 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
}

/// Unwind strings of the format 'unwind(x)' and the mangled names are to be parsed for the value 'x'
fn handle_kanitool_unwind(&mut self, attr: &Attribute, harness: &mut HarnessMetadata) {
// Check if some unwind value doesnt already exist
if !harness.unwind_value.is_none() {
// User warning for when there's more than one unwind attribute, in this case, only the first value will be
self.tcx.sess.span_err(attr.span, "Use only one Unwind Annotation per Harness");
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 integers given as argument to the annotatio
self.tcx
.sess
.span_err(attr.span, "Exactly one Unwind Argument as Integer accepted");
return;
}
Some(unwind_integer_value) => {
// Binding integer to u32
if unwind_integer_value >= u32::MAX.into() {
self.tcx
.sess
.span_err(attr.span, "Value above maximum permitted value - u32::MAX");
return;
}
// Expected case
harness.unwind_value = Some(unwind_integer_value.try_into().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,
}
}

/// Create two vectors, proof_vector and attribute_vector which seperates the list of attributes into
/// proof and non-proof for easier parsing
fn partition_kanitool_attributes(
all_attributes: &[Attribute],
) -> (Vec<&Attribute>, Vec<(String, &Attribute)>) {
// Vectors for storing instances of each attribute type,
// TODO: This can be modifed to use Enums when more options are provided
let mut proof_attributes = vec![];
let mut other_attributes = vec![];

// Loop through instances to get all "kanitool::x" attribute strings
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() {
// Push to proof vector
if attribute_string == "proof" {
proof_attributes.push(attr);
}
// Push to attribute vector that can be expanded to a map when more options become available
else {
other_attributes.push((attribute_string.to_string(), attr));
}
}
}

(proof_attributes, other_attributes)
}

/// Extracts the integer value argument from the any attribute provided
fn extract_integer_argument(attr: &Attribute) -> Option<u128> {
// Vector of meta items , that contain metadata about the annotation
let attr_args = attr.meta_item_list().map(|x| x.to_vec())?;
// Only extracts one integer value as argument
if attr_args.len() == 1 {
// Returns the integer value that's the argument for the attribute
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 Proof Attribute 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