Skip to content

Commit d5402d9

Browse files
committed
Adds support for null pointers passed as function call parameter.
This adds support for the cases where a null pointer is passed as a parameter to a function that may assign to it. We add checks in goto program to handle such instances as special cases of assigns clause handling.
1 parent 25a7b36 commit d5402d9

File tree

10 files changed

+294
-3
lines changed

10 files changed

+294
-3
lines changed
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.
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+
}
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).
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+
}
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.
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+
}
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.
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+
}
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

+51-2
Original file line numberDiff line numberDiff line change
@@ -1524,11 +1524,40 @@ goto_programt assigns_clauset::havoc_code(
15241524
goto_programt havoc_statements;
15251525
for(assigns_clause_targett *target : targets)
15261526
{
1527+
// (1) If the assigned target is not a dereference,
1528+
// only include the havoc_statement
1529+
1530+
// (2) If the assigned target is a dereference, do the following:
1531+
1532+
// if(!__CPROVER_w_ok(target, 0)) goto z;
1533+
// havoc_statements
1534+
// z: skip
1535+
1536+
// create the z label
1537+
goto_programt tmp_z;
1538+
goto_programt::targett z = tmp_z.add(goto_programt::make_skip(location));
1539+
1540+
const auto &target_ptr = target->get_direct_pointer();
1541+
if(to_address_of_expr(target_ptr).object().id() == ID_dereference)
1542+
{
1543+
// create the condition
1544+
exprt condition =
1545+
not_exprt(w_ok_exprt(target_ptr, from_integer(0, integer_typet())));
1546+
havoc_statements.add(goto_programt::make_goto(z, condition, location));
1547+
}
1548+
1549+
// create havoc_statements
15271550
for(goto_programt::instructiont instruction :
15281551
target->havoc_code(location).instructions)
15291552
{
15301553
havoc_statements.add(std::move(instruction));
15311554
}
1555+
1556+
if(to_address_of_expr(target_ptr).object().id() == ID_dereference)
1557+
{
1558+
// add the z label instruction
1559+
havoc_statements.destructive_append(tmp_z);
1560+
}
15321561
}
15331562
return havoc_statements;
15341563
}
@@ -1577,8 +1606,28 @@ exprt assigns_clauset::compatible_expression(
15771606
{
15781607
if(first_iter)
15791608
{
1580-
current_target_compatible =
1581-
target->compatible_expression(*called_target);
1609+
// TODO: Optimize the validation below and remove code duplication
1610+
// See GitHub issue #6105 for further details
1611+
1612+
// Validating the called target through __CPROVER_w_ok() is
1613+
// only useful when the called target is a dereference
1614+
const auto &called_target_ptr = called_target->get_direct_pointer();
1615+
if(
1616+
to_address_of_expr(called_target_ptr).object().id() == ID_dereference)
1617+
{
1618+
// or_exprt is short-circuited, therefore
1619+
// target->compatible_expression(*called_target) would not be
1620+
// checked on invalid called_targets.
1621+
current_target_compatible = or_exprt(
1622+
not_exprt(
1623+
w_ok_exprt(called_target_ptr, from_integer(0, integer_typet()))),
1624+
target->compatible_expression(*called_target));
1625+
}
1626+
else
1627+
{
1628+
current_target_compatible =
1629+
target->compatible_expression(*called_target);
1630+
}
15821631
first_iter = false;
15831632
}
15841633
else

0 commit comments

Comments
 (0)