Skip to content

Add support for quantifiers #3993

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

Merged
merged 28 commits into from
May 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a92688f
Add support for quantifiers
feliperodri Sep 7, 2024
e8c5e43
Format code and avoid code duplication
feliperodri Feb 2, 2025
0be406a
Inlining function calls in quantifier expressions
qinheping Apr 7, 2025
f838e91
Fix ranged existential quantification
qinheping Apr 11, 2025
6d0017b
Add support of switch in quantifier expression
qinheping Apr 11, 2025
df78d91
Remove debug leftover
qinheping Apr 11, 2025
d463882
Make tests depending on new CBMC fixme
qinheping Apr 11, 2025
bf9eb97
Fix clippy
qinheping Apr 11, 2025
f21041d
Fix clippy
qinheping Apr 11, 2025
49c22c9
Fix clippy
qinheping Apr 11, 2025
8210209
Make universal quantification complete
qinheping Apr 11, 2025
e276642
Make universal quantification complete
qinheping Apr 11, 2025
5a60da9
Add tests for quantifier expression with array indexing
qinheping Apr 15, 2025
8c242b8
Add fixme info
qinheping Apr 15, 2025
40195bc
Update RFC with function inlining
qinheping Apr 15, 2025
fac313f
Make even test non fixme
qinheping Apr 22, 2025
131d503
Update array tests
qinheping Apr 22, 2025
3bf250b
Add tests with quantifiers in contracts
qinheping Apr 22, 2025
acee4ef
Add document for quantifiers
qinheping Apr 22, 2025
e336cad
Fix format
qinheping Apr 24, 2025
a3549ec
Fix clippy
qinheping Apr 24, 2025
39832fa
Merge branch 'main' into quantifiers
tautschnig Apr 29, 2025
41b4755
Update tests
tautschnig Apr 29, 2025
6d795e9
Add expected tests
qinheping Apr 30, 2025
71d1a92
Fix format
qinheping Apr 30, 2025
abb3e8e
Address Carolyn's comments
qinheping May 1, 2025
c8ea72f
Remove the need of import quantifiers
qinheping May 1, 2025
445b809
Make quantifier functions hidden
qinheping May 7, 2025
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
18 changes: 18 additions & 0 deletions cprover_bindings/src/goto_program/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,14 @@ pub enum ExprValue {
Vector {
elems: Vec<Expr>,
},
Forall {
variable: Expr, // symbol
domain: Expr, // where
},
Exists {
variable: Expr, // symbol
domain: Expr, // where
},
}

/// Binary operators. The names are the same as in the Irep representation.
Expand Down Expand Up @@ -972,6 +980,16 @@ impl Expr {
let typ = typ.aggr_tag().unwrap();
expr!(Union { value, field }, typ)
}

pub fn forall_expr(typ: Type, variable: Expr, domain: Expr) -> Expr {
assert!(variable.is_symbol());
expr!(Forall { variable, domain }, typ)
}

pub fn exists_expr(typ: Type, variable: Expr, domain: Expr) -> Expr {
assert!(variable.is_symbol());
expr!(Exists { variable, domain }, typ)
}
}

/// Constructors for Binary Operations
Expand Down
4 changes: 4 additions & 0 deletions cprover_bindings/src/goto_program/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ impl Symbol {
}
}

pub fn update(&mut self, value: SymbolValues) {
self.value = value;
}

/// Add this contract to the symbol (symbol must be a function) or fold the
/// conditions into an existing contract.
pub fn attach_contract(&mut self, contract: FunctionContract) {
Expand Down
32 changes: 31 additions & 1 deletion cprover_bindings/src/goto_program/symbol_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@ use std::collections::BTreeMap;
#[derive(Clone, Debug)]
pub struct SymbolTable {
symbol_table: BTreeMap<InternedString, Symbol>,
parameters_map: BTreeMap<InternedString, Vec<InternedString>>,
machine_model: MachineModel,
}

/// Constructors
impl SymbolTable {
pub fn new(machine_model: MachineModel) -> SymbolTable {
let mut symtab = SymbolTable { machine_model, symbol_table: BTreeMap::new() };
let mut symtab = SymbolTable {
machine_model,
symbol_table: BTreeMap::new(),
parameters_map: BTreeMap::new(),
};
env::machine_model_symbols(symtab.machine_model())
.into_iter()
.for_each(|s| symtab.insert(s));
Expand Down Expand Up @@ -54,6 +59,19 @@ impl SymbolTable {
self.symbol_table.insert(symbol.name, symbol);
}

/// Inserts a parameter into the parameters map for a given function symbol.
/// If the function does not exist in the parameters map, it initializes it with an empty vector.
pub fn insert_parameter<T: Into<InternedString>, P: Into<InternedString>>(
&mut self,
function_name: T,
parameter: P,
) {
let function_name = function_name.into();
let parameter = parameter.into();

self.parameters_map.entry(function_name).or_default().push(parameter);
}

/// Validates the previous value of the symbol using the validator function, then replaces it.
/// Useful to replace declarations with the actual definition.
pub fn replace<F: FnOnce(Option<&Symbol>) -> bool>(
Expand Down Expand Up @@ -102,6 +120,10 @@ impl SymbolTable {
self.symbol_table.iter()
}

pub fn iter_mut(&mut self) -> std::collections::btree_map::IterMut<'_, InternedString, Symbol> {
self.symbol_table.iter_mut()
}

pub fn lookup<T: Into<InternedString>>(&self, name: T) -> Option<&Symbol> {
let name = name.into();
self.symbol_table.get(&name)
Expand All @@ -112,6 +134,14 @@ impl SymbolTable {
self.symbol_table.get_mut(&name)
}

pub fn lookup_parameters<T: Into<InternedString>>(
&self,
name: T,
) -> Option<&Vec<InternedString>> {
let name = name.into();
self.parameters_map.get(&name)
}

pub fn machine_model(&self) -> &MachineModel {
&self.machine_model
}
Expand Down
24 changes: 24 additions & 0 deletions cprover_bindings/src/irep/to_irep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,30 @@ impl ToIrep for ExprValue {
sub: elems.iter().map(|x| x.to_irep(mm)).collect(),
named_sub: linear_map![],
},
ExprValue::Forall { variable, domain } => Irep {
id: IrepId::Forall,
sub: vec![
Irep {
id: IrepId::Tuple,
sub: vec![variable.to_irep(mm)],
named_sub: linear_map![],
},
domain.to_irep(mm),
],
named_sub: linear_map![],
},
ExprValue::Exists { variable, domain } => Irep {
id: IrepId::Exists,
sub: vec![
Irep {
id: IrepId::Tuple,
sub: vec![variable.to_irep(mm)],
named_sub: linear_map![],
},
domain.to_irep(mm),
],
named_sub: linear_map![],
},
}
}
}
Expand Down
1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
- [Contracts](./reference/experimental/contracts.md)
- [Loop Contracts](./reference/experimental/loop-contracts.md)
- [Concrete Playback](./reference/experimental/concrete-playback.md)
- [Quantifiers](./reference/experimental/quantifiers.md)
- [Application](./application.md)
- [Comparison with other tools](./tool-comparison.md)
- [Where to start on real code](./tutorial-real-code.md)
Expand Down
56 changes: 56 additions & 0 deletions docs/src/reference/experimental/quantifiers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Quantifiers in Kani

Quantifiers are a powerful feature in formal verification that allow you to express properties over a range of values. Kani provides experimental support for quantifiers, enabling users to write concise and expressive specifications for their programs.

## Supported Quantifiers

Kani currently supports the following quantifiers:

1. **Universal Quantifier**:
- Ensures that a property holds for all values in a given range.
- Syntax: `kani::forall!(|variable in range| condition)`
- Example:

```rust
#[kani::proof]
fn test_forall() {
let v = vec![10; 10];
kani::assert(kani::forall!(|i in 0..10| v[i] == 10));
}
```

2. **Existential Quantifier**:
- Ensures that there exists at least one value in a given range for which a property holds.
- Syntax: `kani::exists!(|variable in range| condition)`
- Example:

```rust
#[kani::proof]
fn test_exists() {
let v = vec![1, 2, 3, 4, 5];
kani::assert(kani::exists!(|i in 0..v.len()| v[i] == 3));
}
```

### Limitations

#### Array Indexing

The performance of quantifiers can be affected by the depth of call stacks in the quantified expressions. If the call stack is too deep, Kani may not be able to evaluate the quantifier effectively, leading to potential timeouts or running out of memory. Actually, array indexing in Rust leads to a deep call stack, which can cause issues with quantifiers. To mitigate this, consider using *unsafe* pointer dereferencing instead of array indexing when working with quantifiers. For example:

```rust

#[kani::proof]
fn vec_assert_forall_harness() {
let v = vec![10 as u8; 128];
let ptr = v.as_ptr();
unsafe {
kani::assert(kani::forall!(|i in (0,128)| *ptr.wrapping_byte_offset(i as isize) == 10), "");
}
}
```

#### Types of Quantified Variables

We now assume that all quantified variables are of type `usize`. This means that the range specified in the quantifier must be compatible with `usize`.
We plan to support other types in the future, but for now, ensure that your quantifiers use `usize` ranges.
23 changes: 17 additions & 6 deletions kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use crate::codegen_cprover_gotoc::GotocCtx;
use crate::codegen_cprover_gotoc::codegen::block::reverse_postorder;
use cbmc::InternString;
use cbmc::InternedString;
use cbmc::goto_program::{Expr, Stmt, Symbol};
use stable_mir::CrateDef;
use stable_mir::mir::mono::Instance;
Expand All @@ -20,7 +21,7 @@ impl GotocCtx<'_> {
/// - Index 0 represents the return value.
/// - Indices [1, N] represent the function parameters where N is the number of parameters.
/// - Indices that are greater than N represent local variables.
fn codegen_declare_variables(&mut self, body: &Body) {
fn codegen_declare_variables(&mut self, body: &Body, function_name: InternedString) {
let ldecls = body.local_decls();
let num_args = body.arg_locals().len();
for (lc, ldata) in ldecls {
Expand All @@ -35,13 +36,23 @@ impl GotocCtx<'_> {
let loc = self.codegen_span_stable(ldata.span);
// Indices [1, N] represent the function parameters where N is the number of parameters.
// Except that ZST fields are not included as parameters.
let sym =
Symbol::variable(name, base_name, var_type, self.codegen_span_stable(ldata.span))
.with_is_hidden(!self.is_user_variable(&lc))
.with_is_parameter((lc > 0 && lc <= num_args) && !self.is_zst_stable(ldata.ty));
let sym = Symbol::variable(
name.clone(),
base_name,
var_type,
self.codegen_span_stable(ldata.span),
)
.with_is_hidden(!self.is_user_variable(&lc))
.with_is_parameter((lc > 0 && lc <= num_args) && !self.is_zst_stable(ldata.ty));
let sym_e = sym.to_expr();
self.symbol_table.insert(sym);

// Store the parameter symbols of the function, which will be used for function
// inlining.
if lc > 0 && lc <= num_args {
self.symbol_table.insert_parameter(function_name, name);
}

// Index 0 represents the return value, which does not need to be
// declared in the first block
if lc < 1 || lc > body.arg_locals().len() {
Expand All @@ -64,7 +75,7 @@ impl GotocCtx<'_> {
self.set_current_fn(instance, &body);
self.print_instance(instance, &body);
self.codegen_function_prelude(&body);
self.codegen_declare_variables(&body);
self.codegen_declare_variables(&body, name.clone().into());

// Get the order from internal body for now.
reverse_postorder(&body).for_each(|bb| self.codegen_block(bb, &body.blocks[bb]));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ impl GotocCodegenBackend {
None
};

gcx.handle_quantifiers();

// No output should be generated if user selected no_codegen.
if !tcx.sess.opts.unstable_opts.no_codegen && tcx.sess.opts.output_types.should_codegen() {
let pretty = self.queries.lock().unwrap().args().output_pretty_json;
Expand Down
Loading
Loading