Skip to content

Commit 3ebacfe

Browse files
authored
Merge pull request #6269 from padhi-aws-forks/decreases-fix
Fix evaluation of decreases clause
2 parents fc3d885 + dbb5163 commit 3ebacfe

File tree

4 files changed

+134
-110
lines changed

4 files changed

+134
-110
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
int main()
2+
{
3+
unsigned start, max;
4+
unsigned i = start;
5+
6+
while(i < max)
7+
// clang-format off
8+
__CPROVER_loop_invariant(
9+
(start > max && i == start) ||
10+
(start <= i && i <= max)
11+
)
12+
__CPROVER_decreases(max - i)
13+
// clang-format on
14+
{
15+
i++;
16+
}
17+
18+
return 0;
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
CORE
2+
main.c
3+
--apply-loop-contracts _ --unsigned-overflow-check
4+
^\[main.1\] .* Check loop invariant before entry: SUCCESS$
5+
^\[main.2\] .* Check that loop invariant is preserved: SUCCESS$
6+
^\[main.3\] .* Check decreases clause on loop iteration: SUCCESS$
7+
^\[main.overflow.1\] .* arithmetic overflow on unsigned - in max - i: SUCCESS$
8+
^\[main.overflow.3\] .* arithmetic overflow on unsigned - in max - i: SUCCESS$
9+
^\[main.overflow.2\] .* arithmetic overflow on unsigned \+ in i \+ 1u: SUCCESS$
10+
^VERIFICATION SUCCESSFUL$
11+
^EXIT=0$
12+
^SIGNAL=0$
13+
--
14+
--
15+
This test checks that the decreases clause is evaluated only within the loop iteration,
16+
not outside of it (before the loop guard).
17+
The `main.overflow.1` check would fail if the decreases clause `(max - i)` is evaluated
18+
before the loop guard is satisfied. This would occur when `start > max` and therefore
19+
`i > max` after assuming the invariant.

src/goto-instrument/contracts/contracts.cpp

+95-109
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,62 @@ Date: February 2016
3838
// This is used in the implementation of multidimensional decreases clauses.
3939
static exprt create_lexicographic_less_than(
4040
const std::vector<symbol_exprt> &lhs,
41-
const std::vector<symbol_exprt> &rhs);
41+
const std::vector<symbol_exprt> &rhs)
42+
{
43+
PRECONDITION(lhs.size() == rhs.size());
44+
45+
if(lhs.empty())
46+
{
47+
return false_exprt();
48+
}
4249

43-
exprt get_size(const typet &type, const namespacet &ns, messaget &log)
50+
// Store conjunctions of equalities.
51+
// For example, suppose that the two input vectors are <s1, s2, s3> and <l1,
52+
// l2, l3>.
53+
// Then this vector stores <s1 == l1, s1 == l1 && s2 == l2,
54+
// s1 == l1 && s2 == l2 && s3 == l3>.
55+
// In fact, the last element is unnecessary, so we do not create it.
56+
exprt::operandst equality_conjunctions(lhs.size());
57+
equality_conjunctions[0] = binary_relation_exprt(lhs[0], ID_equal, rhs[0]);
58+
for(size_t i = 1; i < equality_conjunctions.size() - 1; i++)
59+
{
60+
binary_relation_exprt component_i_equality{lhs[i], ID_equal, rhs[i]};
61+
equality_conjunctions[i] =
62+
and_exprt(equality_conjunctions[i - 1], component_i_equality);
63+
}
64+
65+
// Store inequalities between the i-th components of the input vectors
66+
// (i.e. lhs and rhs).
67+
// For example, suppose that the two input vectors are <s1, s2, s3> and <l1,
68+
// l2, l3>.
69+
// Then this vector stores <s1 < l1, s1 == l1 && s2 < l2, s1 == l1 &&
70+
// s2 == l2 && s3 < l3>.
71+
exprt::operandst lexicographic_individual_comparisons(lhs.size());
72+
lexicographic_individual_comparisons[0] =
73+
binary_relation_exprt(lhs[0], ID_lt, rhs[0]);
74+
for(size_t i = 1; i < lexicographic_individual_comparisons.size(); i++)
75+
{
76+
binary_relation_exprt component_i_less_than{lhs[i], ID_lt, rhs[i]};
77+
lexicographic_individual_comparisons[i] =
78+
and_exprt(equality_conjunctions[i - 1], component_i_less_than);
79+
}
80+
return disjunction(lexicographic_individual_comparisons);
81+
}
82+
83+
static void insert_before_swap_and_advance(
84+
goto_programt &program,
85+
goto_programt::targett &target,
86+
goto_programt &payload)
4487
{
45-
auto size_of_opt = size_of_expr(type, ns);
46-
CHECK_RETURN(size_of_opt.has_value());
47-
exprt result = size_of_opt.value();
48-
result.add(ID_C_c_sizeof_type) = type;
49-
return result;
88+
const auto offset = payload.instructions.size();
89+
program.insert_before_swap(target, payload);
90+
std::advance(target, offset);
5091
}
5192

5293
void code_contractst::check_apply_loop_contracts(
5394
goto_functionst::goto_functiont &goto_function,
5495
const local_may_aliast &local_may_alias,
55-
const goto_programt::targett loop_head,
96+
goto_programt::targett loop_head,
5697
const loopt &loop,
5798
const irep_idt &mode)
5899
{
@@ -67,10 +108,11 @@ void code_contractst::check_apply_loop_contracts(
67108
loop_end = t;
68109

69110
// see whether we have an invariant and a decreases clause
70-
exprt invariant = static_cast<const exprt &>(
111+
auto invariant = static_cast<const exprt &>(
71112
loop_end->get_condition().find(ID_C_spec_loop_invariant));
72-
exprt decreases_clause = static_cast<const exprt &>(
113+
auto decreases_clause = static_cast<const exprt &>(
73114
loop_end->get_condition().find(ID_C_spec_decreases));
115+
74116
if(invariant.is_nil())
75117
{
76118
if(decreases_clause.is_nil())
@@ -92,12 +134,12 @@ void code_contractst::check_apply_loop_contracts(
92134
}
93135

94136
// Vector representing a (possibly multidimensional) decreases clause
95-
const auto decreases_clause_vector = decreases_clause.operands();
137+
const auto &decreases_clause_exprs = decreases_clause.operands();
96138

97139
// Temporary variables for storing the multidimensional decreases clause
98140
// at the start of and end of a loop body
99-
std::vector<symbol_exprt> old_temporary_variables;
100-
std::vector<symbol_exprt> new_temporary_variables;
141+
std::vector<symbol_exprt> old_decreases_vars;
142+
std::vector<symbol_exprt> new_decreases_vars;
101143

102144
// change
103145
// H: loop;
@@ -106,8 +148,8 @@ void code_contractst::check_apply_loop_contracts(
106148
// H: assert(invariant);
107149
// havoc;
108150
// assume(invariant);
109-
// old_decreases_value = decreases_clause(current_environment);
110151
// if(guard) goto E:
152+
// old_decreases_value = decreases_clause(current_environment);
111153
// loop;
112154
// new_decreases_value = decreases_clause(current_environment);
113155
// assert(invariant);
@@ -159,34 +201,19 @@ void code_contractst::check_apply_loop_contracts(
159201
// decreases clause's value before and after the loop
160202
for(const auto &clause : decreases_clause.operands())
161203
{
162-
old_temporary_variables.push_back(
204+
const auto old_decreases_var =
163205
new_tmp_symbol(clause.type(), loop_head->source_location, mode)
164-
.symbol_expr());
165-
new_temporary_variables.push_back(
166-
new_tmp_symbol(clause.type(), loop_head->source_location, mode)
167-
.symbol_expr());
168-
}
169-
170-
if(!decreases_clause.is_nil())
171-
{
172-
// Generate: declarations of the temporary variables that stores the
173-
// multidimensional decreases clause's value before the loop
174-
for(const auto &old_temp_var : old_temporary_variables)
175-
{
176-
havoc_code.add(
177-
goto_programt::make_decl(old_temp_var, loop_head->source_location));
178-
}
206+
.symbol_expr();
207+
havoc_code.add(
208+
goto_programt::make_decl(old_decreases_var, loop_head->source_location));
209+
old_decreases_vars.push_back(old_decreases_var);
179210

180-
// Generate: assignments to store the multidimensional decreases clause's
181-
// value before the loop
182-
for(size_t i = 0; i < old_temporary_variables.size(); i++)
183-
{
184-
code_assignt old_decreases_assignment{old_temporary_variables[i],
185-
decreases_clause_vector[i]};
186-
old_decreases_assignment.add_source_location() =
187-
loop_head->source_location;
188-
converter.goto_convert(old_decreases_assignment, havoc_code, mode);
189-
}
211+
const auto new_decreases_var =
212+
new_tmp_symbol(clause.type(), loop_head->source_location, mode)
213+
.symbol_expr();
214+
havoc_code.add(
215+
goto_programt::make_decl(new_decreases_var, loop_head->source_location));
216+
new_decreases_vars.push_back(new_decreases_var);
190217
}
191218

192219
// non-deterministically skip the loop if it is a do-while loop
@@ -199,7 +226,23 @@ void code_contractst::check_apply_loop_contracts(
199226

200227
// Now havoc at the loop head.
201228
// Use insert_before_swap to preserve jumps to loop head.
202-
goto_function.body.insert_before_swap(loop_head, havoc_code);
229+
insert_before_swap_and_advance(goto_function.body, loop_head, havoc_code);
230+
231+
// Generate: assignments to store the multidimensional decreases clause's
232+
// value before the loop
233+
if(!decreases_clause.is_nil())
234+
{
235+
for(size_t i = 0; i < old_decreases_vars.size(); i++)
236+
{
237+
code_assignt old_decreases_assignment{old_decreases_vars[i],
238+
decreases_clause_exprs[i]};
239+
old_decreases_assignment.add_source_location() =
240+
loop_head->source_location;
241+
converter.goto_convert(old_decreases_assignment, havoc_code, mode);
242+
}
243+
244+
goto_function.body.destructive_insert(std::next(loop_head), havoc_code);
245+
}
203246

204247
// Generate: assert(invariant) just after the loop exits
205248
// We use a block scope to create a temporary assertion,
@@ -212,23 +255,14 @@ void code_contractst::check_apply_loop_contracts(
212255
"Check that loop invariant is preserved");
213256
}
214257

258+
// Generate: assignments to store the multidimensional decreases clause's
259+
// value after one iteration of the loop
215260
if(!decreases_clause.is_nil())
216261
{
217-
// Generate: declarations of temporary variables that stores the
218-
// multidimensional decreases clause's value after one arbitrary iteration
219-
// of the loop
220-
for(const auto &new_temp_var : new_temporary_variables)
262+
for(size_t i = 0; i < new_decreases_vars.size(); i++)
221263
{
222-
havoc_code.add(
223-
goto_programt::make_decl(new_temp_var, loop_head->source_location));
224-
}
225-
226-
// Generate: assignments to store the multidimensional decreases clause's
227-
// value after one iteration of the loop
228-
for(size_t i = 0; i < new_temporary_variables.size(); i++)
229-
{
230-
code_assignt new_decreases_assignment{new_temporary_variables[i],
231-
decreases_clause_vector[i]};
264+
code_assignt new_decreases_assignment{new_decreases_vars[i],
265+
decreases_clause_exprs[i]};
232266
new_decreases_assignment.add_source_location() =
233267
loop_head->source_location;
234268
converter.goto_convert(new_decreases_assignment, havoc_code, mode);
@@ -237,27 +271,25 @@ void code_contractst::check_apply_loop_contracts(
237271
// Generate: assertion that the multidimensional decreases clause's value
238272
// after the loop is smaller than the value before the loop.
239273
// Here, we use the lexicographic order.
240-
code_assertt monotonic_decreasing_assertion{create_lexicographic_less_than(
241-
new_temporary_variables, old_temporary_variables)};
274+
code_assertt monotonic_decreasing_assertion{
275+
create_lexicographic_less_than(new_decreases_vars, old_decreases_vars)};
242276
monotonic_decreasing_assertion.add_source_location() =
243277
loop_head->source_location;
244278
converter.goto_convert(monotonic_decreasing_assertion, havoc_code, mode);
245279
havoc_code.instructions.back().source_location.set_comment(
246280
"Check decreases clause on loop iteration");
247281

248282
// Discard the temporary variables that store decreases clause's value
249-
for(size_t i = 0; i < old_temporary_variables.size(); i++)
283+
for(size_t i = 0; i < old_decreases_vars.size(); i++)
250284
{
251285
havoc_code.add(goto_programt::make_dead(
252-
old_temporary_variables[i], loop_head->source_location));
286+
old_decreases_vars[i], loop_head->source_location));
253287
havoc_code.add(goto_programt::make_dead(
254-
new_temporary_variables[i], loop_head->source_location));
288+
new_decreases_vars[i], loop_head->source_location));
255289
}
256290
}
257291

258-
auto offset = havoc_code.instructions.size();
259-
goto_function.body.insert_before_swap(loop_end, havoc_code);
260-
std::advance(loop_end, offset);
292+
insert_before_swap_and_advance(goto_function.body, loop_end, havoc_code);
261293

262294
// change the back edge into assume(false) or assume(guard)
263295
loop_end->targets.clear();
@@ -268,52 +300,6 @@ void code_contractst::check_apply_loop_contracts(
268300
loop_end->set_condition(boolean_negate(loop_end->get_condition()));
269301
}
270302

271-
// Create a lexicographic less-than relation between two tuples of variables.
272-
// This is used in the implementation of multidimensional decreases clauses.
273-
static exprt create_lexicographic_less_than(
274-
const std::vector<symbol_exprt> &lhs,
275-
const std::vector<symbol_exprt> &rhs)
276-
{
277-
PRECONDITION(lhs.size() == rhs.size());
278-
279-
if(lhs.empty())
280-
{
281-
return false_exprt();
282-
}
283-
284-
// Store conjunctions of equalities.
285-
// For example, suppose that the two input vectors are <s1, s2, s3> and <l1,
286-
// l2, l3>.
287-
// Then this vector stores <s1 == l1, s1 == l1 && s2 == l2,
288-
// s1 == l1 && s2 == l2 && s3 == l3>.
289-
// In fact, the last element is unnecessary, so we do not create it.
290-
exprt::operandst equality_conjunctions(lhs.size());
291-
equality_conjunctions[0] = binary_relation_exprt(lhs[0], ID_equal, rhs[0]);
292-
for(unsigned int i = 1; i < equality_conjunctions.size() - 1; i++)
293-
{
294-
binary_relation_exprt component_i_equality{lhs[i], ID_equal, rhs[i]};
295-
equality_conjunctions[i] =
296-
and_exprt(equality_conjunctions[i - 1], component_i_equality);
297-
}
298-
299-
// Store inequalities between the i-th components of the input vectors
300-
// (i.e. lhs and rhs).
301-
// For example, suppose that the two input vectors are <s1, s2, s3> and <l1,
302-
// l2, l3>.
303-
// Then this vector stores <s1 < l1, s1 == l1 && s2 < l2, s1 == l1 &&
304-
// s2 == l2 && s3 < l3>.
305-
exprt::operandst lexicographic_individual_comparisons(lhs.size());
306-
lexicographic_individual_comparisons[0] =
307-
binary_relation_exprt(lhs[0], ID_lt, rhs[0]);
308-
for(unsigned int i = 1; i < lexicographic_individual_comparisons.size(); i++)
309-
{
310-
binary_relation_exprt component_i_less_than{lhs[i], ID_lt, rhs[i]};
311-
lexicographic_individual_comparisons[i] =
312-
and_exprt(equality_conjunctions[i - 1], component_i_less_than);
313-
}
314-
return disjunction(lexicographic_individual_comparisons);
315-
}
316-
317303
bool code_contractst::has_contract(const irep_idt fun_name)
318304
{
319305
const symbolt &function_symbol = ns.lookup(fun_name);

src/goto-instrument/contracts/contracts.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ class code_contractst
123123
void check_apply_loop_contracts(
124124
goto_functionst::goto_functiont &goto_function,
125125
const local_may_aliast &local_may_alias,
126-
const goto_programt::targett loop_head,
126+
goto_programt::targett loop_head,
127127
const loopt &loop,
128128
const irep_idt &mode);
129129

0 commit comments

Comments
 (0)