Skip to content

Commit db238e6

Browse files
qinhepingfeliperodritautschnig
authored
Add support for quantifiers (#3993)
This PR add support for quantifiers. Especially, we inline function calls in quantified expressions so that the result statement-expression can be accepted by the CBMC backend. RFC: [RFC 0010-quantifiers](https://github.com/model-checking/kani/blob/main/rfc/src/rfcs/0010-quantifiers.md). Resolves #2546 and #836. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --------- Signed-off-by: Felipe R. Monteiro <felisous@amazon.com> Co-authored-by: Felipe R. Monteiro <felisous@amazon.com> Co-authored-by: Michael Tautschnig <tautschn@amazon.com>
1 parent 1ff8054 commit db238e6

30 files changed

+1121
-28
lines changed

cprover_bindings/src/goto_program/expr.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,14 @@ pub enum ExprValue {
177177
Vector {
178178
elems: Vec<Expr>,
179179
},
180+
Forall {
181+
variable: Expr, // symbol
182+
domain: Expr, // where
183+
},
184+
Exists {
185+
variable: Expr, // symbol
186+
domain: Expr, // where
187+
},
180188
}
181189

182190
/// Binary operators. The names are the same as in the Irep representation.
@@ -972,6 +980,16 @@ impl Expr {
972980
let typ = typ.aggr_tag().unwrap();
973981
expr!(Union { value, field }, typ)
974982
}
983+
984+
pub fn forall_expr(typ: Type, variable: Expr, domain: Expr) -> Expr {
985+
assert!(variable.is_symbol());
986+
expr!(Forall { variable, domain }, typ)
987+
}
988+
989+
pub fn exists_expr(typ: Type, variable: Expr, domain: Expr) -> Expr {
990+
assert!(variable.is_symbol());
991+
expr!(Exists { variable, domain }, typ)
992+
}
975993
}
976994

977995
/// Constructors for Binary Operations

cprover_bindings/src/goto_program/symbol.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ impl Symbol {
172172
}
173173
}
174174

175+
pub fn update(&mut self, value: SymbolValues) {
176+
self.value = value;
177+
}
178+
175179
/// Add this contract to the symbol (symbol must be a function) or fold the
176180
/// conditions into an existing contract.
177181
pub fn attach_contract(&mut self, contract: FunctionContract) {

cprover_bindings/src/goto_program/symbol_table.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,18 @@ use std::collections::BTreeMap;
1010
#[derive(Clone, Debug)]
1111
pub struct SymbolTable {
1212
symbol_table: BTreeMap<InternedString, Symbol>,
13+
parameters_map: BTreeMap<InternedString, Vec<InternedString>>,
1314
machine_model: MachineModel,
1415
}
1516

1617
/// Constructors
1718
impl SymbolTable {
1819
pub fn new(machine_model: MachineModel) -> SymbolTable {
19-
let mut symtab = SymbolTable { machine_model, symbol_table: BTreeMap::new() };
20+
let mut symtab = SymbolTable {
21+
machine_model,
22+
symbol_table: BTreeMap::new(),
23+
parameters_map: BTreeMap::new(),
24+
};
2025
env::machine_model_symbols(symtab.machine_model())
2126
.into_iter()
2227
.for_each(|s| symtab.insert(s));
@@ -54,6 +59,19 @@ impl SymbolTable {
5459
self.symbol_table.insert(symbol.name, symbol);
5560
}
5661

62+
/// Inserts a parameter into the parameters map for a given function symbol.
63+
/// If the function does not exist in the parameters map, it initializes it with an empty vector.
64+
pub fn insert_parameter<T: Into<InternedString>, P: Into<InternedString>>(
65+
&mut self,
66+
function_name: T,
67+
parameter: P,
68+
) {
69+
let function_name = function_name.into();
70+
let parameter = parameter.into();
71+
72+
self.parameters_map.entry(function_name).or_default().push(parameter);
73+
}
74+
5775
/// Validates the previous value of the symbol using the validator function, then replaces it.
5876
/// Useful to replace declarations with the actual definition.
5977
pub fn replace<F: FnOnce(Option<&Symbol>) -> bool>(
@@ -102,6 +120,10 @@ impl SymbolTable {
102120
self.symbol_table.iter()
103121
}
104122

123+
pub fn iter_mut(&mut self) -> std::collections::btree_map::IterMut<'_, InternedString, Symbol> {
124+
self.symbol_table.iter_mut()
125+
}
126+
105127
pub fn lookup<T: Into<InternedString>>(&self, name: T) -> Option<&Symbol> {
106128
let name = name.into();
107129
self.symbol_table.get(&name)
@@ -112,6 +134,14 @@ impl SymbolTable {
112134
self.symbol_table.get_mut(&name)
113135
}
114136

137+
pub fn lookup_parameters<T: Into<InternedString>>(
138+
&self,
139+
name: T,
140+
) -> Option<&Vec<InternedString>> {
141+
let name = name.into();
142+
self.parameters_map.get(&name)
143+
}
144+
115145
pub fn machine_model(&self) -> &MachineModel {
116146
&self.machine_model
117147
}

cprover_bindings/src/irep/to_irep.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,30 @@ impl ToIrep for ExprValue {
377377
sub: elems.iter().map(|x| x.to_irep(mm)).collect(),
378378
named_sub: linear_map![],
379379
},
380+
ExprValue::Forall { variable, domain } => Irep {
381+
id: IrepId::Forall,
382+
sub: vec![
383+
Irep {
384+
id: IrepId::Tuple,
385+
sub: vec![variable.to_irep(mm)],
386+
named_sub: linear_map![],
387+
},
388+
domain.to_irep(mm),
389+
],
390+
named_sub: linear_map![],
391+
},
392+
ExprValue::Exists { variable, domain } => Irep {
393+
id: IrepId::Exists,
394+
sub: vec![
395+
Irep {
396+
id: IrepId::Tuple,
397+
sub: vec![variable.to_irep(mm)],
398+
named_sub: linear_map![],
399+
},
400+
domain.to_irep(mm),
401+
],
402+
named_sub: linear_map![],
403+
},
380404
}
381405
}
382406
}

docs/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
- [Contracts](./reference/experimental/contracts.md)
2626
- [Loop Contracts](./reference/experimental/loop-contracts.md)
2727
- [Concrete Playback](./reference/experimental/concrete-playback.md)
28+
- [Quantifiers](./reference/experimental/quantifiers.md)
2829
- [Application](./application.md)
2930
- [Comparison with other tools](./tool-comparison.md)
3031
- [Where to start on real code](./tutorial-real-code.md)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Quantifiers in Kani
2+
3+
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.
4+
5+
## Supported Quantifiers
6+
7+
Kani currently supports the following quantifiers:
8+
9+
1. **Universal Quantifier**:
10+
- Ensures that a property holds for all values in a given range.
11+
- Syntax: `kani::forall!(|variable in range| condition)`
12+
- Example:
13+
14+
```rust
15+
#[kani::proof]
16+
fn test_forall() {
17+
let v = vec![10; 10];
18+
kani::assert(kani::forall!(|i in 0..10| v[i] == 10));
19+
}
20+
```
21+
22+
2. **Existential Quantifier**:
23+
- Ensures that there exists at least one value in a given range for which a property holds.
24+
- Syntax: `kani::exists!(|variable in range| condition)`
25+
- Example:
26+
27+
```rust
28+
#[kani::proof]
29+
fn test_exists() {
30+
let v = vec![1, 2, 3, 4, 5];
31+
kani::assert(kani::exists!(|i in 0..v.len()| v[i] == 3));
32+
}
33+
```
34+
35+
### Limitations
36+
37+
#### Array Indexing
38+
39+
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:
40+
41+
```rust
42+
43+
#[kani::proof]
44+
fn vec_assert_forall_harness() {
45+
let v = vec![10 as u8; 128];
46+
let ptr = v.as_ptr();
47+
unsafe {
48+
kani::assert(kani::forall!(|i in (0,128)| *ptr.wrapping_byte_offset(i as isize) == 10), "");
49+
}
50+
}
51+
```
52+
53+
#### Types of Quantified Variables
54+
55+
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`.
56+
We plan to support other types in the future, but for now, ensure that your quantifiers use `usize` ranges.

kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use crate::codegen_cprover_gotoc::GotocCtx;
77
use crate::codegen_cprover_gotoc::codegen::block::reverse_postorder;
88
use cbmc::InternString;
9+
use cbmc::InternedString;
910
use cbmc::goto_program::{Expr, Stmt, Symbol};
1011
use stable_mir::CrateDef;
1112
use stable_mir::mir::mono::Instance;
@@ -20,7 +21,7 @@ impl GotocCtx<'_> {
2021
/// - Index 0 represents the return value.
2122
/// - Indices [1, N] represent the function parameters where N is the number of parameters.
2223
/// - Indices that are greater than N represent local variables.
23-
fn codegen_declare_variables(&mut self, body: &Body) {
24+
fn codegen_declare_variables(&mut self, body: &Body, function_name: InternedString) {
2425
let ldecls = body.local_decls();
2526
let num_args = body.arg_locals().len();
2627
for (lc, ldata) in ldecls {
@@ -35,13 +36,23 @@ impl GotocCtx<'_> {
3536
let loc = self.codegen_span_stable(ldata.span);
3637
// Indices [1, N] represent the function parameters where N is the number of parameters.
3738
// Except that ZST fields are not included as parameters.
38-
let sym =
39-
Symbol::variable(name, base_name, var_type, self.codegen_span_stable(ldata.span))
40-
.with_is_hidden(!self.is_user_variable(&lc))
41-
.with_is_parameter((lc > 0 && lc <= num_args) && !self.is_zst_stable(ldata.ty));
39+
let sym = Symbol::variable(
40+
name.clone(),
41+
base_name,
42+
var_type,
43+
self.codegen_span_stable(ldata.span),
44+
)
45+
.with_is_hidden(!self.is_user_variable(&lc))
46+
.with_is_parameter((lc > 0 && lc <= num_args) && !self.is_zst_stable(ldata.ty));
4247
let sym_e = sym.to_expr();
4348
self.symbol_table.insert(sym);
4449

50+
// Store the parameter symbols of the function, which will be used for function
51+
// inlining.
52+
if lc > 0 && lc <= num_args {
53+
self.symbol_table.insert_parameter(function_name, name);
54+
}
55+
4556
// Index 0 represents the return value, which does not need to be
4657
// declared in the first block
4758
if lc < 1 || lc > body.arg_locals().len() {
@@ -64,7 +75,7 @@ impl GotocCtx<'_> {
6475
self.set_current_fn(instance, &body);
6576
self.print_instance(instance, &body);
6677
self.codegen_function_prelude(&body);
67-
self.codegen_declare_variables(&body);
78+
self.codegen_declare_variables(&body, name.clone().into());
6879

6980
// Get the order from internal body for now.
7081
reverse_postorder(&body).for_each(|bb| self.codegen_block(bb, &body.blocks[bb]));

kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ impl GotocCodegenBackend {
199199
None
200200
};
201201

202+
gcx.handle_quantifiers();
203+
202204
// No output should be generated if user selected no_codegen.
203205
if !tcx.sess.opts.unstable_opts.no_codegen && tcx.sess.opts.output_types.should_codegen() {
204206
let pretty = self.queries.lock().unwrap().args().output_pretty_json;

0 commit comments

Comments
 (0)