Skip to content

Commit d0eee83

Browse files
authored
Merge pull request #6334 from padhi-aws-forks/assigns-cleanup
Refactor assigns clause implementation
2 parents 3961473 + e1e7235 commit d0eee83

File tree

7 files changed

+172
-190
lines changed

7 files changed

+172
-190
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
int foo(int *x) __CPROVER_assigns(*x, *x)
2+
{
3+
*x = *x + 0;
4+
return *x + 5;
5+
}
6+
7+
int main()
8+
{
9+
int n = 4;
10+
n = foo(&n);
11+
12+
return 0;
13+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE
2+
main.c
3+
--enforce-all-contracts
4+
^Ignored duplicate expression '\*x' in assigns clause at file main\.c line \d+$
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
^VERIFICATION SUCCESSFUL$
8+
--
9+
--
10+
This test checks that repeated expressions in assigns clauses are
11+
correctly detected and ignored.

regression/contracts/assigns_validity_pointer_04/test.desc

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
CORE
1+
KNOWNBUG
22
main.c
3-
--enforce-contract foo --replace-call-with-contract bar --replace-call-with-contract baz
3+
--enforce-contract foo --replace-call-with-contract bar --replace-call-with-contract baz _ --pointer-primitive-check
44
^EXIT=0$
55
^SIGNAL=0$
66
^VERIFICATION SUCCESSFUL$
@@ -13,8 +13,13 @@ ASSUME .*::tmp_if_expr
1313
ASSUME \*z = 7
1414
--
1515
--
16-
Verification:
1716
This test checks support for a malloced pointer that is assigned to by
1817
a function (bar and baz). Both functions bar and baz are being replaced by
1918
their function contracts, while the calling function foo is being checked
2019
(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.

regression/contracts/test_aliasing_ensure_indirect/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ main.c
77
\[postcondition.\d+\] file main.c line \d+ Check ensures clause: SUCCESS
88
\[bar.\d+\] line \d+ Check that z is assignable: SUCCESS
99
\[foo.\d+\] line \d+ Check that \*x is assignable: SUCCESS
10-
\[foo.\d+\] line \d+ Check compatibility of assigns clause with the called function: SUCCESS
10+
\[foo.\d+\] line \d+ Check that callee's assigns clause is a subset of caller's: SUCCESS
1111
\[main.assertion.\d+\] line \d+ assertion \!\(n \< 4\): SUCCESS
1212
^VERIFICATION SUCCESSFUL$
1313
--

src/goto-instrument/contracts/assigns.cpp

Lines changed: 79 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*******************************************************************\
22
3-
Module: Specify write set in function contracts.
3+
Module: Specify write set in code contracts.
44
55
Author: Felipe R. Monteiro
66
@@ -19,205 +19,156 @@ Date: July 2021
1919
#include <util/c_types.h>
2020
#include <util/pointer_predicates.h>
2121

22-
assigns_clause_targett::assigns_clause_targett(
23-
const exprt &object,
24-
code_contractst &contract,
25-
messaget &log_parameter,
26-
const irep_idt &function_id)
27-
: contract(contract),
28-
init_block(),
29-
log(log_parameter),
30-
target(pointer_for(object)),
31-
target_id(object.id())
22+
assigns_clauset::targett::targett(
23+
const assigns_clauset &parent,
24+
const exprt &expr)
25+
: address(address_of_exprt(normalize(expr))),
26+
expr(expr),
27+
id(expr.id()),
28+
parent(parent)
3229
{
33-
INVARIANT(
34-
target.type().id() == ID_pointer,
35-
"Assigns clause targets should be stored as pointer expressions.");
3630
}
3731

38-
assigns_clause_targett::~assigns_clause_targett()
32+
exprt assigns_clauset::targett::normalize(const exprt &expr)
3933
{
34+
if(expr.id() != ID_address_of)
35+
return expr;
36+
37+
const auto &object = to_address_of_expr(expr).object();
38+
if(object.id() != ID_index)
39+
return object;
40+
41+
return to_index_expr(object).array();
4042
}
4143

42-
exprt assigns_clause_targett::alias_expression(const exprt &lhs)
44+
exprt assigns_clauset::targett::generate_containment_check(
45+
const address_of_exprt &lhs_address) const
4346
{
4447
exprt::operandst condition;
45-
exprt lhs_ptr = (lhs.id() == ID_address_of) ? to_address_of_expr(lhs).object()
46-
: pointer_for(lhs);
4748

4849
// __CPROVER_w_ok(target, sizeof(target))
4950
condition.push_back(w_ok_exprt(
50-
target,
51-
size_of_expr(dereference_exprt(target).type(), contract.ns).value()));
51+
address,
52+
size_of_expr(dereference_exprt(address).type(), parent.ns).value()));
5253

5354
// __CPROVER_same_object(lhs, target)
54-
condition.push_back(same_object(lhs_ptr, target));
55+
condition.push_back(same_object(lhs_address, address));
5556

5657
// If assigns target was a dereference, comparing objects is enough
57-
if(target_id == ID_dereference)
58+
if(id == ID_dereference)
5859
{
5960
return conjunction(condition);
6061
}
6162

62-
const exprt lhs_offset = pointer_offset(lhs_ptr);
63-
const exprt target_offset = pointer_offset(target);
63+
const auto lhs_offset = pointer_offset(lhs_address);
64+
const auto own_offset = pointer_offset(address);
6465

6566
// __CPROVER_offset(lhs) >= __CPROVER_offset(target)
66-
condition.push_back(binary_relation_exprt(lhs_offset, ID_ge, target_offset));
67+
condition.push_back(binary_relation_exprt(lhs_offset, ID_ge, own_offset));
6768

68-
const exprt region_lhs = plus_exprt(
69+
const auto lhs_region = plus_exprt(
6970
typecast_exprt::conditional_cast(
70-
size_of_expr(lhs.type(), contract.ns).value(), lhs_offset.type()),
71+
size_of_expr(lhs_address.object().type(), parent.ns).value(),
72+
lhs_offset.type()),
7173
lhs_offset);
7274

73-
const exprt region_target = plus_exprt(
75+
const exprt own_region = plus_exprt(
7476
typecast_exprt::conditional_cast(
75-
size_of_expr(dereference_exprt(target).type(), contract.ns).value(),
76-
target_offset.type()),
77-
target_offset);
77+
size_of_expr(address.object().type(), parent.ns).value(),
78+
own_offset.type()),
79+
own_offset);
7880

7981
// (sizeof(lhs) + __CPROVER_offset(lhs)) <=
8082
// (sizeof(target) + __CPROVER_offset(target))
81-
condition.push_back(binary_relation_exprt(region_lhs, ID_le, region_target));
83+
condition.push_back(binary_relation_exprt(lhs_region, ID_le, own_region));
8284

8385
return conjunction(condition);
8486
}
8587

86-
exprt assigns_clause_targett::compatible_expression(
87-
const assigns_clause_targett &called_target)
88-
{
89-
return same_object(called_target.get_target(), target);
90-
}
91-
92-
const exprt &assigns_clause_targett::get_target() const
93-
{
94-
return target;
95-
}
96-
9788
assigns_clauset::assigns_clauset(
98-
const exprt &assigns,
99-
code_contractst &contract,
100-
const irep_idt function_id,
101-
messaget log_parameter)
102-
: assigns(assigns),
103-
parent(contract),
104-
function_id(function_id),
105-
log(log_parameter)
89+
const exprt &expr,
90+
const messaget &log,
91+
const namespacet &ns)
92+
: expr(expr), log(log), ns(ns)
10693
{
107-
for(exprt target : assigns.operands())
94+
for(const auto &target_expr : expr.operands())
10895
{
109-
add_target(target);
96+
add_target(target_expr);
11097
}
11198
}
11299

113-
assigns_clauset::~assigns_clauset()
100+
void assigns_clauset::add_target(const exprt &target_expr)
114101
{
115-
for(assigns_clause_targett *target : targets)
102+
auto insertion_succeeded = targets.emplace(*this, target_expr).second;
103+
104+
if(!insertion_succeeded)
116105
{
117-
delete target;
106+
log.warning() << "Ignored duplicate expression '"
107+
<< from_expr(ns, target_expr.id(), target_expr)
108+
<< "' in assigns clause at "
109+
<< target_expr.source_location().as_string() << messaget::eom;
118110
}
119111
}
120112

121-
void assigns_clauset::add_target(exprt target)
113+
void assigns_clauset::remove_target(const exprt &target_expr)
122114
{
123-
assigns_clause_targett *new_target = new assigns_clause_targett(
124-
(target.id() == ID_address_of)
125-
? to_index_expr(to_address_of_expr(target).object()).array()
126-
: target,
127-
parent,
128-
log,
129-
function_id);
130-
targets.push_back(new_target);
115+
targets.erase(targett(*this, targett::normalize(target_expr)));
131116
}
132117

133-
goto_programt assigns_clauset::havoc_code()
118+
goto_programt assigns_clauset::generate_havoc_code() const
134119
{
135120
modifiest modifies;
136-
for(const auto &t : targets)
137-
modifies.insert(to_address_of_expr(t->get_target()).object());
121+
for(const auto &target : targets)
122+
modifies.insert(target.address.object());
138123

139124
goto_programt havoc_statements;
140-
append_havoc_code(assigns.source_location(), modifies, havoc_statements);
125+
append_havoc_code(expr.source_location(), modifies, havoc_statements);
141126
return havoc_statements;
142127
}
143128

144-
exprt assigns_clauset::alias_expression(const exprt &lhs)
129+
exprt assigns_clauset::generate_containment_check(const exprt &lhs) const
145130
{
146131
// If write set is empty, no assignment is allowed.
147132
if(targets.empty())
148-
{
149133
return false_exprt();
150-
}
134+
135+
const auto lhs_address = address_of_exprt(targett::normalize(lhs));
151136

152137
exprt::operandst condition;
153-
for(assigns_clause_targett *target : targets)
138+
for(const auto &target : targets)
154139
{
155-
condition.push_back(target->alias_expression(lhs));
140+
condition.push_back(target.generate_containment_check(lhs_address));
156141
}
157142
return disjunction(condition);
158143
}
159144

160-
exprt assigns_clauset::compatible_expression(
161-
const assigns_clauset &called_assigns)
145+
exprt assigns_clauset::generate_subset_check(
146+
const assigns_clauset &subassigns) const
162147
{
163-
if(called_assigns.targets.empty())
164-
{
148+
if(subassigns.targets.empty())
165149
return true_exprt();
166-
}
167150

168-
bool first_clause = true;
169151
exprt result = true_exprt();
170-
for(assigns_clause_targett *called_target : called_assigns.targets)
152+
for(const auto &subtarget : subassigns.targets)
171153
{
172-
bool first_iter = true;
173-
exprt current_target_compatible = false_exprt();
174-
for(assigns_clause_targett *target : targets)
175-
{
176-
if(first_iter)
177-
{
178-
// TODO: Optimize the validation below and remove code duplication
179-
// See GitHub issue #6105 for further details
180-
181-
// Validating the called target through __CPROVER_w_ok() is
182-
// only useful when the called target is a dereference
183-
const auto &called_target_ptr = called_target->get_target();
184-
if(
185-
to_address_of_expr(called_target_ptr).object().id() == ID_dereference)
186-
{
187-
// or_exprt is short-circuited, therefore
188-
// target->compatible_expression(*called_target) would not be
189-
// checked on invalid called_targets.
190-
current_target_compatible = or_exprt(
191-
not_exprt(w_ok_exprt(
192-
called_target_ptr, from_integer(0, unsigned_int_type()))),
193-
target->compatible_expression(*called_target));
194-
}
195-
else
196-
{
197-
current_target_compatible =
198-
target->compatible_expression(*called_target);
199-
}
200-
first_iter = false;
201-
}
202-
else
203-
{
204-
current_target_compatible = or_exprt(
205-
current_target_compatible,
206-
target->compatible_expression(*called_target));
207-
}
208-
}
209-
if(first_clause)
210-
{
211-
result = current_target_compatible;
212-
first_clause = false;
213-
}
214-
else
154+
// TODO: Optimize the implication generated due to the validity check.
155+
// In some cases, e.g. when `subtarget` is known to be `NULL`,
156+
// the implication can be skipped entirely. See #6105 for more details.
157+
auto validity_check =
158+
w_ok_exprt(subtarget.address, from_integer(0, unsigned_int_type()));
159+
160+
exprt::operandst current_subtarget_found_conditions;
161+
for(const auto &target : targets)
215162
{
216-
exprt::operandst conjuncts;
217-
conjuncts.push_back(result);
218-
conjuncts.push_back(current_target_compatible);
219-
result = conjunction(conjuncts);
163+
current_subtarget_found_conditions.push_back(
164+
target.generate_containment_check(subtarget.address));
220165
}
166+
167+
auto current_subtarget_found = or_exprt(
168+
not_exprt(validity_check),
169+
disjunction(current_subtarget_found_conditions));
170+
171+
result = and_exprt(result, current_subtarget_found);
221172
}
222173

223174
return result;

0 commit comments

Comments
 (0)