Skip to content

Commit 8965d02

Browse files
SaswatPadhiArenBabikian
authored andcommitted
Fixed side-effect checking for invariant contracts
Moved side-effect checking on invariant contracts to goto-instrument. Since invariant contracts are expanded to multiple assertions (and assumptions) around the loop, it is cleaner to expand them only when they are enforced.
1 parent d79e5f0 commit 8965d02

File tree

5 files changed

+78
-30
lines changed

5 files changed

+78
-30
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#include <assert.h>
2+
3+
int main()
4+
{
5+
unsigned N, *a = malloc(sizeof(unsigned int));
6+
7+
*a = 0;
8+
while(*a < N)
9+
__CPROVER_loop_invariant((0 <= *a) && (*a <= N))
10+
{
11+
++(*a);
12+
}
13+
14+
assert(*a == N);
15+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE
2+
main.c
3+
--enforce-all-contracts
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^\[main.1\] .* Check loop invariant before entry: SUCCESS$
7+
^\[main.2\] .* Check that loop invariant is preserved: SUCCESS$
8+
^\[main.assertion.1\] .* assertion \*a == N: SUCCESS$
9+
^VERIFICATION SUCCESSFUL$
10+
--
11+
This test checks that C expressions are correctly converted to logic
12+
when enforcing loop invariant annotations.

src/goto-instrument/code_contracts.cpp

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Date: February 2016
1717

1818
#include <analyses/local_may_alias.h>
1919

20+
#include <goto-programs/goto_convert_class.h>
2021
#include <goto-programs/remove_skip.h>
2122

2223
#include <linking/static_lifetime_init.h>
@@ -75,9 +76,11 @@ exprt get_size(const typet &type, const namespacet &ns, messaget &log)
7576

7677
static void check_apply_invariants(
7778
goto_functionst::goto_functiont &goto_function,
79+
goto_convertt &converter,
7880
const local_may_aliast &local_may_alias,
7981
const goto_programt::targett loop_head,
80-
const loopt &loop)
82+
const loopt &loop,
83+
const irep_idt &mode)
8184
{
8285
PRECONDITION(!loop.empty());
8386

@@ -95,16 +98,18 @@ static void check_apply_invariants(
9598
if(invariant.is_nil())
9699
return;
97100

98-
// change H: loop; E: ...
101+
// change
102+
// H: loop;
103+
// E: ...
99104
// to
100-
// H: assert(invariant);
101-
// havoc;
102-
// assume(invariant);
103-
// if(guard) goto E:
104-
// loop;
105-
// assert(invariant);
106-
// assume(false);
107-
// E: ...
105+
// H: assert(invariant);
106+
// havoc;
107+
// assume(invariant);
108+
// if(guard) goto E:
109+
// loop;
110+
// assert(invariant);
111+
// assume(false);
112+
// E: ...
108113

109114
// find out what can get changed in the loop
110115
modifiest modifies;
@@ -115,17 +120,20 @@ static void check_apply_invariants(
115120

116121
// assert the invariant
117122
{
118-
goto_programt::targett a = havoc_code.add(
119-
goto_programt::make_assertion(invariant, loop_head->source_location));
120-
a->source_location.set_comment("Check loop invariant before entry");
123+
auto assertion = code_assertt(invariant);
124+
assertion.add_source_location().swap(loop_head->source_location);
125+
converter.goto_convert(assertion, havoc_code, mode);
126+
havoc_code.instructions.back().source_location.set_comment(
127+
"Check loop invariant before entry");
121128
}
122129

123130
// havoc variables being written to
124131
build_havoc_code(loop_head, modifies, havoc_code);
125132

126133
// assume the invariant
127-
havoc_code.add(
128-
goto_programt::make_assumption(invariant, loop_head->source_location));
134+
auto assumption = code_assumet(invariant);
135+
assumption.add_source_location().swap(loop_head->source_location);
136+
converter.goto_convert(assumption, havoc_code, mode);
129137

130138
// non-deterministically skip the loop if it is a do-while loop
131139
if(!loop_head->is_goto())
@@ -141,11 +149,14 @@ static void check_apply_invariants(
141149

142150
// assert the invariant at the end of the loop body
143151
{
144-
goto_programt::instructiont a =
145-
goto_programt::make_assertion(invariant, loop_end->source_location);
146-
a.source_location.set_comment("Check that loop invariant is preserved");
147-
goto_function.body.insert_before_swap(loop_end, a);
148-
++loop_end;
152+
auto assertion = code_assertt(invariant);
153+
assertion.add_source_location().swap(loop_end->source_location);
154+
converter.goto_convert(assertion, havoc_code, mode);
155+
havoc_code.instructions.back().source_location.set_comment(
156+
"Check that loop invariant is preserved");
157+
auto offset = havoc_code.instructions.size();
158+
goto_function.body.insert_before_swap(loop_end, havoc_code);
159+
std::advance(loop_end, offset);
149160
}
150161

151162
// change the back edge into assume(false) or assume(guard)
@@ -366,15 +377,24 @@ bool code_contractst::apply_function_contract(
366377
}
367378

368379
void code_contractst::apply_loop_contract(
380+
const irep_idt &function_name,
369381
goto_functionst::goto_functiont &goto_function)
370382
{
371383
local_may_aliast local_may_alias(goto_function);
372384
natural_loops_mutablet natural_loops(goto_function.body);
373385

386+
// setup goto_convertt to convert C expressions to logic
387+
goto_convertt converter(symbol_table, log.get_message_handler());
388+
374389
// iterate over the (natural) loops in the function
375390
for(const auto &loop : natural_loops.loop_map)
376391
check_apply_invariants(
377-
goto_function, local_may_alias, loop.first, loop.second);
392+
goto_function,
393+
converter,
394+
local_may_alias,
395+
loop.first,
396+
loop.second,
397+
symbol_table.lookup_ref(function_name).mode);
378398
}
379399

380400
const symbolt &code_contractst::new_tmp_symbol(
@@ -971,7 +991,7 @@ bool code_contractst::enforce_contracts()
971991
if(has_contract(goto_function.first))
972992
funs_to_enforce.insert(id2string(goto_function.first));
973993
else
974-
apply_loop_contract(goto_function.second);
994+
apply_loop_contract(goto_function.first, goto_function.second);
975995
}
976996
return enforce_contracts(funs_to_enforce);
977997
}
@@ -991,7 +1011,7 @@ bool code_contractst::enforce_contracts(
9911011
<< messaget::eom;
9921012
continue;
9931013
}
994-
apply_loop_contract(goto_function->second);
1014+
apply_loop_contract(goto_function->first, goto_function->second);
9951015

9961016
if(!has_contract(fun))
9971017
{

src/goto-instrument/code_contracts.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,9 @@ class code_contractst
155155
std::vector<exprt> &aliasable_references);
156156

157157
void apply_loop_contract(goto_functionst::goto_functiont &goto_function);
158+
void apply_loop_contract(
159+
const irep_idt &function_name,
160+
goto_functionst::goto_functiont &goto_function);
158161

159162
/// \brief Does the named function have a contract?
160163
bool has_contract(const irep_idt);

src/goto-programs/goto_convert.cpp

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -868,13 +868,11 @@ void goto_convertt::convert_loop_invariant(
868868
if(invariant.is_nil())
869869
return;
870870

871-
goto_programt no_sideeffects;
872-
clean_expr(invariant, no_sideeffects, mode);
873-
874-
INVARIANT_WITH_DIAGNOSTICS(
875-
no_sideeffects.instructions.empty(),
876-
"loop invariant is not side-effect free",
877-
code.find_source_location());
871+
// Loop contracts are expanded to assertions (and assumptions)
872+
// at multiple code points around a loop.
873+
// Rather than performing `clean_expr` during compilation,
874+
// we convert the invariant to logic at these various code points
875+
// only when they are enforced (using goto-instrument).
878876

879877
PRECONDITION(loop->is_goto());
880878
loop->guard.add(ID_C_spec_loop_invariant).swap(invariant);

0 commit comments

Comments
 (0)