Skip to content

Commit 426d3dc

Browse files
Emit explicit contract:: symbols for functions with contracts (#4612)
CBMC's DFCC contract instrumentation (`--enforce-contract` / `--replace-call-with-contract`) expects a dedicated `contract::<fn>` symbol to exist for any function carrying a contract. For C inputs this symbol is created by the ANSI-C typechecker, which also moves the contract clauses (e.g. `c_spec_assigns`) onto it. Kani builds goto-programs directly and never runs that typechecker, so the symbol was absent. CBMC used to paper over this by deriving an empty contract symbol from the function on the fly, but that fallback was removed for soundness in diffblue/cbmc#8739, turning the missing symbol into a hard error and breaking every `#[kani::proof_for_contract]` harness. Synthesize the companion `contract::<name>` symbol during symbol-table serialization. It shares the function's contract-bearing type, has no body, and is marked as a property, reconstructing exactly the symbol CBMC's typechecker (and its old fallback) produced. This is a necessary (though not necessarily sufficient) change to enable upgrading to newer CBMC versions. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --------- Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
1 parent 0e9d205 commit 426d3dc

1 file changed

Lines changed: 62 additions & 1 deletion

File tree

cprover_bindings/src/irep/to_irep.rs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -700,12 +700,73 @@ impl goto_program::Symbol {
700700
}
701701
}
702702

703+
impl goto_program::Symbol {
704+
/// Build the dedicated `contract::<name>` symbol that CBMC's contract
705+
/// instrumentation expects to exist for any function that carries a
706+
/// contract.
707+
///
708+
/// When CBMC processes C inputs, its ANSI-C typechecker creates this
709+
/// symbol (see `c_typecheck_baset` in CBMC) and moves the contract clauses
710+
/// (e.g. `c_spec_assigns`) onto it. Kani builds goto-programs directly and
711+
/// thus never goes through that typechecker, so we synthesize the symbol
712+
/// here. The symbol shares the function's (contract-bearing) type, has no
713+
/// body, and is marked as a property.
714+
///
715+
/// Historically CBMC would derive this symbol on the fly when it was
716+
/// missing, but that fallback was removed for soundness in
717+
/// <https://github.com/diffblue/cbmc/pull/8739>, turning the missing
718+
/// symbol into a hard error during `--enforce-contract` /
719+
/// `--replace-call-with-contract`.
720+
///
721+
/// `function_irep` is this symbol already lowered to its `irep` form. We
722+
/// reuse its (contract-bearing) type and metadata so the function body is
723+
/// not serialized a second time — the contract symbol drops it anyway.
724+
fn to_contract_irep(&self, function_irep: &super::Symbol) -> super::Symbol {
725+
super::Symbol {
726+
// Share the function's contract-bearing type; the contract symbol
727+
// is a pure specification with no body.
728+
typ: function_irep.typ.clone(),
729+
value: Irep::nil(),
730+
location: function_irep.location.clone(),
731+
name: format!("contract::{}", self.name).into(),
732+
is_property: true,
733+
// Everything else is copied verbatim from the function symbol.
734+
module: function_irep.module,
735+
base_name: function_irep.base_name,
736+
pretty_name: function_irep.pretty_name,
737+
mode: function_irep.mode,
738+
is_type: function_irep.is_type,
739+
is_macro: function_irep.is_macro,
740+
is_exported: function_irep.is_exported,
741+
is_input: function_irep.is_input,
742+
is_output: function_irep.is_output,
743+
is_state_var: function_irep.is_state_var,
744+
is_static_lifetime: function_irep.is_static_lifetime,
745+
is_thread_local: function_irep.is_thread_local,
746+
is_lvalue: function_irep.is_lvalue,
747+
is_file_local: function_irep.is_file_local,
748+
is_extern: function_irep.is_extern,
749+
is_volatile: function_irep.is_volatile,
750+
is_parameter: function_irep.is_parameter,
751+
is_auxiliary: function_irep.is_auxiliary,
752+
is_weak: function_irep.is_weak,
753+
}
754+
}
755+
}
756+
703757
impl goto_program::SymbolTable {
704758
pub fn to_irep(&self) -> super::SymbolTable {
705759
let mm = self.machine_model();
706760
let mut st = super::SymbolTable::new();
707761
for (_key, value) in self.iter() {
708-
st.insert(value.to_irep(mm))
762+
let symbol_irep = value.to_irep(mm);
763+
// Functions carrying a contract need a companion `contract::<name>`
764+
// symbol so that CBMC's DFCC contract instrumentation can find it.
765+
// See `goto_program::Symbol::to_contract_irep` for details.
766+
if value.contract.is_some() {
767+
st.insert(value.to_contract_irep(&symbol_irep));
768+
}
769+
st.insert(symbol_irep);
709770
}
710771
st
711772
}

0 commit comments

Comments
 (0)