Skip to content

Commit 1a1245e

Browse files
authored
Merge pull request #6473 from padhi-aws-forks/assigns-scope-fix
Fix handling of DEAD instructions and function call inlining
2 parents 8d314eb + abd36cf commit 1a1245e

File tree

3 files changed

+122
-21
lines changed

3 files changed

+122
-21
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#include <assert.h>
2+
3+
int j;
4+
5+
int lowerbound()
6+
{
7+
return 0;
8+
}
9+
10+
int upperbound()
11+
{
12+
return 10;
13+
}
14+
15+
void incr(int *i)
16+
{
17+
(*i)++;
18+
}
19+
20+
void body_1(int i)
21+
{
22+
j = i;
23+
}
24+
25+
void body_2(int *i)
26+
{
27+
(*i)++;
28+
(*i)--;
29+
}
30+
31+
int body_3(int *i)
32+
{
33+
(*i)++;
34+
if(*i == 4)
35+
return 1;
36+
37+
(*i)--;
38+
return 0;
39+
}
40+
41+
int main()
42+
{
43+
for(int i = lowerbound(); i < upperbound(); incr(&i))
44+
// clang-format off
45+
__CPROVER_assigns(i, j)
46+
__CPROVER_loop_invariant(0 <= i && i <= 10)
47+
__CPROVER_loop_invariant(i != 0 ==> j + 1 == i)
48+
// clang-format on
49+
{
50+
body_1(i);
51+
52+
if(body_3(&i))
53+
return 1;
54+
55+
body_2(&i);
56+
}
57+
58+
assert(j == 9);
59+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
CORE
2+
main.c
3+
--apply-loop-contracts
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^\[body_1.\d+\] .* Check that j is assignable: SUCCESS$
7+
^\[body_2.\d+\] .* Check that \*i is assignable: SUCCESS$
8+
^\[body_3.\d+\] .* Check that \*i is assignable: SUCCESS$
9+
^\[incr.\d+\] .* Check that \*i is assignable: SUCCESS$
10+
^\[main.\d+\] .* Check that i is assignable: SUCCESS$
11+
^\[main.\d+\] .* Check loop invariant before entry: SUCCESS$
12+
^\[main.\d+\] .* Check that loop invariant is preserved: SUCCESS$
13+
^\[main.assertion.\d+\] .* assertion j == 9: SUCCESS$
14+
^VERIFICATION SUCCESSFUL$
15+
--
16+
--
17+
This test checks write set inclusion checks in presence of function calls,
18+
which are inlined, and also checks that DEAD instructions introduced during
19+
inlining is correctly handled.

src/goto-instrument/contracts/contracts.cpp

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ void code_contractst::check_apply_loop_contracts(
189189
insert_before_swap_and_advance(
190190
goto_function.body, loop_head, snapshot_instructions);
191191
};
192+
193+
// Perform write set instrumentation on the entire loop.
194+
check_frame_conditions(
195+
function_name, goto_function.body, loop_head, loop_end, loop_assigns);
192196
}
193197

194198
havoc_assigns_targetst havoc_gen(modifies, ns);
@@ -251,29 +255,28 @@ void code_contractst::check_apply_loop_contracts(
251255

252256
// Assume invariant & decl the variant temporaries (just before loop guard).
253257
// Use insert_before_swap to preserve jumps to loop head.
254-
insert_before_swap_and_advance(goto_function.body, loop_head, generated_code);
258+
insert_before_swap_and_advance(
259+
goto_function.body,
260+
loop_head,
261+
add_pragma_disable_assigns_check(generated_code));
255262

256263
// Forward the loop_head iterator until the start of the body.
257264
// This is necessary because complex C loop_head conditions could be
258265
// converted to multiple GOTO instructions (e.g. temporaries are introduced).
266+
// If the loop_head location shifts to a different function,
267+
// assume that it's an inlined function and keep skipping.
259268
// FIXME: This simple approach wouldn't work when
260269
// the loop guard in the source file is split across multiple lines.
261270
const auto head_loc = loop_head->source_location();
262-
while(loop_head->source_location() == head_loc)
271+
while(loop_head->source_location() == head_loc ||
272+
loop_head->source_location().get_function() != head_loc.get_function())
263273
loop_head++;
264274

265275
// At this point, we are just past the loop head,
266276
// so at the beginning of the loop body.
267277
auto loop_body = loop_head;
268278
loop_head--;
269279

270-
// Perform write set instrumentation before adding anything else to loop body.
271-
if(assigns.is_not_nil())
272-
{
273-
check_frame_conditions(
274-
function_name, goto_function.body, loop_body, loop_end, loop_assigns);
275-
}
276-
277280
// Generate: assignments to store the multidimensional decreases clause's
278281
// value just before the loop body (but just after the loop guard)
279282
if(!decreases_clause.is_nil())
@@ -287,7 +290,8 @@ void code_contractst::check_apply_loop_contracts(
287290
converter.goto_convert(old_decreases_assignment, generated_code, mode);
288291
}
289292

290-
goto_function.body.destructive_insert(loop_body, generated_code);
293+
goto_function.body.destructive_insert(
294+
loop_body, add_pragma_disable_assigns_check(generated_code));
291295
}
292296

293297
// Generate: assert(invariant) just after the loop exits
@@ -337,7 +341,10 @@ void code_contractst::check_apply_loop_contracts(
337341
}
338342
}
339343

340-
insert_before_swap_and_advance(goto_function.body, loop_end, generated_code);
344+
insert_before_swap_and_advance(
345+
goto_function.body,
346+
loop_end,
347+
add_pragma_disable_assigns_check(generated_code));
341348

342349
// change the back edge into assume(false) or assume(guard)
343350
loop_end->turn_into_assume();
@@ -678,6 +685,12 @@ void code_contractst::apply_loop_contract(
678685
local_may_aliast local_may_alias(goto_function);
679686
natural_loops_mutablet natural_loops(goto_function.body);
680687

688+
if(!natural_loops.loop_map.size())
689+
return;
690+
691+
goto_function_inline(
692+
goto_functions, function_name, ns, log.get_message_handler());
693+
681694
// A graph node type that stores information about a loop.
682695
// We create a DAG representing nesting of various loops in goto_function,
683696
// sort them topologically, and instrument them in the top-sorted order.
@@ -992,7 +1005,11 @@ bool code_contractst::check_frame_conditions_function(const irep_idt &function)
9921005
function_obj->second.body, instruction_it, snapshot_instructions);
9931006
};
9941007

995-
// Insert aliasing assertions
1008+
// Inline all function calls.
1009+
goto_function_inline(
1010+
goto_functions, function_obj->first, ns, log.get_message_handler());
1011+
1012+
// Insert write set inclusion checks.
9961013
check_frame_conditions(
9971014
function_obj->first,
9981015
function_obj->second.body,
@@ -1010,8 +1027,6 @@ void code_contractst::check_frame_conditions(
10101027
const goto_programt::targett &instruction_end,
10111028
assigns_clauset &assigns)
10121029
{
1013-
goto_function_inline(goto_functions, function, ns, log.get_message_handler());
1014-
10151030
for(; instruction_it != instruction_end; ++instruction_it)
10161031
{
10171032
const auto &pragmas = instruction_it->source_location().get_pragmas();
@@ -1054,20 +1069,28 @@ void code_contractst::check_frame_conditions(
10541069
assigns_clauset::conditional_address_ranget{assigns, symbol});
10551070
if(symbol_car != assigns.get_write_set().end())
10561071
{
1057-
instruction_it++;
10581072
auto invalidation_assignment = goto_programt::make_assignment(
10591073
symbol_car->validity_condition_var,
10601074
false_exprt{},
10611075
instruction_it->source_location());
1062-
// note that instruction_it is not advanced by this call,
1063-
// so no need to move it backwards
1064-
body.insert_before_swap(instruction_it, invalidation_assignment);
1076+
// note that the CAR must be invalidated _after_ the DEAD instruction
1077+
body.insert_after(
1078+
instruction_it,
1079+
add_pragma_disable_assigns_check(invalidation_assignment));
10651080
}
10661081
else
10671082
{
1068-
throw incorrect_goto_program_exceptiont(
1069-
"Found a `DEAD` variable without corresponding `DECL`!",
1070-
instruction_it->source_location());
1083+
// For loops, the loop_head appears after the DECL of counters,
1084+
// and any other temporaries introduced during "initialization".
1085+
// However, they go `DEAD` (possible conditionally) inside the loop,
1086+
// in presence of return statements.
1087+
// Notice that for them those variables be writable,
1088+
// they must appear as assigns targets anyway,
1089+
// but their DECL statements are outside of the loop.
1090+
log.warning() << "Found a `DEAD` variable "
1091+
<< name2string(symbol.get_identifier())
1092+
<< " without corresponding `DECL`, at: "
1093+
<< instruction_it->source_location() << messaget::eom;
10711094
}
10721095
}
10731096
else if(

0 commit comments

Comments
 (0)