Skip to content

Commit 7435884

Browse files
authored
Merge pull request #6331 from feliperodri/handle-free
Adds the notion of a local write set to contracts
2 parents 14d35cc + 39c62dd commit 7435884

File tree

10 files changed

+243
-115
lines changed

10 files changed

+243
-115
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#include <assert.h>
2+
#include <stdlib.h>
3+
4+
int x = 0;
5+
int y = 0;
6+
7+
void foo(int *xp, int *xq, int a) __CPROVER_assigns(*xp)
8+
{
9+
a = 2;
10+
int *yp = malloc(sizeof(int));
11+
free(yp);
12+
int z = 3;
13+
*xp = 1;
14+
y = -1;
15+
}
16+
17+
void bar(int *a, int *b) __CPROVER_assigns(*a, *b)
18+
{
19+
free(a);
20+
*b = 0;
21+
}
22+
23+
void baz(int *a, int *c) __CPROVER_assigns(*a)
24+
{
25+
free(c);
26+
*a = 0;
27+
}
28+
29+
int main()
30+
{
31+
int z = 0;
32+
foo(&x, &z, z);
33+
assert(x == 1);
34+
assert(y == -1);
35+
assert(z == 0);
36+
int *a = malloc(sizeof(*a));
37+
int b = 1;
38+
bar(a, &b);
39+
assert(b == 0);
40+
int *c = malloc(sizeof(*c));
41+
baz(&y, c);
42+
return 0;
43+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
CORE
2+
main.c
3+
--enforce-all-contracts _ --pointer-primitive-check
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
^\[bar.\d+\] line \d+ Check that \*a is assignable: SUCCESS$
7+
^\[bar.\d+\] line \d+ Check that \*b is assignable: SUCCESS$
8+
^\[baz.\d+\] line \d+ Check that \*c is assignable: FAILURE$
9+
^\[baz.\d+\] line \d+ Check that \*a is assignable: SUCCESS$
10+
^\[foo.\d+\] line \d+ Check that a is assignable: SUCCESS$
11+
^\[foo.\d+\] line \d+ Check that yp is assignable: SUCCESS$
12+
^\[foo.\d+\] line \d+ Check that z is assignable: SUCCESS$
13+
^\[foo.\d+\] line \d+ Check that \*xp is assignable: SUCCESS$
14+
^\[foo.\d+\] line \d+ Check that y is assignable: FAILURE$
15+
^.* 2 of \d+ failed \(\d+ iteration.*\)$
16+
^VERIFICATION FAILED$
17+
--
18+
^\[foo.\d+\] line \d+ Check that a is assignable: FAILURE$
19+
^\[foo.pointer\_primitives.\d+\] line \d+ deallocated dynamic object in POINTER_OBJECT\(tmp\_cc\$\d+\): FAILURE$
20+
^.* 3 of \d+ failed (\d+ iterations)$
21+
--
22+
Checks whether contract enforcement works with functions that deallocate memory.
23+
We had problems before when freeing a variable, but still keeping it on
24+
the writable set, which lead to deallocated variables issues.
25+
Now, if a memory is deallocated, we remove it from the our freely assignable set.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#include <assert.h>
2+
3+
static int b = 0;
4+
static int c = 0;
5+
6+
int f() __CPROVER_assigns()
7+
{
8+
static int a = 0;
9+
a++;
10+
return a;
11+
}
12+
13+
int g(int *points_to_b, int *points_to_c)
14+
__CPROVER_assigns(b) // Error: it should also mention c
15+
{
16+
b = 1;
17+
c = 2;
18+
*points_to_b = 1;
19+
*points_to_c = 2;
20+
}
21+
22+
int main()
23+
{
24+
assert(f() == 1);
25+
assert(f() == 2);
26+
assert(b == 0);
27+
assert(c == 0);
28+
g(&b, &c);
29+
assert(b == 1);
30+
assert(c == 2);
31+
32+
return 0;
33+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
CORE
2+
main.c
3+
--enforce-all-contracts
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
^\[f.\d+\] line \d+ Check that a is assignable: SUCCESS$
7+
^\[g.\d+\] line \d+ Check that b is assignable: SUCCESS$
8+
^\[g.\d+\] line \d+ Check that c is assignable: FAILURE$
9+
^\[g.\d+\] line \d+ Check that \*points\_to\_b is assignable: SUCCESS$
10+
^\[g.\d+\] line \d+ Check that \*points\_to\_c is assignable: FAILURE$
11+
^VERIFICATION FAILED$
12+
--
13+
--
14+
Checks whether contract enforcement works with (local and global) static variables.
15+
Local static variables should be part of the local write set.
16+
Global static variables should be included in the global write set,
17+
otherwise any assignment to it is invalid.

regression/contracts/assigns_enforce_structs_04/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ main.c
66
^\[f1.\d+\] line \d+ Check that p->y is assignable: FAILURE$
77
^\[f2.\d+\] line \d+ Check that p->x is assignable: FAILURE$
88
^\[f3.\d+\] line \d+ Check that p->y is assignable: SUCCESS$
9-
^\[f4.\d+\] line \d+ Check that p is assignable: FAILURE$
9+
^\[f4.\d+\] line \d+ Check that p is assignable: SUCCESS$
1010
^VERIFICATION FAILED$
1111
--
1212
--
Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
KNOWNBUG
1+
CORE
22
main.c
33
--enforce-contract foo --replace-call-with-contract bar --replace-call-with-contract baz _ --pointer-primitive-check
4-
^EXIT=0$
4+
^EXIT=10$
55
^SIGNAL=0$
6-
^VERIFICATION SUCCESSFUL$
6+
^\[foo.\d+\] line \d+ Check that z is assignable: FAILURE$
7+
^.* 1 of \d+ failed \(\d+ iteration.*\)$
8+
^VERIFICATION FAILED$
79
// bar
810
ASSERT \*foo::x > 0
911
IF ¬\(\*foo::x = 3\) THEN GOTO \d
@@ -17,9 +19,3 @@ This test checks support for a malloced pointer that is assigned to by
1719
a function (bar and baz). Both functions bar and baz are being replaced by
1820
their function contracts, while the calling function foo is being checked
1921
(by enforcing it's function contracts).
20-
21-
BUG: `z` is being assigned to in `foo`, but is not in `foo`s assigns clause!
22-
This test is expected to pass but it should not.
23-
It somehow used to (and still does on *nix), but that seems buggy.
24-
Most likely the bug is related to `freely_assignable_symbols`,
25-
which would be properly fixed in a subsequent PR.

src/goto-instrument/contracts/assigns.cpp

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ assigns_clauset::targett::targett(
2323
const assigns_clauset &parent,
2424
const exprt &expr)
2525
: address(address_of_exprt(normalize(expr))),
26-
expr(expr),
2726
id(expr.id()),
2827
parent(parent)
2928
{
@@ -89,17 +88,18 @@ assigns_clauset::assigns_clauset(
8988
const exprt &expr,
9089
const messaget &log,
9190
const namespacet &ns)
92-
: expr(expr), log(log), ns(ns)
91+
: location(expr.source_location()), log(log), ns(ns)
9392
{
9493
for(const auto &target_expr : expr.operands())
9594
{
96-
add_target(target_expr);
95+
add_to_global_write_set(target_expr);
9796
}
9897
}
9998

100-
void assigns_clauset::add_target(const exprt &target_expr)
99+
void assigns_clauset::add_to_global_write_set(const exprt &target_expr)
101100
{
102-
auto insertion_succeeded = targets.emplace(*this, target_expr).second;
101+
auto insertion_succeeded =
102+
global_write_set.emplace(*this, target_expr).second;
103103

104104
if(!insertion_succeeded)
105105
{
@@ -110,32 +110,50 @@ void assigns_clauset::add_target(const exprt &target_expr)
110110
}
111111
}
112112

113-
void assigns_clauset::remove_target(const exprt &target_expr)
113+
void assigns_clauset::remove_from_global_write_set(const exprt &target_expr)
114114
{
115-
targets.erase(targett(*this, targett::normalize(target_expr)));
115+
global_write_set.erase(targett(*this, target_expr));
116+
}
117+
118+
void assigns_clauset::add_to_local_write_set(const exprt &expr)
119+
{
120+
local_write_set.emplace(*this, expr);
121+
}
122+
123+
void assigns_clauset::remove_from_local_write_set(const exprt &expr)
124+
{
125+
local_write_set.erase(targett(*this, expr));
116126
}
117127

118128
goto_programt assigns_clauset::generate_havoc_code() const
119129
{
120130
modifiest modifies;
121-
for(const auto &target : targets)
131+
for(const auto &target : global_write_set)
132+
modifies.insert(target.address.object());
133+
134+
for(const auto &target : local_write_set)
122135
modifies.insert(target.address.object());
123136

124137
goto_programt havoc_statements;
125-
append_havoc_code(expr.source_location(), modifies, havoc_statements);
138+
append_havoc_code(location, modifies, havoc_statements);
126139
return havoc_statements;
127140
}
128141

129142
exprt assigns_clauset::generate_containment_check(const exprt &lhs) const
130143
{
131144
// If write set is empty, no assignment is allowed.
132-
if(targets.empty())
145+
if(global_write_set.empty() && local_write_set.empty())
133146
return false_exprt();
134147

135148
const auto lhs_address = address_of_exprt(targett::normalize(lhs));
136149

137150
exprt::operandst condition;
138-
for(const auto &target : targets)
151+
for(const auto &target : local_write_set)
152+
{
153+
condition.push_back(target.generate_containment_check(lhs_address));
154+
}
155+
156+
for(const auto &target : global_write_set)
139157
{
140158
condition.push_back(target.generate_containment_check(lhs_address));
141159
}
@@ -145,11 +163,16 @@ exprt assigns_clauset::generate_containment_check(const exprt &lhs) const
145163
exprt assigns_clauset::generate_subset_check(
146164
const assigns_clauset &subassigns) const
147165
{
148-
if(subassigns.targets.empty())
166+
if(subassigns.global_write_set.empty())
149167
return true_exprt();
150168

169+
INVARIANT(
170+
subassigns.local_write_set.empty(),
171+
"Local write set for function calls should be empty at this point.\n" +
172+
subassigns.location.as_string());
173+
151174
exprt result = true_exprt();
152-
for(const auto &subtarget : subassigns.targets)
175+
for(const auto &subtarget : subassigns.global_write_set)
153176
{
154177
// TODO: Optimize the implication generated due to the validity check.
155178
// In some cases, e.g. when `subtarget` is known to be `NULL`,
@@ -158,7 +181,12 @@ exprt assigns_clauset::generate_subset_check(
158181
w_ok_exprt(subtarget.address, from_integer(0, unsigned_int_type()));
159182

160183
exprt::operandst current_subtarget_found_conditions;
161-
for(const auto &target : targets)
184+
for(const auto &target : global_write_set)
185+
{
186+
current_subtarget_found_conditions.push_back(
187+
target.generate_containment_check(subtarget.address));
188+
}
189+
for(const auto &target : local_write_set)
162190
{
163191
current_subtarget_found_conditions.push_back(
164192
target.generate_containment_check(subtarget.address));

src/goto-instrument/contracts/assigns.h

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,38 +34,40 @@ class assigns_clauset
3434

3535
bool operator==(const targett &other) const
3636
{
37-
return expr == other.expr;
37+
return address == other.address;
3838
}
3939

4040
struct hasht
4141
{
4242
std::size_t operator()(const targett &target) const
4343
{
44-
return irep_hash{}(target.expr);
44+
return irep_hash{}(target.address);
4545
}
4646
};
4747

4848
const address_of_exprt address;
49-
const exprt &expr;
5049
const irep_idt &id;
5150
const assigns_clauset &parent;
5251
};
5352

5453
assigns_clauset(const exprt &, const messaget &, const namespacet &);
5554

56-
void add_target(const exprt &);
57-
void remove_target(const exprt &);
55+
void add_to_global_write_set(const exprt &);
56+
void remove_from_global_write_set(const exprt &);
57+
void add_to_local_write_set(const exprt &);
58+
void remove_from_local_write_set(const exprt &);
5859

5960
goto_programt generate_havoc_code() const;
6061
exprt generate_containment_check(const exprt &) const;
6162
exprt generate_subset_check(const assigns_clauset &) const;
6263

63-
const exprt &expr;
64+
const source_locationt &location;
6465
const messaget &log;
6566
const namespacet &ns;
6667

6768
protected:
68-
std::unordered_set<targett, targett::hasht> targets;
69+
std::unordered_set<targett, targett::hasht> global_write_set;
70+
std::unordered_set<targett, targett::hasht> local_write_set;
6971
};
7072

7173
#endif // CPROVER_GOTO_INSTRUMENT_CONTRACTS_ASSIGNS_H

0 commit comments

Comments
 (0)