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 1 commit
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: 27 additions & 0 deletions library/kani_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?

use proc_macro::TokenStream;

#[cfg(all(not(kani), not(test)))]
Expand Down Expand Up @@ -49,3 +50,29 @@ pub fn proof(_attr: TokenStream, item: TokenStream) -> TokenStream {
// $item
// )
}

#[cfg(not(kani))]
#[proc_macro_attribute]
pub fn unwind(_attr: TokenStream, _item: TokenStream) -> TokenStream {
// Not-RMC, Not-Test means this code shouldn't exist, return nothing.
jaisnan marked this conversation as resolved.
Show resolved Hide resolved
TokenStream::new()
}

#[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_] for easier handling
tedinski marked this conversation as resolved.
Show resolved Hide resolved
let insert_string = "#[kanitool::unwind_".to_owned() + &attr.clone().to_string() + "]";

// Add the string that looks like - #[kanitool::unwind_value_]
result.extend(insert_string.parse::<TokenStream>().unwrap());
// No mangle seems to be necessary as removing it prevents all the attributes in a lib from being read
jaisnan marked this conversation as resolved.
Show resolved Hide resolved
result.extend("#[no_mangle]".parse::<TokenStream>().unwrap());
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we still need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, not anymore. let me update that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Update : We do need this. Without it, all unwind annotations without kani::proof just get ignored and this prevents us from showing an error to the user. IMO, that would confuse the user as they would not know what the correct syntax for the annotation is. So, I think we should keep no_mangle on.

Copy link
Contributor

Choose a reason for hiding this comment

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

No mangle has a side effect of making a function public and potentially causing name conflicts (E.g.: rust-lang/rust#28179).

BTW, how did you test this behavior? Can you please make sure that the function you are annotating isn't dead code. Feel free to ping me offline.

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. We'll remove this.


result.extend(item);
result

// / _attr
}
59 changes: 52 additions & 7 deletions src/kani-compiler/rustc_codegen_kani/src/codegen/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

//! This file contains functions related to codegenning MIR functions into gotoc

use crate::context::metadata::HarnessMetadata;
use crate::context::metadata::{HarnessMetadata, UnwindMetadata};
use crate::GotocCtx;
use cbmc::goto_program::{Expr, Stmt, Symbol};
use cbmc::InternString;
Expand All @@ -13,6 +13,8 @@ use rustc_middle::ty::{self, Instance, TyS};
use std::collections::BTreeMap;
use std::iter::FromIterator;
use tracing::{debug, warn};
extern crate regex;
use regex::Regex;

/// Utility to skip functions that can't currently be successfully codgenned.
impl<'tcx> GotocCtx<'tcx> {
Expand Down Expand Up @@ -243,11 +245,33 @@ impl<'tcx> GotocCtx<'tcx> {
fn handle_kanitool_attributes(&mut self) {
let instance = self.current_fn().instance();

// This regex can be expanded as more attributes become available
// Can be thought as a menu, in expanded mode
let input_re = Regex::new(
r#"(?x)
(proof) |
(unwind_)(\d+)
"#,
)
.unwrap();

for attr in self.tcx.get_attrs(instance.def_id()) {
match kanitool_attr_name(attr).as_deref() {
Some("proof") => self.handle_kanitool_proof(),
_ => {}
}
if let Some(attribute_string) = kanitool_attr_name(attr).as_deref() {
// This regex capture operation returns a vector of strings that can be matched for our purpose
// In later Attributes, it will be easy to parse the regex for the attribute arguments, regardless
// of the type of the argument itself if we capture all the patterns as a vector of strings of the format
// [command, argument]
let captures = input_re.captures(attribute_string).map(|captures| {
jaisnan marked this conversation as resolved.
Show resolved Hide resolved
captures.iter().skip(1).flat_map(|c| c).map(|c| c.as_str()).collect::<Vec<_>>()
});

// Match against the captured values as a slice
match captures.as_ref().map(|c| c.as_slice()) {
Some(["proof"]) => self.handle_kanitool_proof(),
Some(["unwind_", x]) => self.handle_kanitool_unwind(x),
_ => {}
}
};
}
}

Expand All @@ -267,15 +291,36 @@ impl<'tcx> GotocCtx<'tcx> {

self.proof_harnesses.push(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, attribute_string: &str) {
// Parse parameter string for value of the argument
let argument_value: u32 =
attribute_string.parse().expect("Unwind Argument Value is not a number");

// Make sure the value lies between 0 and u32 max
assert!(argument_value < u32::MAX);

let current_fn = self.current_fn();
let mangled_name = current_fn.name();

let unwind_instance =
UnwindMetadata { mangled_name: mangled_name, unwind_value: argument_value };
self.unwind_metadata.push(unwind_instance);
}
}

/// 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.

match &attr.kind {
ast::AttrKind::Normal(ast::AttrItem { path: ast::Path { segments, .. }, .. }, _)
jaisnan marked this conversation as resolved.
Show resolved Hide resolved
if segments.len() == 2 && segments[0].ident.as_str() == "kanitool" =>
if (!segments.is_empty()) && segments[0].ident.as_str() == "kanitool" =>
jaisnan marked this conversation as resolved.
Show resolved Hide resolved
{
Some(segments[1].ident.as_str().to_string())
let mut new_string = String::new();
for index in 1..segments.len() {
new_string.push_str(segments[index].ident.as_str());
}
Some(new_string)
}
_ => None,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ impl CodegenBackend for GotocCodegenBackend {
None
};

let metadata = KaniMetadata { proof_harnesses: c.proof_harnesses };
let metadata =
KaniMetadata { proof_harnesses: c.proof_harnesses, unwind_metadata: c.unwind_metadata };

// No output should be generated if user selected no_codegen.
if !tcx.sess.opts.debugging_opts.no_codegen && tcx.sess.opts.output_types.should_codegen() {
Expand Down
6 changes: 4 additions & 2 deletions src/kani-compiler/rustc_codegen_kani/src/context/goto_ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
//! Any MIR specific functionality (e.g. codegen etc) should live in specialized files that use
//! this structure as input.
use super::current_fn::CurrentFnCtx;
use super::metadata::HarnessMetadata;
use super::vtable_ctx::VtableCtx;
use super::metadata::{HarnessMetadata, UnwindMetadata};
use crate::overrides::{fn_hooks, GotocHooks};
use crate::utils::full_crate_name;
use crate::VtableCtx;
use cbmc::goto_program::{DatatypeComponent, Expr, Location, Stmt, Symbol, SymbolTable, Type};
use cbmc::utils::aggr_tag;
use cbmc::{InternStringOption, InternedString, NO_PRETTY_NAME};
Expand Down Expand Up @@ -59,6 +59,7 @@ pub struct GotocCtx<'tcx> {
pub current_fn: Option<CurrentFnCtx<'tcx>>,
pub type_map: FxHashMap<InternedString, Ty<'tcx>>,
pub proof_harnesses: Vec<HarnessMetadata>,
pub unwind_metadata: Vec<UnwindMetadata>,
jaisnan marked this conversation as resolved.
Show resolved Hide resolved
/// a global counter for generating unique IDs for checks
pub global_checks_count: u64,
}
Expand All @@ -82,6 +83,7 @@ impl<'tcx> GotocCtx<'tcx> {
current_fn: None,
type_map: FxHashMap::default(),
proof_harnesses: vec![],
unwind_metadata: vec![],
global_checks_count: 0,
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/kani-compiler/rustc_codegen_kani/src/context/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,19 @@ pub struct HarnessMetadata {
pub original_line: String,
}

/// We emit this struct for every unwind we find

#[derive(Serialize)]
pub struct UnwindMetadata {
/// The name of the function in the CBMC symbol table, being used as a unique identifer.
pub mangled_name: String,
/// The value of the unwind attribute that the user wants to set
pub unwind_value: u32,
}

/// The structure of `.kani-metadata.json` files, which are emitted for each crate
#[derive(Serialize)]
pub struct KaniMetadata {
pub proof_harnesses: Vec<HarnessMetadata>,
pub unwind_metadata: Vec<UnwindMetadata>,
tedinski marked this conversation as resolved.
Show resolved Hide resolved
}
12 changes: 12 additions & 0 deletions tests/cargo-kani/simple-unwind-annotation/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# 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"
jaisnan marked this conversation as resolved.
Show resolved Hide resolved
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 @@
line 5 assertion failed: 1 == 2: FAILURE
41 changes: 41 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,41 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR MIT

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

// rmc-flags: --no-unwinding-checks

// 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.
jaisnan marked this conversation as resolved.
Show resolved Hide resolved

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

#[kani::unwind(8)]
fn harness_2() {
let mut counter = 0;
for i in 0..7 {
counter += 1;
assert!(counter < 5);
}
}

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

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

// rmc-flags: --no-unwinding-checks

// Fix me
jaisnan marked this conversation as resolved.
Show resolved Hide resolved
#[kani::proof]
#[kani::unwind(10)]
fn harness() {
let mut counter = 0;
loop {
counter += 1;
assert!(counter < 10);
}
}

#[kani::unwind(8)]
fn harness_2() {
let mut counter = 0;
for i in 0..7 {
counter += 1;
assert!(counter < 5);
}
}

#[kani::unwind(9)]
fn harness_3() {
let mut counter = 0;
for i in 0..10 {
counter += 1;
assert!(counter < 8);
}
}