-
Notifications
You must be signed in to change notification settings - Fork 129
Introduce rmc::proof function attribute #668
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
Changes from all commits
1502f02
ea2be69
ee317ea
e4de683
37d6aa2
1446d36
76081aa
00788d2
9709a0b
2377945
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,8 +3,10 @@ | |
|
|
||
| //! This file contains functions related to codegenning MIR functions into gotoc | ||
|
|
||
| use crate::context::metadata::HarnessMetadata; | ||
| use crate::GotocCtx; | ||
| use cbmc::goto_program::{Expr, Stmt, Symbol}; | ||
| use rustc_ast::ast; | ||
| use rustc_middle::mir::{HasLocalDecls, Local}; | ||
| use rustc_middle::ty::{self, Instance, TyS}; | ||
| use tracing::{debug, warn}; | ||
|
|
@@ -98,6 +100,8 @@ impl<'tcx> GotocCtx<'tcx> { | |
| let stmts = self.current_fn_mut().extract_block(); | ||
| let body = Stmt::block(stmts, loc); | ||
| self.symbol_table.update_fn_declaration_with_definition(&name, body); | ||
|
|
||
| self.handle_rmctool_attributes(); | ||
| } | ||
| self.reset_current_fn(); | ||
| } | ||
|
|
@@ -199,4 +203,49 @@ impl<'tcx> GotocCtx<'tcx> { | |
| }); | ||
| self.reset_current_fn(); | ||
| } | ||
|
|
||
| /// 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. `#[rmc::proof]` (which rmc_macros translates to `#[rmctool::proof]` for us to handle here) | ||
| fn handle_rmctool_attributes(&mut self) { | ||
| let instance = self.current_fn().instance(); | ||
|
|
||
| for attr in self.tcx.get_attrs(instance.def_id()) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you please add a debug statement? |
||
| match rmctool_attr_name(attr).as_deref() { | ||
| Some("proof") => self.handle_rmctool_proof(), | ||
| _ => {} | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Update `self` (the goto context) to add the current function as a listed proof harness | ||
| fn handle_rmctool_proof(&mut self) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the plan for proof options like unwind? Are you thinking about adding arguments to this attribute or creating new attributes?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm thinking add arguments to this attribute, yes. |
||
| let current_fn = self.current_fn(); | ||
| let pretty_name = current_fn.readable_name().to_owned(); | ||
| let mangled_name = current_fn.name(); | ||
| let loc = self.codegen_span(¤t_fn.mir().span); | ||
|
|
||
| let harness = HarnessMetadata { | ||
| pretty_name, | ||
| mangled_name, | ||
| original_file: loc.filename().unwrap(), | ||
| original_line: loc.line().unwrap().to_string(), | ||
| }; | ||
|
|
||
| self.proof_harnesses.push(harness); | ||
| } | ||
| } | ||
|
|
||
| /// If the attribute is named `rmctool::name`, this extracts `name` | ||
| fn rmctool_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() == "rmctool" => | ||
| { | ||
| Some(segments[1].ident.as_str().to_string()) | ||
| } | ||
| _ => None, | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,8 +3,8 @@ | |
|
|
||
| //! This file contains the code necessary to interface with the compiler backend | ||
|
|
||
| use crate::context::metadata::RmcMetadata; | ||
| use crate::GotocCtx; | ||
|
|
||
| use bitflags::_core::any::Any; | ||
| use cbmc::goto_program::symtab_transformer; | ||
| use cbmc::goto_program::SymbolTable; | ||
|
|
@@ -29,9 +29,10 @@ use tracing::{debug, warn}; | |
|
|
||
| // #[derive(RustcEncodable, RustcDecodable)] | ||
| pub struct GotocCodegenResult { | ||
| pub type_map: BTreeMap<InternedString, InternedString>, | ||
| pub symtab: SymbolTable, | ||
| pub crate_name: rustc_span::Symbol, | ||
| pub metadata: RmcMetadata, | ||
| pub symtab: SymbolTable, | ||
| pub type_map: BTreeMap<InternedString, InternedString>, | ||
| pub vtable_restrictions: Option<VtableCtxResults>, | ||
| } | ||
|
|
||
|
|
@@ -128,7 +129,7 @@ impl CodegenBackend for GotocCodegenBackend { | |
| } | ||
|
|
||
| // perform post-processing symbol table passes | ||
| let symbol_table = symtab_transformer::do_passes( | ||
| let symtab = symtab_transformer::do_passes( | ||
| c.symbol_table, | ||
| &tcx.sess.opts.debugging_opts.symbol_table_passes, | ||
| ); | ||
|
|
@@ -144,11 +145,14 @@ impl CodegenBackend for GotocCodegenBackend { | |
| None | ||
| }; | ||
|
|
||
| let metadata = RmcMetadata { proof_harnesses: c.proof_harnesses }; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need this?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (I'm not sure what you mean by 'this'. The RmcMetadata type?) I'm planning on the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I mean the copy statement.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still not sure what the "copy statement" is. |
||
|
|
||
| Box::new(GotocCodegenResult { | ||
| type_map, | ||
| symtab: symbol_table, | ||
| crate_name: tcx.crate_name(LOCAL_CRATE) as rustc_span::Symbol, | ||
| vtable_restrictions: vtable_restrictions, | ||
| metadata, | ||
| symtab, | ||
| type_map, | ||
| vtable_restrictions, | ||
| }) | ||
| } | ||
|
|
||
|
|
@@ -176,7 +180,7 @@ impl CodegenBackend for GotocCodegenBackend { | |
| let base_filename = outputs.path(OutputType::Object); | ||
| write_file(&base_filename, "symtab.json", &result.symtab); | ||
| write_file(&base_filename, "type_map.json", &result.type_map); | ||
|
|
||
| write_file(&base_filename, "rmc-metadata.json", &result.metadata); | ||
| // If they exist, write out vtable virtual call function pointer restrictions | ||
| if let Some(restrictions) = result.vtable_restrictions { | ||
| write_file(&base_filename, "restrictions.json", &restrictions); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 OR MIT | ||
|
|
||
| //! This module should be factored out into its own separate crate eventually, | ||
| //! but leaving it here for now... | ||
|
|
||
| use serde::Serialize; | ||
|
|
||
| /// We emit this structure for each annotated proof harness we find | ||
| #[derive(Serialize)] | ||
| pub struct HarnessMetadata { | ||
| /// The name the user gave to the function | ||
| pub pretty_name: String, | ||
| /// The name of the function in the CBMC symbol table | ||
| pub mangled_name: String, | ||
| /// The (currently full-) path to the file this proof harness was declared within | ||
| pub original_file: String, | ||
| /// The line in that file where the proof harness begins | ||
| pub original_line: String, | ||
| } | ||
|
|
||
| /// The structure of `.rmc-metadata.json` files, which are emitted for each crate | ||
| #[derive(Serialize)] | ||
| pub struct RmcMetadata { | ||
| pub proof_harnesses: Vec<HarnessMetadata>, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |
|
|
||
| mod current_fn; | ||
| mod goto_ctx; | ||
| pub mod metadata; | ||
| mod vtable_ctx; | ||
|
|
||
| pub use goto_ctx::GotocCtx; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,3 +8,4 @@ edition = "2018" | |
| license = "MIT OR Apache-2.0" | ||
|
|
||
| [dependencies] | ||
| rmc_macros = { path = "../rmc_macros" } | ||
| 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 = "rmc_macros" | ||
| version = "0.1.0" | ||
| edition = "2018" | ||
| license = "MIT OR Apache-2.0" | ||
|
|
||
| [lib] | ||
| proc-macro = true | ||
|
|
||
| [dependencies] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 OR MIT | ||
|
|
||
| // #![feature(register_tool)] | ||
| // #![register_tool(rmctool)] | ||
| // Frustratingly, it's not enough for our crate to enable these features, because we need all | ||
| // downstream crates to enable these features as well. | ||
| // So we have to enable this on the commandline (see rmc-rustc) with: | ||
| // RUSTFLAGS="-Zcrate-attr=feature(register_tool) -Zcrate-attr=register_tool(rmctool)" | ||
|
|
||
| // proc_macro::quote is nightly-only, so we'll cobble things together instead | ||
| use proc_macro::TokenStream; | ||
|
|
||
| #[cfg(all(not(rmc), not(test)))] | ||
| #[proc_macro_attribute] | ||
| pub fn proof(_attr: TokenStream, item: TokenStream) -> TokenStream { | ||
| // Not-RMC, Not-Test means this code shouldn't exist, return nothing. | ||
| TokenStream::new() | ||
| } | ||
|
|
||
| #[cfg(all(not(rmc), test))] | ||
| #[proc_macro_attribute] | ||
| pub fn proof(_attr: TokenStream, item: TokenStream) -> TokenStream { | ||
| // Leave the code intact, so it can be easily be edited in an IDE, | ||
| // but outside RMC, this code is likely never called. | ||
| let mut result = TokenStream::new(); | ||
|
|
||
| result.extend("#[allow(dead_code)]".parse::<TokenStream>().unwrap()); | ||
| result.extend(item); | ||
| result | ||
| // quote!( | ||
| // #[allow(dead_code)] | ||
| // $item | ||
| // ) | ||
| } | ||
|
|
||
| #[cfg(rmc)] | ||
| #[proc_macro_attribute] | ||
| pub fn proof(_attr: TokenStream, item: TokenStream) -> TokenStream { | ||
| let mut result = TokenStream::new(); | ||
|
|
||
| result.extend("#[rmctool::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()); | ||
| result.extend(item); | ||
| result | ||
| // quote!( | ||
| // #[rmctool::proof] | ||
| // $item | ||
| // ) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 OR MIT | ||
|
|
||
| [package] | ||
| name = "simple-proof-annotation" | ||
| version = "0.1.0" | ||
| edition = "2021" | ||
|
|
||
| [dependencies] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| line 5 assertion failed: 1 == 2: FAILURE |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 OR MIT | ||
|
|
||
| fn main() { | ||
| assert!(1 == 2); | ||
| } | ||
|
|
||
| // NOTE: Currently the below is not detected or run by this test! | ||
|
|
||
| // 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. | ||
|
|
||
| #[rmc::proof] | ||
| fn harness() { | ||
| assert!(3 == 4); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a suggestion. This could be a one liner:
if let Location::Loc { file, .. } = self { Some(file.to_string) } else { None }