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

Conversation

jaisnan
Copy link
Contributor

@jaisnan jaisnan commented Feb 19, 2022

Description of changes:

Adds Unwind Attribute without Python Changes. The kani-metadata.json file now has the following structure

{
    "proof_harnesses": [
        {
            "pretty_name": "harness",
            "mangled_name": "harness",
            "original_file": "/home/ubuntu/kani/tests/cargo-kani/simple-unwind-annotation/src/main.rs",
            "original_line": "17",
            "unwind_value": 10
        },
        {
            "pretty_name": "harness_2",
            "mangled_name": "harness_2",
            "original_file": "/home/ubuntu/kani/tests/cargo-kani/simple-unwind-annotation/src/main.rs",
            "original_line": "26",
            "unwind_value": null
        }
    ]
}

Given that the rust library looks like -

#[kani::proof]
#[kani::unwind(10)]
fn harness() {
...
}

#[kani::proof]
fn harness_2() {
...
}

Update - The PR has been updated to include handling for the following scenarios, all of which produce user errors which point to suggestions on how to fix the attribute.

#[kani::proof]
#[kani::unwind(10,5)]
fn harness_3() {
...
}

#[kani::unwind(8)]
fn harness_4() {
...
}

#[kani::proof]
#[kani::proof]
fn harness_5() {
...
}

#[kani::proof(some, argument2)]
fn harness_6() {
...
}

Resolved issues:

Resolves - partly #600 and #689.

Call-outs:

The scripting needs to parse the metadata file and apply the unwind value to the target function.

Testing:

  • How is this change tested? Somewhat

  • Is this a refactor change? No

Checklist

  • Each commit message has a non-empty body, explaining why the change was made
  • Methods or procedures are documented
  • Regression or unit tests are included, or existing tests cover the modified code
  • My PR is restricted to a single feature or bugfix

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.

@jaisnan jaisnan requested a review from a team as a code owner February 19, 2022 05:29
library/kani_macros/src/lib.rs Outdated Show resolved Hide resolved
library/kani_macros/src/lib.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@adpaco-aws adpaco-aws left a comment

Choose a reason for hiding this comment

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

Unwinding values should be part of the harness metadata and not a separate struct. For me it is okay to keep it a struct (not just a u32 value) since long term we want to support unwinding values for individual loops in addition to this one.

library/kani_macros/src/lib.rs Outdated Show resolved Hide resolved
tests/cargo-kani/simple-unwind-annotation/Cargo.toml Outdated Show resolved Hide resolved
tests/cargo-kani/simple-unwind-annotation/src/main.rs Outdated Show resolved Hide resolved
tests/kani/Unwind-Attribute/fixme_lib.rs Outdated Show resolved Hide resolved
@jaisnan
Copy link
Contributor Author

jaisnan commented Mar 1, 2022

Updated the PR as per suggestions.

library/kani_macros/src/lib.rs Outdated Show resolved Hide resolved

// 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.
if proof_attribute_vector.len() == 1 {
Copy link
Contributor

Choose a reason for hiding this comment

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

These cases do not seem to overlap so I am not sure why you are using if-then-else statements here. I would also recommend handling them in small functions if possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They do overlap as it's necessary to check to how many proof attributes have been declared per harness and depending on the number, there's different handling that needs to happen. The entire function has a single purpose so it didn't seem necessary to factor it out into smaller functions.

Copy link
Contributor

Choose a reason for hiding this comment

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

@jaisnan, if I understand this code correctly, you are trying to restrict any other kani attribute to things that are tagged with the proof attribute, right?

If that is the case, I would recommend to change this to have something like the following structure:

        let attrs: kani_attributes(self.tcx.get_attrs(instance.def_id()));  // BTreeMap<&str, &rustc_ast::Attribute>
        if attrs.contains_key("proof") {
            // Handle attributes here.
        } else {
            if attrs.len() > 0 {
                // Warning: We currently only support attributes on proof harnesses. The attribute {} will be ignored.
            }
        }

Copy link
Contributor Author

@jaisnan jaisnan Mar 1, 2022

Choose a reason for hiding this comment

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

So, I am trying to restrict it to kani attribute but also use count information for each of the attributes.

I think using a BTreeMap would prevent us from checking if there are duplicate attributes in the same harness or not. If someone calls two unwind annotations then, the last one is going to be read and parsed if we use a BTreeMap I think.

We ideally should not just check if a key exists but also how many instances of them. Maybe I can use a multimap? https://docs.rs/btreemultimap/latest/btreemultimap/ ? I think using two vectors and a match statement serves these purposes well enough. If we add more options as attributes, we'd just have to expand the match statement and add a handler and that's it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's talk offline about this one. There are other things we can go to simplify the code.

@@ -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<u128>,
Copy link
Contributor

Choose a reason for hiding this comment

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

I thought we agreed having a specific struct for unwind data where this would be an optional value. Also, Option<u32> should be enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A struct didn't seem necessary for now as we're just targeting a default value for the entire harness. The struct and related changes will be added with the changes to map each loop to it's own harness.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe create a type alias here that we can extend later.

type UnwindMetadata = usize;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

do we wantusize or u32/64. Doesnt usize tie the limit of the unwind value to the target architecture?

Copy link
Contributor

Choose a reason for hiding this comment

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

I suggested usize because that's usually used to restrict structure sizes, but in practice, I highly doubt someone is going to use a large unwind parameter. So u32 is fine too.

Copy link
Contributor

Choose a reason for hiding this comment

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

IMO stay an integer. Any future unwinding information (e.g. unwindset) can be provided in separate fields. We're never going to change unwind_value because it's just the thing we pass to cbmc as --unwind and that's it.

Copy link
Contributor

@celinval celinval Mar 2, 2022

Choose a reason for hiding this comment

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

CBMC supports min and max unwind notation as well. But I do agree that if we are going to add a type, we should also change this to unwind.

Since min / max is an obvious extension here, maybe we should just add the struct and encode this as a map right away.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know why any future support for those wouldn't just add an unwind_max_value as well. There doesn't seem to be a value to nesting it. We control the producer (kani-compiler) and consumer (cargo-kani) of this structure, so if it's looks like a mistake later, we can just fix it. So simplest choice seems best to me.

But I don't feel strongly about it.

tests/cargo-kani/simple-unwind-annotation/Cargo.toml Outdated Show resolved Hide resolved
}
}

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

Choose a reason for hiding this comment

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

Why is this commented code included? If there is a reason to, add a comment explaining why.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we change these to be UI tests that we ensure that Kani prints the correct error message?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure

tests/kani/Unwind-Attribute/fixme_lib.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@celinval celinval left a comment

Choose a reason for hiding this comment

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

Thanks for doing this! I have a few suggestions to clean the code a bit. Let me know if you disagree with any of them. :)

format!("Please use '#kani[proof]' above the annotation {}", attribute_vector[0].0)
.as_str(),
);
} else {
Copy link
Contributor

Choose a reason for hiding this comment

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

When do we expect to hit this else? Should we trigger an error?

Copy link
Contributor Author

@jaisnan jaisnan Mar 1, 2022

Choose a reason for hiding this comment

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

When neither proof or any other attributes are used. This function handle_kanitool_attributes is called on all instances in codegen_function so raising an error might break a lot of cases. Hence, the empty handling.

Copy link
Contributor

Choose a reason for hiding this comment

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

Please, either remove the else or add a comment to explain what cases it is covering.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure


// 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.
if proof_attribute_vector.len() == 1 {
Copy link
Contributor

Choose a reason for hiding this comment

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

@jaisnan, if I understand this code correctly, you are trying to restrict any other kani attribute to things that are tagged with the proof attribute, right?

If that is the case, I would recommend to change this to have something like the following structure:

        let attrs: kani_attributes(self.tcx.get_attrs(instance.def_id()));  // BTreeMap<&str, &rustc_ast::Attribute>
        if attrs.contains_key("proof") {
            // Handle attributes here.
        } else {
            if attrs.len() > 0 {
                // Warning: We currently only support attributes on proof harnesses. The attribute {} will be ignored.
            }
        }

}
}

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

Choose a reason for hiding this comment

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

Can we change these to be UI tests that we ensure that Kani prints the correct error message?

library/kani_macros/src/lib.rs Show resolved Hide resolved
library/kani_macros/src/lib.rs Outdated Show resolved Hide resolved
} else {
// 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");
}
}
}

/// If the attribute is named `kanitool::name`, this extracts `name`
fn kanitool_attr_name(attr: &ast::Attribute) -> Option<String> {
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe this should return Option<&str> since we keep converting it to &str.

Copy link
Contributor Author

@jaisnan jaisnan Mar 2, 2022

Choose a reason for hiding this comment

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

Returning Option<&str> would return a dangling pointer as the original string would be destroyed once the function returns. We need to return an Owned String so I don't think we can replace the return type.
Reference - https://stackoverflow.com/questions/43079077/proper-way-to-return-a-new-string-in-rust

Copy link
Contributor

Choose a reason for hiding this comment

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

Indeed, I didn't notice that you changed the code to join the segments. For the original code, the return value would have the same lifetime as the argument attr.

BTW, why did you change it? This code might actually be doing the wrong thing. I.e., let's say you have the following attribute:

#[kanitool::type::sub_type]

I believe that you are going to return the string typesub_type.

Copy link
Contributor Author

@jaisnan jaisnan Mar 2, 2022

Choose a reason for hiding this comment

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

Maybe so, but I don't see the need to modify it to potential future attributes when there's no clear consensus on what attributes we're planning on supporting. Imo we can always change this later to adapt to future annotations.

Let me check about the scenario you've posted however.

Copy link
Contributor

Choose a reason for hiding this comment

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

Talked offline, this is no longer needed. Going to revert the change.

// Vectors for storing instances of each attribute type,
// TODO: This can be modifed to use Enums when more options are provided
let mut attribute_vector = vec![];
let mut proof_attribute_vector = vec![];
Copy link
Contributor

Choose a reason for hiding this comment

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

BTW, shouldn't this be a boolean?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need to maintain a count of the instances apart from the attributes themselves, hence vectors.

Copy link
Contributor

@celinval celinval left a comment

Choose a reason for hiding this comment

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

Here are some notes from our chat.

else if proof_attribute_vector.len() > 1 {
self.tcx
.sess
.span_err(proof_attribute_vector[0].span, "Only one Proof Attribute allowed");
Copy link
Contributor

Choose a reason for hiding this comment

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

Reduce this to warning.

_ => None,
}
}
// Return none if there are no unwind attributes or if there's too many unwind attributes
Copy link
Contributor

Choose a reason for hiding this comment

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

unwind comment in the generic function.

}

/// 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) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Anyway, not a biggie. Let's move on.

} else {
// 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");
}
}
}

/// If the attribute is named `kanitool::name`, this extracts `name`
fn kanitool_attr_name(attr: &ast::Attribute) -> Option<String> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Talked offline, this is no longer needed. Going to revert the change.

@jaisnan
Copy link
Contributor Author

jaisnan commented Mar 4, 2022

  1. Removed no_mangle
  2. Refactored the parsing into functions, added utility functions
  3. Added documentation for all user cases
  4. Added a test to check failure of harness assert instead of main.

@jaisnan jaisnan requested a review from adpaco-aws March 4, 2022 20:34
Copy link
Contributor

@tedinski tedinski left a comment

Choose a reason for hiding this comment

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

Almost there!

@@ -9,6 +9,7 @@
// RUSTFLAGS="-Zcrate-attr=feature(register_tool) -Zcrate-attr=register_tool(kanitool)"

// proc_macro::quote is nightly-only, so we'll cobble things together instead
extern crate proc_macro;
Copy link
Contributor

Choose a reason for hiding this comment

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

was this necessary?

Comment on lines 62 to 64
// 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.

Comment on lines 246 to 247
let attributes_list = self.tcx.get_attrs(self.current_fn().instance().def_id());
let (proof_attribute_vector, attribute_vector) = create_attribute_vectors(attributes_list);
Copy link
Contributor

Choose a reason for hiding this comment

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

Naming suggestion: don't put the type into the name really.

IMO, these names are clearer as "all_attributes" "proof_attributes" "other_attributes".

Comment on lines 251 to 253
if proof_attribute_count == 1 {
self.create_proof_harness(attribute_vector);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

There's a very common programming convention to write functions like:

fn foo() {
  if error condition {
    // bail, whatever
  }
  if other error {
    // bail
  }
  // etc
  // no errors:
  the conventional, expected case.
}

So try that. Spot errors, raise them and return. Then just unconditionally create_proof_harness at the end of the function (no need for any empty else). By following the convention, functions are usually a lot easier to read (if only because people expect it)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For the no-op case, can we now early return with the empty parenthesis?

Comment on lines 292 to 293
/// Update `self` (the goto context) to add the current function as a listed proof harness
fn handle_kanitool_proof(&mut self) {
fn handle_kanitool_proof(&mut self) -> HarnessMetadata {
Copy link
Contributor

Choose a reason for hiding this comment

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

Function doc comment needs updating. Also, we should rename this, maybe. "default_kanitool_proof" maybe?

Comment on lines 312 to 313
// Check if some unwind value doesnt already exist
if harness.unwind_value.is_none() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same suggestion as previous comment on handling errors, then the expected case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Going to wrap the behavior with a match statement.

Comment on lines 317 to 320
assert!(
integer_value < u32::MAX.into(),
"Value above maximum permitted value - u32::MAX"
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Good call. But we might want to span_err instead of assert.


// Create two vectors, proof_vector and attribute_vector which seperates the list of attributes into
// proof and non-proof for easier parsing
fn create_attribute_vectors(
Copy link
Contributor

Choose a reason for hiding this comment

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

Rename partition_kanitool_attributes maybe?

Second suggestion: try improving the doc-comment enough that all the comments in the body aren't necessary anymore. This function is small enough this is possible and would very likely be an improvement.

// 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_metadata = self.handle_kanitool_proof();
if attribute_vector.len() > 0 {
Copy link
Contributor

Choose a reason for hiding this comment

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

unnecessary if

"unwind" => {
self.handle_kanitool_unwind(attribute_tuple.1, &mut harness_metadata)
}
_ => {}
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe we could error or warn?

@jaisnan jaisnan requested a review from tedinski March 7, 2022 17:55
}

#[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

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

Copy link
Contributor

@tedinski tedinski left a comment

Choose a reason for hiding this comment

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

Assuming CI passes, looks largely good. Maybe one last typo.

library/kani_macros/src/lib.rs Outdated Show resolved Hide resolved
@jaisnan jaisnan dismissed adpaco-aws’s stale review March 7, 2022 20:32

addressed changes requested

@jaisnan jaisnan merged commit 7152657 into model-checking:main Mar 7, 2022
tedinski pushed a commit to tedinski/rmc that referenced this pull request Apr 22, 2022
* Unwind Attribute without Python Changes
tedinski pushed a commit to tedinski/rmc that referenced this pull request Apr 25, 2022
* Unwind Attribute without Python Changes
tedinski pushed a commit to tedinski/rmc that referenced this pull request Apr 26, 2022
* Unwind Attribute without Python Changes
tedinski pushed a commit that referenced this pull request Apr 27, 2022
* Unwind Attribute without Python Changes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants