Skip to content

Commit 41293a0

Browse files
committed
Adds support for pointer history variables in function contracts
This adds support for history variables in function contracts. History variables are (1) declared and (2) assigned to the value that their corresponding variable has at function call time. Currently, only pointers are supported.
1 parent afd8a60 commit 41293a0

File tree

29 files changed

+498
-16
lines changed

29 files changed

+498
-16
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
void foo(int *x) __CPROVER_assigns(*x)
2+
__CPROVER_ensures(*x == __CPROVER_old(*x) + 5)
3+
{
4+
*x = *x + 5;
5+
}
6+
7+
int main()
8+
{
9+
int n;
10+
foo(&n);
11+
12+
return 0;
13+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CORE
2+
main.c
3+
--enforce-all-contracts
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
ASSERT \*tmp_cc\$\d == tmp_cc\$\d \+ 5
8+
--
9+
--
10+
Verification:
11+
This test checks that history variables are supported for parameters of the
12+
the function under test. By using the --enforce-all-contracts flag,
13+
the post-condition (which contains the history variable) is asserted.
14+
In this case, this assertion should pass.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
void foo(int *x) __CPROVER_assigns(*x)
2+
__CPROVER_ensures(*x < __CPROVER_old(*x) + 5)
3+
{
4+
*x = *x + 5;
5+
}
6+
7+
int main()
8+
{
9+
int n;
10+
foo(&n);
11+
12+
return 0;
13+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CORE
2+
main.c
3+
--enforce-all-contracts
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
^VERIFICATION FAILED$
7+
ASSERT \*tmp_cc\$\d < tmp_cc\$\d \+ 5
8+
--
9+
--
10+
Verification:
11+
This test checks that history variables are supported for parameters of the
12+
the function under test. By using the --enforce-all-contracts flag,
13+
the post-condition (which contains the history variable) is asserted.
14+
In this case, this assertion should fail.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#include <limits.h>
2+
3+
void foo(int *x) __CPROVER_assigns(*x)
4+
__CPROVER_requires(*x > 0 && *x < INT_MAX - 5) __CPROVER_ensures(
5+
*x >= __CPROVER_old(*x) + 4 && *x <= __CPROVER_old(*x) + 6)
6+
{
7+
*x = *x + 5;
8+
}
9+
10+
int main()
11+
{
12+
int n;
13+
foo(&n);
14+
15+
return 0;
16+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CORE
2+
main.c
3+
--enforce-all-contracts
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
ASSERT tmp_if_expr\$\d
8+
--
9+
--
10+
Verification:
11+
This test checks that history variables are supported in the case where a
12+
history variable is referred to multiple times within an ensures clause.
13+
By using the --enforce-all-contracts flag, the post-condition (which contains
14+
the history variable) is asserted. In this case, this assertion should pass.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
void foo(int *x, int *y) __CPROVER_assigns(*x, *y)
2+
__CPROVER_ensures(*x == __CPROVER_old(*y) + 1 && *y == __CPROVER_old(*x) + 2)
3+
{
4+
int x_initial = *x;
5+
*x = *y + 1;
6+
*y = x_initial + 2;
7+
}
8+
9+
int main()
10+
{
11+
int x, y;
12+
foo(&x, &y);
13+
14+
return 0;
15+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CORE
2+
main.c
3+
--enforce-all-contracts
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
ASSERT tmp_if_expr
8+
--
9+
--
10+
Verification:
11+
This test checks that history variables are supported in the case where the
12+
function under test has multiple parameters. By using the
13+
--enforce-all-contracts flag, the post-condition (which contains the history
14+
variables) is asserted. In this case, this assertion should pass.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
void foo(int *x, int *y) __CPROVER_assigns(*x, *y)
2+
__CPROVER_ensures(*x == __CPROVER_old(*x) + 2 || *y == __CPROVER_old(*y) + 3)
3+
{
4+
*x = *x + 1;
5+
*y = *y + 2;
6+
}
7+
8+
int main()
9+
{
10+
int x, y;
11+
foo(&x, &y);
12+
13+
return 0;
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CORE
2+
main.c
3+
--enforce-all-contracts
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
^VERIFICATION FAILED$
7+
ASSERT tmp_if_expr
8+
--
9+
--
10+
Verification:
11+
This test checks that history variables are supported in the case where the
12+
function under test has multiple parameters. By using the
13+
--enforce-all-contracts flag, the post-condition (which contains the history
14+
variables) is asserted. In this case, this assertion should fail.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#include <assert.h>
2+
3+
void foo(int *x) __CPROVER_assigns(*x)
4+
__CPROVER_ensures(*x == __CPROVER_old(*x) + 5)
5+
{
6+
assert(__CPROVER_old(*x) == *x);
7+
*x = *x + 5;
8+
}
9+
10+
int main()
11+
{
12+
int n;
13+
foo(&n);
14+
15+
return 0;
16+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE
2+
main.c
3+
--enforce-all-contracts
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
^VERIFICATION FAILED$
7+
warning: ignoring old
8+
--
9+
--
10+
Verification:
11+
This test checks that history variables are not supported when referred to from
12+
a function body. In such a case, verification should fail.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
void foo(int *x) __CPROVER_assigns(*x)
2+
__CPROVER_ensures(*x == __CPROVER_old(*y) + 5)
3+
{
4+
*x = *x + 5;
5+
}
6+
7+
int main()
8+
{
9+
int n;
10+
foo(&n);
11+
12+
return 0;
13+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CORE
2+
main.c
3+
--enforce-all-contracts
4+
^EXIT=(1|64)$
5+
^SIGNAL=0$
6+
^CONVERSION ERROR$
7+
error: failed to find symbol 'y'
8+
--
9+
--
10+
Verification:
11+
This test checks that history variables may only be used with existing
12+
symbols. In other words, including a new symbol as part of __CPROVER_old()
13+
is not alowed. In such a case, the program should not parse and there
14+
should be a conversion error.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include <assert.h>
2+
#include <limits.h>
3+
4+
void foo(int *x) __CPROVER_assigns(*x) __CPROVER_requires(*x > 0)
5+
__CPROVER_ensures(*x == __CPROVER_old(*x) + 2)
6+
{
7+
*x = *x + 2;
8+
}
9+
10+
int main()
11+
{
12+
int n;
13+
__CPROVER_assume(n > 0 && n < INT_MAX - 2);
14+
foo(&n);
15+
16+
assert(n > 2);
17+
18+
return 0;
19+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
CORE
2+
main.c
3+
--replace-all-calls-with-contracts
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
ASSERT \*\(&n\) > 0
8+
ASSUME \*\(&n\) == tmp_cc\$\d \+ 2
9+
--
10+
--
11+
Verification:
12+
This test checks that history variables are supported with the use of the
13+
--replace-all-calls-with-contracts flag. In this case, the pre-condition
14+
becomes an assertion and the post-condition (which contains the history
15+
variable) becomes an assumption.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#include <assert.h>
2+
3+
void foo(int *x) __CPROVER_assigns(*x) __CPROVER_requires(*x == 0)
4+
__CPROVER_ensures(*x >= __CPROVER_old(*x) + 2)
5+
{
6+
*x = *x + 2;
7+
}
8+
9+
int main()
10+
{
11+
int n = 0;
12+
foo(&n);
13+
14+
assert(n >= 2);
15+
16+
return 0;
17+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
CORE
2+
main.c
3+
--replace-all-calls-with-contracts
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
ASSERT \*\(&n\) == 0
8+
ASSUME \*\(&n\) >= tmp_cc\$\d \+ 2
9+
--
10+
--
11+
Verification:
12+
This test checks that history variables are supported with the use of the
13+
--replace-all-calls-with-contracts flag. In this case, the pre-condition
14+
becomes an assertion and the post-condition (which contains the history
15+
variable) becomes an assumption.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#include <assert.h>
2+
3+
void foo(int *x) __CPROVER_assigns(*x)
4+
__CPROVER_requires(*x == __CPROVER_old(*x))
5+
__CPROVER_ensures(*x == __CPROVER_old(*x) + 2)
6+
{
7+
*x = *x + 2;
8+
}
9+
10+
int main()
11+
{
12+
int n = 0;
13+
foo(&n);
14+
15+
assert(n == 2);
16+
17+
return 0;
18+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE
2+
main.c
3+
--replace-all-calls-with-contracts
4+
^EXIT=(1|64)$
5+
^SIGNAL=0$
6+
^CONVERSION ERROR$
7+
error: __CPROVER_old expressions are not allowed in __CPROVER_requires clauses
8+
--
9+
--
10+
Verification:
11+
This test checks that history variables cannot be used as part of the
12+
pre-condition contract. In this case, verification should fail.

src/ansi-c/c_typecheck_base.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,7 @@ void c_typecheck_baset::typecheck_declaration(
729729
if(as_const(code_type).requires().is_not_nil())
730730
{
731731
auto &requires = code_type.requires();
732+
disallow_history_variables(requires);
732733
typecheck_expr(requires);
733734
implicit_typecast_bool(requires);
734735
}

src/ansi-c/c_typecheck_base.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ class c_typecheck_baset:
205205
const symbol_exprt &function_symbol);
206206
virtual exprt
207207
typecheck_shuffle_vector(const side_effect_expr_function_callt &expr);
208+
virtual void disallow_history_variables(exprt &expr);
208209

209210
virtual void make_index_type(exprt &expr);
210211
virtual void make_constant(exprt &expr);

src/ansi-c/c_typecheck_expr.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2575,6 +2575,20 @@ exprt c_typecheck_baset::do_special_functions(
25752575

25762576
return std::move(ok_expr);
25772577
}
2578+
else if(identifier == CPROVER_PREFIX "old")
2579+
{
2580+
if(expr.arguments().size() != 1)
2581+
{
2582+
error().source_location = f_op.source_location();
2583+
error() << identifier << " expects one operands" << eom;
2584+
throw 0;
2585+
}
2586+
2587+
old_exprt old_expr(ID_old, expr.arguments()[0]);
2588+
old_expr.add_source_location() = source_location;
2589+
2590+
return std::move(old_expr);
2591+
}
25782592
else if(identifier==CPROVER_PREFIX "isinff" ||
25792593
identifier==CPROVER_PREFIX "isinfd" ||
25802594
identifier==CPROVER_PREFIX "isinfld" ||
@@ -3797,3 +3811,26 @@ void c_typecheck_baset::make_constant_index(exprt &expr)
37973811
throw 0;
37983812
}
37993813
}
3814+
3815+
void c_typecheck_baset::disallow_history_variables(exprt &expr)
3816+
{
3817+
for(auto &op : expr.operands())
3818+
{
3819+
disallow_history_variables(op);
3820+
}
3821+
3822+
if(expr.id() == ID_side_effect && expr.get(ID_statement) == ID_function_call)
3823+
{
3824+
const auto &func_call_expr = to_side_effect_expr_function_call(expr);
3825+
const exprt &f_op = func_call_expr.function();
3826+
const irep_idt &identifier = to_symbol_expr(f_op).get_identifier();
3827+
if(identifier == CPROVER_PREFIX "old")
3828+
{
3829+
error().source_location = f_op.source_location();
3830+
error() << CPROVER_PREFIX
3831+
"old expressions are not allowed in " CPROVER_PREFIX "requires clauses"
3832+
<< eom;
3833+
throw 0;
3834+
}
3835+
}
3836+
}

src/ansi-c/cprover_builtin_headers.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ __CPROVER_size_t __CPROVER_zero_string_length(const void *);
1212
__CPROVER_size_t __CPROVER_buffer_size(const void *);
1313
__CPROVER_bool __CPROVER_r_ok(const void *, __CPROVER_size_t);
1414
__CPROVER_bool __CPROVER_w_ok(const void *, __CPROVER_size_t);
15+
void __CPROVER_old(const void *);
1516

1617
// bitvector analysis
1718
__CPROVER_bool __CPROVER_get_flag(const void *, const char *);

src/ansi-c/library/cprover.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ __CPROVER_size_t __CPROVER_zero_string_length(const void *);
3535
__CPROVER_size_t __CPROVER_buffer_size(const void *);
3636
__CPROVER_bool __CPROVER_r_ok(const void *, __CPROVER_size_t);
3737
__CPROVER_bool __CPROVER_w_ok(const void *, __CPROVER_size_t);
38+
void __CPROVER_old(const void *);
3839

3940
#if 0
4041
__CPROVER_bool __CPROVER_equal();

0 commit comments

Comments
 (0)