Skip to content

Commit 7e05751

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 7795e1e commit 7e05751

File tree

9 files changed

+210
-2
lines changed

9 files changed

+210
-2
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#include <assert.h>
2+
#include <stddef.h>
3+
4+
void bar(int *x, int *y) __CPROVER_assigns(*x, *y) __CPROVER_requires(*x > 0)
5+
__CPROVER_ensures(*x == 3 && (y == NULL || *y == 5))
6+
{
7+
*x = 3;
8+
if(y != NULL)
9+
*y = 5;
10+
}
11+
12+
void foo(int *x) __CPROVER_assigns(*x) __CPROVER_requires(*x > 0)
13+
__CPROVER_ensures(*x == 3)
14+
{
15+
bar(x, NULL);
16+
}
17+
18+
int main()
19+
{
20+
int n;
21+
foo(&n);
22+
23+
assert(n == 3);
24+
return 0;
25+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
CORE
2+
main.c
3+
--enforce-contract foo --replace-call-with-contract bar
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
Verification:
10+
This test checks support for a NULL pointer passed as a parameter to a
11+
function that may assign to it. The NULL pointer is passed to function
12+
bar which has been replaced by it's function contracts, while the calling
13+
function foo is being checked (by enforcing it's function contracts).
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#include <assert.h>
2+
#include <stddef.h>
3+
4+
void bar(int *x, int *y) __CPROVER_assigns(*x, *y) __CPROVER_requires(*x > 0)
5+
__CPROVER_ensures(*x == 3 && (y == NULL || *y == 5))
6+
{
7+
*x = 3;
8+
if(y != NULL)
9+
*y = 5;
10+
}
11+
12+
void foo(int *x) __CPROVER_assigns(*x) __CPROVER_requires(*x > 0)
13+
__CPROVER_ensures(*x == 3)
14+
{
15+
bar(x, NULL);
16+
*x = 3;
17+
}
18+
19+
int main()
20+
{
21+
int n;
22+
foo(&n);
23+
24+
assert(n == 3);
25+
return 0;
26+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
CORE
2+
main.c
3+
--enforce-contract foo
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
Verification:
10+
This test checks support for a NULL pointer passed as a parameter to a
11+
function that may assign to it. The NULL pointer is passed to function
12+
bar, while the calling function foo is being checked (by enforcing
13+
it's function contracts).
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#include <assert.h>
2+
#include <stddef.h>
3+
4+
void bar(int *x, int *y) __CPROVER_assigns(*x, *y) __CPROVER_requires(*x > 0)
5+
__CPROVER_ensures(*x == 3 && *y == 5)
6+
{
7+
}
8+
9+
void foo(int *x) __CPROVER_assigns(*x) __CPROVER_requires(*x > 0)
10+
__CPROVER_ensures(*x == 3)
11+
{
12+
int *y;
13+
bar(x, y);
14+
assert(*y == 5);
15+
}
16+
17+
int main()
18+
{
19+
int n;
20+
foo(&n);
21+
22+
assert(n == 3);
23+
return 0;
24+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
KNOWNBUG
2+
main.c
3+
--enforce-contract foo --replace-call-with-contract bar
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
Verification:
10+
This test checks support for an uninitialized pointer passed as a parameter
11+
to a function that may assign to it. The uninitialized pointer is passed to
12+
function bar which has been replaced by it's function contracts, while the
13+
calling function foo is being checked (by enforcing it's function contracts).
14+
15+
Known Bug:
16+
Currently, there is a known issue with __CPROVER_w_ok(ptr, 0) such that it
17+
returns true if ptr is uninitialized. This is not the expected behavior,
18+
therefore, the outcome of this test case is currently incorrect.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#include <assert.h>
2+
#include <stddef.h>
3+
4+
void bar(int *x, int *y) __CPROVER_assigns(*x, *y) __CPROVER_requires(*x > 0)
5+
__CPROVER_ensures(*x == 3 && *y == 5)
6+
{
7+
}
8+
9+
void foo(int *x) __CPROVER_assigns(*x) __CPROVER_requires(*x > 0)
10+
__CPROVER_ensures(*x == 3)
11+
{
12+
int *y = malloc(sizeof(int));
13+
bar(x, y);
14+
assert(*y == 5);
15+
}
16+
17+
int main()
18+
{
19+
int n;
20+
foo(&n);
21+
22+
assert(n == 3);
23+
return 0;
24+
}
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-contract foo --replace-call-with-contract bar
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
Verification:
10+
This test checks support for a malloced pointer passed as a parameter
11+
to a function that may assign to it. The malloced pointer is passed to
12+
function bar which has been replaced by it's function contracts, while the
13+
calling function foo is being checked (by enforcing it's function contracts).
14+

src/goto-instrument/code_contracts.cpp

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1542,11 +1542,43 @@ goto_programt assigns_clauset::havoc_code(
15421542
goto_programt havoc_statements;
15431543
for(assigns_clause_targett *target : targets)
15441544
{
1545+
// (1) If the assigned target is not a dereference,
1546+
// only include the havoc_statement
1547+
1548+
// (2) If the assigned target is a dereference, do the following:
1549+
1550+
// if( !__CPROVER_w_ok(target, 0) goto z;
1551+
// havoc_statements
1552+
// z: skip
1553+
1554+
// create the z label
1555+
goto_programt tmp_z;
1556+
goto_programt::targett z = tmp_z.add(goto_programt::make_skip(location));
1557+
1558+
if(
1559+
to_address_of_expr(target->get_direct_pointer()).object().id() ==
1560+
ID_dereference)
1561+
{
1562+
// create the condition
1563+
exprt condition = not_exprt(w_ok_exprt(
1564+
target->get_direct_pointer(), from_integer(0, integer_typet())));
1565+
havoc_statements.add(goto_programt::make_goto(z, condition, location));
1566+
}
1567+
1568+
// create havoc_statements
15451569
for(goto_programt::instructiont instruction :
15461570
target->havoc_code(location).instructions)
15471571
{
15481572
havoc_statements.add(std::move(instruction));
15491573
}
1574+
1575+
if(
1576+
to_address_of_expr(target->get_direct_pointer()).object().id() ==
1577+
ID_dereference)
1578+
{
1579+
// add the z label instruction
1580+
havoc_statements.destructive_append(tmp_z);
1581+
}
15501582
}
15511583
return havoc_statements;
15521584
}
@@ -1595,8 +1627,27 @@ exprt assigns_clauset::compatible_expression(
15951627
{
15961628
if(first_iter)
15971629
{
1598-
current_target_compatible =
1599-
target->compatible_expression(*called_target);
1630+
// TODO: Optimize the validation below and remove code duplication
1631+
// See GitHub issue #6105 for further details
1632+
1633+
// Validating the called target through __CPROVER_w_ok() is
1634+
// only useful when the called target is a dereference
1635+
if(
1636+
to_address_of_expr(called_target->get_direct_pointer())
1637+
.object()
1638+
.id() == ID_dereference)
1639+
{
1640+
current_target_compatible = or_exprt(
1641+
not_exprt(w_ok_exprt(
1642+
called_target->get_direct_pointer(),
1643+
from_integer(0, integer_typet()))),
1644+
target->compatible_expression(*called_target));
1645+
}
1646+
else
1647+
{
1648+
current_target_compatible =
1649+
target->compatible_expression(*called_target);
1650+
}
16001651
first_iter = false;
16011652
}
16021653
else

0 commit comments

Comments
 (0)