Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3342,6 +3342,9 @@ dependencies = [
[[package]]
name = "rmc"
version = "0.1.0"
dependencies = [
"rmc_macros",
]

[[package]]
name = "rmc-link-restrictions"
Expand All @@ -3353,6 +3356,10 @@ dependencies = [
"serde_json",
]

[[package]]
name = "rmc_macros"
version = "0.1.0"

[[package]]
name = "rmc_restrictions"
version = "0.1.0"
Expand Down
14 changes: 14 additions & 0 deletions compiler/cbmc/src/goto_program/location.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ impl Location {
}
}

pub fn filename(&self) -> Option<String> {
match self {
Copy link
Contributor

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 }

Location::Loc { file, .. } => Some(file.to_string()),
_ => None,
}
}

pub fn line(&self) -> Option<u64> {
match self {
Location::Loc { line, .. } => Some(*line),
_ => None,
}
}

/// Convert a location to a short string suitable for (e.g.) logging.
/// Goal is to return just "file:line" as clearly as possible.
pub fn short_string(&self) -> String {
Expand Down
49 changes: 49 additions & 0 deletions compiler/rustc_codegen_rmc/src/codegen/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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(&current_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,
}
}
20 changes: 12 additions & 8 deletions compiler/rustc_codegen_rmc/src/compiler_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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>,
}

Expand Down Expand Up @@ -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,
);
Expand All @@ -144,11 +145,14 @@ impl CodegenBackend for GotocCodegenBackend {
None
};

let metadata = RmcMetadata { proof_harnesses: c.proof_harnesses };
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we 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.

(I'm not sure what you mean by 'this'. The RmcMetadata type?) I'm planning on the rmc-metadata file becoming where we emit everything that isn't the symtab. So type_map, function restrictions, and more things like notable/important warnings (instead of just logging them).

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, I mean the copy statement.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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,
})
}

Expand Down Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_codegen_rmc/src/context/goto_ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
//! 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 crate::overrides::{fn_hooks, GotocHooks};
use crate::utils::full_crate_name;
Expand Down Expand Up @@ -53,6 +54,7 @@ pub struct GotocCtx<'tcx> {
pub vtable_ctx: VtableCtx,
pub current_fn: Option<CurrentFnCtx<'tcx>>,
pub type_map: FxHashMap<InternedString, Ty<'tcx>>,
pub proof_harnesses: Vec<HarnessMetadata>,
}

/// Constructor
Expand All @@ -72,6 +74,7 @@ impl<'tcx> GotocCtx<'tcx> {
vtable_ctx: VtableCtx::new(emit_vtable_restrictions),
current_fn: None,
type_map: FxHashMap::default(),
proof_harnesses: vec![],
}
}
}
Expand Down
26 changes: 26 additions & 0 deletions compiler/rustc_codegen_rmc/src/context/metadata.rs
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>,
}
1 change: 1 addition & 0 deletions compiler/rustc_codegen_rmc/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

mod current_fn;
mod goto_ctx;
pub mod metadata;
mod vtable_ctx;

pub use goto_ctx::GotocCtx;
Expand Down
1 change: 1 addition & 0 deletions library/rmc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ edition = "2018"
license = "MIT OR Apache-2.0"

[dependencies]
rmc_macros = { path = "../rmc_macros" }
3 changes: 3 additions & 0 deletions library/rmc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,6 @@ pub fn nondet<T>() -> T {
#[inline(never)]
#[rustc_diagnostic_item = "RmcExpectFail"]
pub fn expect_fail(_cond: bool, _message: &str) {}

/// RMC proc macros must be in a separate crate
pub use rmc_macros::*;
13 changes: 13 additions & 0 deletions library/rmc_macros/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 = "rmc_macros"
version = "0.1.0"
edition = "2018"
license = "MIT OR Apache-2.0"

[lib]
proc-macro = true

[dependencies]
51 changes: 51 additions & 0 deletions library/rmc_macros/src/lib.rs
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
// )
}
17 changes: 16 additions & 1 deletion scripts/rmc-rustc
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ set_rmc_lib_path() {
fi
RMC_LIB_PATH=$(dirname ${RMC_LIB_CANDIDATES[0]})
fi
# Having set RMC_LIB_PATH, find RMC_MACRO_LIB_PATH
local MACRO_LIB_CANDIDATE=(${RMC_LIB_PATH}/deps/librmc_macros-*)
if [[ ${#MACRO_LIB_CANDIDATE[@]} -ne 1 ]]
then
echo "ERROR: Could not find RMC library."
echo "Looked for: \"${RMC_LIB_PATH}/deps/librmc_macros-*\""
echo "Was RMC library successfully built first?"
exit 1
fi
RMC_MACRO_LIB_PATH="${MACRO_LIB_CANDIDATE[0]}"
}

set_rmc_path
Expand All @@ -58,8 +68,13 @@ else
-Z trim-diagnostic-paths=no \
-Z human_readable_cgu_names \
--cfg=rmc \
-Zcrate-attr=feature(register_tool) \
-Zcrate-attr=register_tool(rmctool) \
-L ${RMC_LIB_PATH} \
--extern rmc"
--extern rmc \
-L ${RMC_LIB_PATH}/deps \
--extern librmc_macros=${RMC_MACRO_LIB_PATH} \
"
if [ "${1:-''}" == "--rmc-flags" ]
then
echo ${RMC_FLAGS}
Expand Down
1 change: 1 addition & 0 deletions scripts/rmc.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ def compile_single_rust_file(
if not extra_args.keep_temps:
atexit.register(delete_file, output_filename)
atexit.register(delete_file, base + ".type_map.json")
atexit.register(delete_file, base + ".rmc-metadata.json")

build_cmd = [RMC_RUSTC_EXE] + rustc_flags(extra_args.mangler, symbol_table_passes,
extra_args.restrict_vtable)
Expand Down
1 change: 1 addition & 0 deletions scripts/setup/build_rmc_lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ SCRIPTS_DIR="$(dirname $SCRIPT_DIR)"
REPO_DIR="$(dirname $SCRIPTS_DIR)"

export RUSTC=$(${SCRIPTS_DIR}/rmc-rustc --rmc-path)
export RUSTFLAGS="--cfg rmc"
cargo clean --manifest-path "${REPO_DIR}/library/rmc/Cargo.toml" $@
cargo build --manifest-path "${REPO_DIR}/library/rmc/Cargo.toml" $@

9 changes: 9 additions & 0 deletions src/test/cargo-rmc/simple-proof-annotation/Cargo.toml
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]
1 change: 1 addition & 0 deletions src/test/cargo-rmc/simple-proof-annotation/main.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
line 5 assertion failed: 1 == 2: FAILURE
18 changes: 18 additions & 0 deletions src/test/cargo-rmc/simple-proof-annotation/src/main.rs
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);
}