Skip to content

Commit 0b4a3e8

Browse files
authored
Merge pull request #6077 from ArenBabikian/assign-to-null
Check for validity of pointers in assigns clause before havocing them.
2 parents e645434 + d5402d9 commit 0b4a3e8

File tree

10 files changed

+294
-3
lines changed

10 files changed

+294
-3
lines changed
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CORE
1+
KNOWNBUG
22
main.c
33
--replace-all-calls-with-contracts
44
^EXIT=0$
@@ -7,3 +7,7 @@ main.c
77
--
88
--
99
This test checks that a havocked variable can be constrained by a function post-condition.
10+
11+
Known Bug:
12+
Currently, there is a bug when char variables are being passed to
13+
__CPROVER_w_ok(). See GitHub issue #6106 for further details.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#include <assert.h>
2+
#include <stddef.h>
3+
4+
int *z;
5+
6+
void bar(int *x, int *y) __CPROVER_assigns(*x, *y) __CPROVER_requires(*x > 0)
7+
__CPROVER_ensures(*x == 3 && (y == NULL || *y == 5))
8+
{
9+
*x = 3;
10+
if(y != NULL)
11+
*y = 5;
12+
}
13+
14+
void baz() __CPROVER_assigns(*z) __CPROVER_ensures(z == NULL || *z == 7)
15+
{
16+
if(z != NULL)
17+
*z = 7;
18+
}
19+
20+
void foo(int *x) __CPROVER_assigns(*x) __CPROVER_requires(*x > 0)
21+
__CPROVER_ensures(*x == 3)
22+
{
23+
bar(x, NULL);
24+
z == NULL;
25+
baz();
26+
}
27+
28+
int main()
29+
{
30+
int n;
31+
foo(&n);
32+
33+
assert(n == 3);
34+
assert(z == NULL || *z == 7);
35+
return 0;
36+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
CORE
2+
main.c
3+
--enforce-contract foo --replace-call-with-contract bar --replace-call-with-contract baz
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
SUCCESS
8+
// bar
9+
ASSERT \*x > 0
10+
IF !\(\*x == 3\) THEN GOTO \d
11+
IF !\(\(signed int \*\)\(void \*\)0 == \(\(signed int \*\)NULL\)\) THEN GOTO \d
12+
tmp_if_expr = \*\(\(signed int \*\)\(void \*\)0\) == 5 \? TRUE \: FALSE;
13+
tmp_if_expr\$\d = tmp_if_expr \? TRUE : FALSE;
14+
ASSUME tmp_if_expr\$\d
15+
// baz
16+
IF !\(z == \(\(signed int \*\)NULL\)\) THEN GOTO \d
17+
tmp_if_expr\$\d = \*z == 7 \? TRUE : FALSE;
18+
ASSUME tmp_if_expr\$\d
19+
// foo
20+
ASSUME \*tmp_cc\$\d > 0
21+
ASSERT \*tmp_cc\$\d == 3
22+
--
23+
\[3\] file main\.c line 6 assertion: FAILURE
24+
--
25+
Verification:
26+
This test checks support for a NULL pointer that is assigned to by
27+
a function (bar and baz). Both functions bar and baz are being replaced by
28+
their function contracts, while the calling function foo is being checked
29+
(by enforcing it's function contracts).
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#include <assert.h>
2+
#include <stddef.h>
3+
4+
int *z;
5+
6+
void bar(int *x, int *y) __CPROVER_assigns(*x, *y) __CPROVER_requires(*x > 0)
7+
__CPROVER_ensures(*x == 3 && (y == NULL || *y == 5))
8+
{
9+
*x = 3;
10+
if(y != NULL)
11+
*y = 5;
12+
}
13+
14+
void baz() __CPROVER_assigns(*z) __CPROVER_ensures(z == NULL || *z == 7)
15+
{
16+
if(z != NULL)
17+
*z = 7;
18+
}
19+
20+
void foo(int *x) __CPROVER_assigns(*x) __CPROVER_requires(*x > 0)
21+
__CPROVER_ensures(*x == 3)
22+
{
23+
bar(x, NULL);
24+
*x = 3;
25+
z == NULL;
26+
baz();
27+
}
28+
29+
int main()
30+
{
31+
int n;
32+
foo(&n);
33+
34+
assert(n == 3);
35+
return 0;
36+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
CORE
2+
main.c
3+
--enforce-contract foo
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
//^([foo\.1] line 15 assertion: FAILURE)
8+
// foo
9+
ASSUME \*tmp_cc\$\d > 0
10+
ASSERT \*tmp_cc\$\d == 3
11+
--
12+
\[foo\.1\] line 24 assertion: FAILURE
13+
\[foo\.3\] line 27 assertion: FAILURE
14+
--
15+
Verification:
16+
This test checks support for a NULL pointer that is assigned to by
17+
a function (bar and baz). The calling function foo is being checked
18+
(by enforcing it's function contracts). As for bar and baz, their
19+
function contracts are ot being considered for this test.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#include <assert.h>
2+
#include <stddef.h>
3+
4+
int *z;
5+
6+
void bar(int *x, int *y) __CPROVER_assigns(*x, *y) __CPROVER_requires(*x > 0)
7+
__CPROVER_ensures(*x == 3 && *y == 5)
8+
{
9+
}
10+
11+
void baz() __CPROVER_assigns(*z) __CPROVER_ensures(*z == 7)
12+
{
13+
}
14+
15+
void foo(int *x) __CPROVER_assigns(*x) __CPROVER_requires(*x > 0)
16+
__CPROVER_ensures(*x == 3)
17+
{
18+
int *y;
19+
bar(x, y);
20+
assert(*y == 5);
21+
22+
baz();
23+
assert(*z == 7);
24+
}
25+
26+
int main()
27+
{
28+
int n;
29+
foo(&n);
30+
31+
assert(n == 3);
32+
return 0;
33+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
KNOWNBUG
2+
main.c
3+
--enforce-contract foo --replace-call-with-contract bar --replace-call-with-contract baz
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
// bar
8+
ASSERT \*x > 0
9+
IF !\(\*x == 3\) THEN GOTO \d
10+
tmp_if_expr = \*y == 5 \? TRUE \: FALSE;
11+
ASSUME tmp_if_expr
12+
// baz
13+
ASSUME \*z == 7
14+
// foo
15+
ASSUME \*tmp_cc\$\d > 0
16+
ASSERT \*tmp_cc\$\d == 3
17+
--
18+
--
19+
Verification:
20+
This test checks support for an uninitialized pointer that is assigned to by
21+
a function (bar and baz). Both functions bar and baz are being replaced by
22+
their function contracts, while the calling function foo is being checked
23+
(by enforcing it's function contracts).
24+
25+
Known Bug:
26+
Currently, there is a known issue with __CPROVER_w_ok(ptr, 0) such that it
27+
returns true if ptr is uninitialized. This is not the expected behavior,
28+
therefore, the outcome of this test case is currently incorrect.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#include <assert.h>
2+
#include <stddef.h>
3+
4+
int *z;
5+
6+
void bar(int *x, int *y) __CPROVER_assigns(*x, *y) __CPROVER_requires(*x > 0)
7+
__CPROVER_ensures(*x == 3 && *y == 5)
8+
{
9+
}
10+
11+
void baz() __CPROVER_assigns(*z) __CPROVER_ensures(*z == 7)
12+
{
13+
}
14+
15+
void foo(int *x) __CPROVER_assigns(*x) __CPROVER_requires(*x > 0)
16+
__CPROVER_ensures(*x == 3)
17+
{
18+
int *y = malloc(sizeof(int));
19+
bar(x, y);
20+
assert(*y == 5);
21+
22+
z = malloc(sizeof(int));
23+
baz();
24+
assert(*z == 7);
25+
}
26+
27+
int main()
28+
{
29+
int n;
30+
foo(&n);
31+
32+
assert(n == 3);
33+
return 0;
34+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
CORE
2+
main.c
3+
--enforce-contract foo --replace-call-with-contract bar --replace-call-with-contract baz
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
// bar
8+
ASSERT \*x > 0
9+
IF !\(\*x == 3\) THEN GOTO \d
10+
tmp_if_expr = \*y == 5 \? TRUE \: FALSE;
11+
ASSUME tmp_if_expr
12+
// baz
13+
ASSUME \*z == 7
14+
// foo
15+
ASSUME \*tmp_cc\$\d > 0
16+
ASSERT \*tmp_cc\$\d == 3
17+
--
18+
--
19+
Verification:
20+
This test checks support for a malloced pointer that is assigned to by
21+
a function (bar and baz). Both functions bar and baz are being replaced by
22+
their function contracts, while the calling function foo is being checked
23+
(by enforcing it's function contracts).

src/goto-instrument/code_contracts.cpp

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1639,11 +1639,40 @@ goto_programt assigns_clauset::havoc_code(
16391639
goto_programt havoc_statements;
16401640
for(assigns_clause_targett *target : targets)
16411641
{
1642+
// (1) If the assigned target is not a dereference,
1643+
// only include the havoc_statement
1644+
1645+
// (2) If the assigned target is a dereference, do the following:
1646+
1647+
// if(!__CPROVER_w_ok(target, 0)) goto z;
1648+
// havoc_statements
1649+
// z: skip
1650+
1651+
// create the z label
1652+
goto_programt tmp_z;
1653+
goto_programt::targett z = tmp_z.add(goto_programt::make_skip(location));
1654+
1655+
const auto &target_ptr = target->get_direct_pointer();
1656+
if(to_address_of_expr(target_ptr).object().id() == ID_dereference)
1657+
{
1658+
// create the condition
1659+
exprt condition =
1660+
not_exprt(w_ok_exprt(target_ptr, from_integer(0, integer_typet())));
1661+
havoc_statements.add(goto_programt::make_goto(z, condition, location));
1662+
}
1663+
1664+
// create havoc_statements
16421665
for(goto_programt::instructiont instruction :
16431666
target->havoc_code(location).instructions)
16441667
{
16451668
havoc_statements.add(std::move(instruction));
16461669
}
1670+
1671+
if(to_address_of_expr(target_ptr).object().id() == ID_dereference)
1672+
{
1673+
// add the z label instruction
1674+
havoc_statements.destructive_append(tmp_z);
1675+
}
16471676
}
16481677
return havoc_statements;
16491678
}
@@ -1692,8 +1721,28 @@ exprt assigns_clauset::compatible_expression(
16921721
{
16931722
if(first_iter)
16941723
{
1695-
current_target_compatible =
1696-
target->compatible_expression(*called_target);
1724+
// TODO: Optimize the validation below and remove code duplication
1725+
// See GitHub issue #6105 for further details
1726+
1727+
// Validating the called target through __CPROVER_w_ok() is
1728+
// only useful when the called target is a dereference
1729+
const auto &called_target_ptr = called_target->get_direct_pointer();
1730+
if(
1731+
to_address_of_expr(called_target_ptr).object().id() == ID_dereference)
1732+
{
1733+
// or_exprt is short-circuited, therefore
1734+
// target->compatible_expression(*called_target) would not be
1735+
// checked on invalid called_targets.
1736+
current_target_compatible = or_exprt(
1737+
not_exprt(
1738+
w_ok_exprt(called_target_ptr, from_integer(0, integer_typet()))),
1739+
target->compatible_expression(*called_target));
1740+
}
1741+
else
1742+
{
1743+
current_target_compatible =
1744+
target->compatible_expression(*called_target);
1745+
}
16971746
first_iter = false;
16981747
}
16991748
else

0 commit comments

Comments
 (0)