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

Retrieve info for recursion tracker reliably #3045

Merged
merged 11 commits into from
Mar 2, 2024
59 changes: 43 additions & 16 deletions kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,46 @@ impl<'tcx> GotocCtx<'tcx> {
) -> 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 recursion_wrapper_id =
function_under_contract_attrs.checked_with_id().unwrap().unwrap();
let expected_name = format!("{}::REENTRY", tcx.item_name(recursion_wrapper_id));
let mut recursion_tracker = items.iter().filter_map(|i| match i {
MonoItem::Static(recursion_tracker)
if (*recursion_tracker).name().contains(expected_name.as_str()) =>
{
Some(*recursion_tracker)
}
_ => None,
});
let recursion_tracker_def = recursion_tracker
.next()
.expect("There should be at least one recursion tracker (REENTRY) in scope");
assert!(
recursion_tracker.next().is_none(),
"Only one recursion tracker (REENTRY) may be in scope"
);

let span_of_recursion_wrapper = tcx.def_span(recursion_wrapper_id);
let location_of_recursion_wrapper = self.codegen_span(&span_of_recursion_wrapper);
// The name and location for the recursion tracker should match the exact information added
// to the symbol table, otherwise our contract instrumentation will silently failed.
// This happens because Kani relies on `--nondet-static-exclude` from CBMC to properly
// handle this tracker. CBMC silently fails if there is no match in the symbol table
// that correspond to the argument of this flag.
// More details at https://github.com/model-checking/kani/pull/3045.
celinval marked this conversation as resolved.
Show resolved Hide resolved
let full_recursion_tracker_name = format!(
"{}:{}",
location_of_recursion_wrapper
.filename()
.expect("recursion location wrapper should have a file name"),
// We must use the pretty name of the tracker instead of the mangled name.
// This restrictions comes from `--nondet-static-exclude` in CBMC.
// Mode details at https://github.com/diffblue/cbmc/issues/8225.
recursion_tracker_def.name(),
);

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(tcx, def.def_id()) =>
Expand All @@ -56,23 +94,12 @@ impl<'tcx> GotocCtx<'tcx> {
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 }
AssignsContract {
recursion_tracker: full_recursion_tracker_name,
contracted_function_name: wrapper_name,
}
}

/// Convert the Kani level contract into a CBMC level contract by creating a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ impl<'tcx> GotocCtx<'tcx> {

let typ = self.codegen_ty_stable(instance.ty());
let location = self.codegen_span_stable(def.span());
// Contracts instrumentation relies on `--nondet-static-exclude` to properly
// havoc static variables. Kani uses the location and pretty name to identify
// the correct variables. If the wrong name is used, CBMC may fail silently.
// More details at https://github.com/diffblue/cbmc/issues/8225.
let symbol = Symbol::static_variable(symbol_name.clone(), symbol_name, typ, location)
.with_is_hidden(false) // Static items are always user defined.
.with_pretty_name(pretty_name);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VERIFICATION:- SUCCESSFUL
17 changes: 17 additions & 0 deletions tests/expected/function-contract/generic_infinity_recursion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: -Zfunction-contracts

//! Check Kani handling of generics and recursion with function contracts.

#[kani::requires(x != 0)]
celinval marked this conversation as resolved.
Show resolved Hide resolved
fn foo<T: std::cmp::PartialEq<i32>>(x: T) {
assert_ne!(x, 0);
foo(x);
}

#[kani::proof_for_contract(foo)]
fn foo_harness() {
let input: i32 = kani::any();
foo(input);
}
Loading