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

modifies Clauses for Function Contracts #2800

Merged
merged 80 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
2e6bed3
WIP
JustusAdam Sep 30, 2023
c772a10
Sketch for new modifies clause
JustusAdam Sep 30, 2023
ed2e79d
Add explicit lifetimes to arguments and reorder code
JustusAdam Oct 1, 2023
5a8f746
Sketch for the internal mechanisms
JustusAdam Oct 1, 2023
2759469
Debugging proc-macro code
JustusAdam Oct 2, 2023
9dd55a4
Fixed the assigns bug
JustusAdam Nov 2, 2023
ba27ab6
Using lifetime decoupling for `modifies`
JustusAdam Nov 2, 2023
bbc4e99
Fix lifetime decoupling trait
JustusAdam Nov 3, 2023
00157cb
Make assigns existence optional again
JustusAdam Nov 3, 2023
3db104d
Merge remote-tracking branch 'fork/main' into new-assigns-check
JustusAdam Nov 3, 2023
559cf81
Reviving havoc protection for reentry tracker
JustusAdam Nov 3, 2023
f6d939c
Fixed recursion tracker havocking
JustusAdam Nov 7, 2023
187c56f
Fmt
JustusAdam Nov 11, 2023
54727e2
Cargo fix
JustusAdam Nov 12, 2023
b7c8e63
Fix two test cases + support impl call
JustusAdam Nov 14, 2023
27ebf4f
Fix another test case
JustusAdam Nov 14, 2023
d9e921c
Simple havoc
JustusAdam Nov 14, 2023
ffcdc76
Fixing Formatting and clippy
JustusAdam Nov 19, 2023
b833438
Kani fmt
JustusAdam Nov 19, 2023
64f7e5f
Fix simlpe havoc
JustusAdam Nov 21, 2023
0c3cea7
Fix static exclude for recursion tracker
JustusAdam Nov 22, 2023
4b451cf
Some changes had accidentally been reverted
JustusAdam Nov 22, 2023
0b6ba1e
Formatting
JustusAdam Nov 22, 2023
9362624
Allow pointer transmutes
JustusAdam Nov 22, 2023
7148bac
turns out these need to be refs
JustusAdam Nov 22, 2023
ba4a73e
Gracefully handle if the contract artifact is absent
JustusAdam Nov 22, 2023
9610a9e
Clippy ...
JustusAdam Nov 22, 2023
03ef3ce
Ignoring this error for now
JustusAdam Nov 23, 2023
7b788c7
Changing how contracts communicate
JustusAdam Nov 29, 2023
904ef0e
Merge remote-tracking branch 'fork/main' into new-assigns-check
JustusAdam Nov 29, 2023
981a465
Formatting
JustusAdam Nov 29, 2023
f9b4ef3
Fix Warnings
JustusAdam Nov 29, 2023
12a993d
Consistent use of file paths for recursion tracker
JustusAdam Nov 30, 2023
1843442
Simplify check function editingˆ
JustusAdam Nov 30, 2023
00518b0
Fix ordering for havoc and postconditions
JustusAdam Nov 30, 2023
862a5d7
Extra test cases
JustusAdam Nov 30, 2023
e229ad0
Formatting
JustusAdam Nov 30, 2023
9d95def
Grouping test cases and adding more
JustusAdam Dec 1, 2023
15a596b
Documentation, dead code removal and unique arguments in `modifies`
JustusAdam Dec 1, 2023
af780f1
Test case for unique generated argument names
JustusAdam Dec 1, 2023
e76d0b3
Added missing `expected` files
JustusAdam Dec 1, 2023
907a7f9
Another missing `expected` file
JustusAdam Dec 1, 2023
1c71508
Revert some clippy changes
JustusAdam Dec 1, 2023
e4f3cfd
Remove not strictly necessary dep
JustusAdam Dec 1, 2023
9f3d102
Some basic write-sets documentation
JustusAdam Dec 1, 2023
0573d09
Missing formatting
JustusAdam Dec 1, 2023
384c2f6
Forgot to commit this one
JustusAdam Dec 1, 2023
f7578a8
Typo
JustusAdam Dec 1, 2023
1fffd53
Tinkering with the vector test case.
JustusAdam Dec 4, 2023
34e3a1b
Addressing code review
JustusAdam Dec 14, 2023
f01b619
Apply suggestions from code review
JustusAdam Dec 14, 2023
f283759
Addressing code review leftovers
JustusAdam Dec 14, 2023
6192f3e
Merge remote-tracking branch 'origin/new-assigns-check' into new-assi…
JustusAdam Dec 14, 2023
00bfe6e
Added missing expected file
JustusAdam Dec 14, 2023
9c6b876
Fixed the expected file
JustusAdam Dec 15, 2023
a5dfccd
Added test cases for global variable modifications
JustusAdam Dec 15, 2023
c5b6a2b
Apply suggestions from code review
JustusAdam Dec 23, 2023
6892afc
Suggestions from @feliperodri
JustusAdam Dec 23, 2023
0819432
Merge remote-tracking branch 'origin/new-assigns-check' into new-assi…
JustusAdam Dec 23, 2023
55c5583
Merge remote-tracking branch 'fork/main' into new-assigns-check
JustusAdam Dec 25, 2023
455373c
Update contract code to stable MIR
JustusAdam Dec 26, 2023
68a7f6e
Merge remote-tracking branch 'origin/new-assigns-check' into new-assi…
JustusAdam Dec 26, 2023
0980cbc
Addressing code review comments
JustusAdam Jan 29, 2024
fac45b6
Copyright
JustusAdam Jan 29, 2024
96d4b12
Clippy suggestions
JustusAdam Jan 29, 2024
63d2d7a
Merge remote-tracking branch 'fork/main' into new-assigns-check
JustusAdam Jan 29, 2024
d105b87
Update error reporting
JustusAdam Jan 29, 2024
5eab701
Fixing manes used by contracts metadata
JustusAdam Jan 30, 2024
4bd7583
Fix test case
JustusAdam Jan 30, 2024
0a4503a
Formatting
JustusAdam Jan 31, 2024
6414e22
Clippy complained
JustusAdam Jan 31, 2024
6bbbb24
Whoops
JustusAdam Jan 31, 2024
9b17d94
Name the other Rc test case 'fixme'
JustusAdam Jan 31, 2024
52e703a
Forgot that this now needs formatting
JustusAdam Feb 1, 2024
40e3abe
Used the wrong name
JustusAdam Feb 1, 2024
8db7d20
Merge branch 'main' into new-assigns-check
feliperodri Feb 1, 2024
75a554d
Suggestions from code review
JustusAdam Feb 2, 2024
edd7bf8
Forgot to stage after renaming
JustusAdam Feb 2, 2024
7d08810
Moving all the contract communication logic into the compiler
JustusAdam Feb 2, 2024
1807020
Wrong import
JustusAdam Feb 2, 2024
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
Prev Previous commit
Next Next commit
Addressing code review comments
Moved contracts code gen functions to separate module
Moved `Pointer` trait to `internal` module
Remove channel based modifies contract communication
  • Loading branch information
JustusAdam committed Jan 29, 2024
commit 0980cbc20722ff13ad88a035cbb082bdf14a8e07
12 changes: 9 additions & 3 deletions cprover_bindings/src/irep/to_irep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,15 +504,21 @@ impl ToIrep for SwitchCase {
}

impl ToIrep for Lambda {
/// At the moment this function assumes that this lambda is used for a
/// `modifies` contract. It should work for any other lambda body, but
/// the parameter names use "modifies" in their generated names.
fn to_irep(&self, mm: &MachineModel) -> Irep {
let (ops_ireps, types) = self
.arguments
.iter()
.map(|param| {
.enumerate()
.map(|(index, param)| {
let ty_rep = param.typ().to_irep(mm);
(
Irep::symbol(param.identifier().unwrap_or("_".into()))
.with_named_sub(IrepId::Type, ty_rep.clone()),
Irep::symbol(
param.identifier().unwrap_or_else(|| format!("_modifies_{index}").into()),
)
.with_named_sub(IrepId::Type, ty_rep.clone()),
ty_rep,
)
})
Expand Down
117 changes: 117 additions & 0 deletions kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use crate::codegen_cprover_gotoc::GotocCtx;
use crate::kani_middle::attributes::KaniAttributes;
use cbmc::goto_program::FunctionContract;
use cbmc::goto_program::Lambda;
use kani_metadata::AssignsContract;
use rustc_hir::def_id::DefId as InternalDefId;
use rustc_smir::rustc_internal;
use stable_mir::mir::mono::{Instance, MonoItem};
use stable_mir::mir::Local;
use stable_mir::CrateDef;
use tracing::debug;

impl<'tcx> GotocCtx<'tcx> {
/// Given the `proof_for_contract` target `function_under_contract` and the reachable `items`,
/// find or create the `AssignsContract` that needs to be enforced and attach it to the symbol
/// for which it needs to be enforced.
///
/// 1. Gets the `#[kanitool::inner_check = "..."]` target, then resolves exactly one instance
/// of it. Panics if there are more or less than one instance.
/// 2. Expects that a `#[kanitool::modifies(...)]` is placed on the `inner_check` function,
/// turns it into a CBMC contract and attaches it to the symbol for the previously resolved
/// instance.
/// 3. Returns the mangled name of the symbol it attached the contract to.
/// 4. Resolves the `#[kanitool::checked_with = "..."]` target from `function_under_contract`
/// which has `static mut REENTRY : bool` declared inside.
/// 5. Returns the full path to this constant that `--nondet-static-exclude` expects which is
/// comprised of the file path that `checked_with` is located in, the name of the
/// `checked_with` function and the name of the constant (`REENTRY`).
pub fn handle_check_contract(
&mut self,
function_under_contract: InternalDefId,
items: &[MonoItem],
) -> AssignsContract {
let tcx = self.tcx;
let function_under_contract_attrs = KaniAttributes::for_item(tcx, function_under_contract);
let wrapped_fn = function_under_contract_attrs.inner_check().unwrap().unwrap();

let mut instance_under_contract = items.iter().filter_map(|i| match i {
MonoItem::Fn(instance @ Instance { def, .. })
if wrapped_fn == rustc_internal::internal(def.def_id()) =>
{
Some(instance.clone())
}
_ => None,
});
let instance_of_check = instance_under_contract.next().unwrap();
assert!(
instance_under_contract.next().is_none(),
"Only one instance of a checked function may be in scope"
);
let attrs_of_wrapped_fn = KaniAttributes::for_item(tcx, wrapped_fn);
let assigns_contract = attrs_of_wrapped_fn.modifies_contract().unwrap_or_else(|| {
debug!(?instance_of_check, "had no assigns contract specified");
vec![]
});
self.attach_modifies_contract(instance_of_check, assigns_contract);

let wrapper_name = self.symbol_name_stable(instance_of_check);

let recursion_wrapper_id =
function_under_contract_attrs.checked_with_id().unwrap().unwrap();
let span_of_recursion_wrapper = tcx.def_span(recursion_wrapper_id);
let location_of_recursion_wrapper = self.codegen_span(&span_of_recursion_wrapper);

let full_name = format!(
"{}:{}::REENTRY",
location_of_recursion_wrapper
.filename()
.expect("recursion location wrapper should have a file name"),
tcx.item_name(recursion_wrapper_id),
);

AssignsContract { recursion_tracker: full_name, contracted_function_name: wrapper_name }
}

/// Convert the Kani level contract into a CBMC level contract by creating a
/// CBMC lambda.
fn codegen_modifies_contract(&mut self, modified_places: Vec<Local>) -> FunctionContract {
let goto_annotated_fn_name = self.current_fn().name();
let goto_annotated_fn_typ = self
.symbol_table
.lookup(&goto_annotated_fn_name)
.unwrap_or_else(|| panic!("Function '{goto_annotated_fn_name}' is not declared"))
.typ
.clone();

let assigns = modified_places
.into_iter()
.map(|local| {
Lambda::as_contract_for(
&goto_annotated_fn_typ,
None,
self.codegen_place_stable(&local.into()).unwrap().goto_expr.dereference(),
)
})
.collect();

FunctionContract::new(assigns)
}

/// Convert the contract to a CBMC contract, then attach it to `instance`.
/// `instance` must have previously been declared.
///
/// This merges with any previously attached contracts.
pub fn attach_modifies_contract(&mut self, instance: Instance, modified_places: Vec<Local>) {
// This should be safe, since the contract is pretty much evaluated as
// though it was the first (or last) assertion in the function.
assert!(self.current_fn.is_none());
let body = instance.body().unwrap();
self.set_current_fn(instance, &body);
let goto_contract = self.codegen_modifies_contract(modified_places);
let name = self.current_fn().name();

self.symbol_table.attach_contract(name, goto_contract);
self.reset_current_fn()
}
}
46 changes: 1 addition & 45 deletions kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs
JustusAdam marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//! This file contains functions related to codegenning MIR functions into gotoc

use crate::codegen_cprover_gotoc::GotocCtx;
use cbmc::goto_program::{Expr, FunctionContract, Stmt, Symbol};
use cbmc::goto_program::{Expr, Stmt, Symbol};
use cbmc::InternString;
use rustc_middle::mir::traversal::reverse_postorder;
use stable_mir::mir::mono::Instance;
Expand Down Expand Up @@ -202,50 +202,6 @@ impl<'tcx> GotocCtx<'tcx> {
);
}

/// Convert the Kani level contract into a CBMC level contract by creating a
/// CBMC lambda.
fn as_goto_contract(&mut self, assigns_contract: Vec<Local>) -> FunctionContract {
use cbmc::goto_program::Lambda;

let goto_annotated_fn_name = self.current_fn().name();
let goto_annotated_fn_typ = self
.symbol_table
.lookup(&goto_annotated_fn_name)
.unwrap_or_else(|| panic!("Function '{goto_annotated_fn_name}' is not declared"))
.typ
.clone();

let assigns = assigns_contract
.into_iter()
.map(|local| {
Lambda::as_contract_for(
&goto_annotated_fn_typ,
None,
self.codegen_place_stable(&local.into()).unwrap().goto_expr.dereference(),
)
})
.collect();

FunctionContract::new(assigns)
}

/// Convert the contract to a CBMC contract, then attach it to `instance`.
/// `instance` must have previously been declared.
///
/// This merges with any previously attached contracts.
pub fn attach_contract(&mut self, instance: Instance, contract: Vec<Local>) {
// This should be safe, since the contract is pretty much evaluated as
// though it was the first (or last) assertion in the function.
assert!(self.current_fn.is_none());
let body = instance.body().unwrap();
self.set_current_fn(instance, &body);
let goto_contract = self.as_goto_contract(contract);
let name = self.current_fn().name();

self.symbol_table.attach_contract(name, goto_contract);
self.reset_current_fn()
}

pub fn declare_function(&mut self, instance: Instance) {
debug!("declaring {}; {:?}", instance.name(), instance);
let body = instance.body().unwrap();
Expand Down
1 change: 1 addition & 0 deletions kani-compiler/src/codegen_cprover_gotoc/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod statement;
mod static_var;

// Visible for all codegen module.
pub mod contract;
mod ty_stable;
pub(super) mod typ;

Expand Down
109 changes: 19 additions & 90 deletions kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use rustc_codegen_ssa::{CodegenResults, CrateInfo};
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
use rustc_data_structures::temp_dir::MaybeTempDir;
use rustc_errors::{ErrorGuaranteed, DEFAULT_LOCALE_RESOURCE};
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
use rustc_hir::def_id::{DefId as InternalDefId, LOCAL_CRATE};
use rustc_metadata::creader::MetadataLoaderDyn;
use rustc_metadata::fs::{emit_wrapper_file, METADATA_FILENAME};
use rustc_metadata::EncodedMetadata;
Expand All @@ -45,7 +45,7 @@ use rustc_smir::rustc_internal;
use rustc_target::abi::Endian;
use rustc_target::spec::PanicStrategy;
use stable_mir::mir::mono::{Instance, MonoItem};
use stable_mir::CrateDef;
use stable_mir::{CrateDef, DefId};
use std::any::Any;
use std::collections::BTreeMap;
use std::collections::HashSet;
Expand All @@ -63,22 +63,18 @@ use tracing::{debug, error, info};

pub type UnsupportedConstructs = FxHashMap<InternedString, Vec<Location>>;

pub type ContractInfoChannel = std::sync::mpsc::Sender<(InternedString, AssignsContract)>;

#[derive(Clone)]
pub struct GotocCodegenBackend {
/// The query is shared with `KaniCompiler` and it is initialized as part of `rustc`
/// initialization, which may happen after this object is created.
/// Since we don't have any guarantees on when the compiler creates the Backend object, neither
/// in which thread it will be used, we prefer to explicitly synchronize any query access.
queries: Arc<Mutex<QueryDb>>,

contract_channel: ContractInfoChannel,
}

impl GotocCodegenBackend {
pub fn new(queries: Arc<Mutex<QueryDb>>, contract_channel: ContractInfoChannel) -> Self {
GotocCodegenBackend { queries, contract_channel }
pub fn new(queries: Arc<Mutex<QueryDb>>) -> Self {
GotocCodegenBackend { queries }
}

/// Generate code that is reachable from the given starting points.
Expand All @@ -90,7 +86,7 @@ impl GotocCodegenBackend {
starting_items: &[MonoItem],
symtab_goto: &Path,
machine_model: &MachineModel,
check_contract: Option<DefId>,
check_contract: Option<InternalDefId>,
) -> (GotocCtx<'tcx>, Vec<MonoItem>, Option<AssignsContract>) {
celinval marked this conversation as resolved.
Show resolved Hide resolved
let items = with_timer(
|| collect_reachable_items(tcx, starting_items),
Expand Down Expand Up @@ -192,78 +188,6 @@ impl GotocCodegenBackend {
}
}

impl<'tcx> GotocCtx<'tcx> {
/// Given the `proof_for_contract` target `function_under_contract` and the reachable `items`,
/// find or create the `AssignsContract` that needs to be enforced and attach it to the symbol
/// for which it needs to be enforced.
///
/// 1. Gets the `#[kanitool::inner_check = "..."]` target, then resolves exactly one instance
/// of it. Panics if there are more or less than one instance.
/// 2. Expects that a `#[kanitool::modifies(...)]` is placed on the `inner_check` function,
/// turns it into a CBMC contract and attaches it to the symbol for the previously resolved
/// instance.
/// 3. Returns the mangled name of the symbol it attached the contract to.
/// 4. Resolves the `#[kanitool::checked_with = "..."]` target from `function_under_contract`
/// which has `static mut REENTRY : bool` declared inside.
/// 5. Returns the full path to this constant that `--nondet-static-exclude` expects which is
/// comprised of the file path that `checked_with` is located in, the name of the
/// `checked_with` function and the name of the constant (`REENTRY`).
fn handle_check_contract(
&mut self,
function_under_contract: DefId,
items: &[MonoItem],
) -> AssignsContract {
let tcx = self.tcx;
let function_under_contract_attrs = KaniAttributes::for_item(tcx, function_under_contract);
let wrapped_fn = function_under_contract_attrs.inner_check().unwrap().unwrap();

let mut instance_under_contract = items.iter().filter_map(|i| match i {
MonoItem::Fn(instance @ Instance { def, .. })
if wrapped_fn == rustc_internal::internal(def.def_id()) =>
{
Some(instance.clone())
}
_ => None,
});
let instance_of_check = instance_under_contract.next().unwrap();
assert!(
instance_under_contract.next().is_none(),
"Only one instance of a checked function may be in scope"
);
let attrs_of_wrapped_fn = KaniAttributes::for_item(tcx, wrapped_fn);
let assigns_contract = attrs_of_wrapped_fn.modifies_contract().unwrap_or_else(|| {
debug!(?instance_of_check, "had no assigns contract specified");
vec![]
});
self.attach_contract(instance_of_check, assigns_contract);

let wrapper_name = self.symbol_name_stable(instance_of_check);

let recursion_wrapper_id =
function_under_contract_attrs.checked_with_id().unwrap().unwrap();
let span_of_recursion_wrapper = tcx.def_span(recursion_wrapper_id);
let location_of_recursion_wrapper = self.codegen_span(&span_of_recursion_wrapper);

let full_name = format!(
"{}:{}::REENTRY",
location_of_recursion_wrapper
.filename()
.expect("recursion location wrapper should have a file name"),
tcx.item_name(recursion_wrapper_id),
);

AssignsContract { recursion_tracker: full_name, contracted_function_name: wrapper_name }
}
}

fn contract_metadata_for_harness(
tcx: TyCtxt,
def_id: DefId,
) -> Result<Option<DefId>, ErrorGuaranteed> {
let attrs = KaniAttributes::for_item(tcx, def_id);
Ok(attrs.interpret_the_for_contract_attribute().transpose()?.map(|(_, id, _)| id))
}

impl CodegenBackend for GotocCodegenBackend {
fn metadata_loader(&self) -> Box<MetadataLoaderDyn> {
Box::new(rustc_codegen_ssa::back::metadata::DefaultMetadataLoader)
Expand Down Expand Up @@ -317,12 +241,8 @@ impl CodegenBackend for GotocCodegenBackend {
for harness in harnesses {
let model_path =
queries.harness_model_path(&harness.mangled_name()).unwrap();
let Ok(contract_metadata) = contract_metadata_for_harness(
tcx,
rustc_internal::internal(harness.def.def_id()),
) else {
continue;
};
let contract_metadata =
contract_metadata_for_harness(tcx, harness.def.def_id()).unwrap();
let (gcx, items, contract_info) = self.codegen_items(
tcx,
&[MonoItem::Fn(harness)],
Expand All @@ -332,9 +252,10 @@ impl CodegenBackend for GotocCodegenBackend {
);
results.extend(gcx, items, None);
if let Some(assigns_contract) = contract_info {
self.contract_channel
.send((harness.name().intern(), assigns_contract))
.unwrap();
self.queries
.lock()
.unwrap()
.add_modifies_contract(harness.name().intern(), assigns_contract);
}
}
}
Expand Down Expand Up @@ -497,6 +418,14 @@ impl CodegenBackend for GotocCodegenBackend {
}
}

fn contract_metadata_for_harness(
tcx: TyCtxt,
def_id: DefId,
) -> Result<Option<InternalDefId>, ErrorGuaranteed> {
let attrs = KaniAttributes::for_def_id(tcx, def_id);
Ok(attrs.interpret_the_for_contract_attribute().transpose()?.map(|(_, id, _)| id))
}

fn check_target(session: &Session) {
// The requirement below is needed to build a valid CBMC machine model
// in function `machine_model_from_session` from
Expand Down
2 changes: 1 addition & 1 deletion kani-compiler/src/codegen_cprover_gotoc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ mod context;
mod overrides;
mod utils;

pub use compiler_interface::{ContractInfoChannel, GotocCodegenBackend, UnsupportedConstructs};
pub use compiler_interface::{GotocCodegenBackend, UnsupportedConstructs};
pub use context::GotocCtx;
pub use context::VtableCtx;
Loading
Loading