Skip to content

Commit 3f1a56a

Browse files
committed
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 8cfcfdb commit 3f1a56a

File tree

5 files changed

+78
-31
lines changed

5 files changed

+78
-31
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
@@ -16,6 +16,7 @@ Date: February 2016
1616

1717
#include <analyses/local_may_alias.h>
1818

19+
#include <goto-programs/goto_convert_class.h>
1920
#include <goto-programs/remove_skip.h>
2021

2122
#include <linking/static_lifetime_init.h>
@@ -62,9 +63,11 @@ class return_value_visitort : public const_expr_visitort
6263

6364
static void check_apply_invariants(
6465
goto_functionst::goto_functiont &goto_function,
66+
goto_convertt &converter,
6567
const local_may_aliast &local_may_alias,
6668
const goto_programt::targett loop_head,
67-
const loopt &loop)
69+
const loopt &loop,
70+
const irep_idt &mode)
6871
{
6972
PRECONDITION(!loop.empty());
7073

@@ -82,16 +85,18 @@ static void check_apply_invariants(
8285
if(invariant.is_nil())
8386
return;
8487

85-
// change H: loop; E: ...
88+
// change
89+
// H: loop;
90+
// E: ...
8691
// to
87-
// H: assert(invariant);
88-
// havoc;
89-
// assume(invariant);
90-
// if(guard) goto E:
91-
// loop;
92-
// assert(invariant);
93-
// assume(false);
94-
// E: ...
92+
// H: assert(invariant);
93+
// havoc;
94+
// assume(invariant);
95+
// if(guard) goto E:
96+
// loop;
97+
// assert(invariant);
98+
// assume(false);
99+
// E: ...
95100

96101
// find out what can get changed in the loop
97102
modifiest modifies;
@@ -102,17 +107,20 @@ static void check_apply_invariants(
102107

103108
// assert the invariant
104109
{
105-
goto_programt::targett a = havoc_code.add(
106-
goto_programt::make_assertion(invariant, loop_head->source_location));
107-
a->source_location.set_comment("Check loop invariant before entry");
110+
auto assertion = code_assertt(invariant);
111+
assertion.add_source_location().swap(loop_head->source_location);
112+
converter.goto_convert(assertion, havoc_code, mode);
113+
havoc_code.instructions.back().source_location.set_comment(
114+
"Check loop invariant before entry");
108115
}
109116

110117
// havoc variables being written to
111118
build_havoc_code(loop_head, modifies, havoc_code);
112119

113120
// assume the invariant
114-
havoc_code.add(
115-
goto_programt::make_assumption(invariant, loop_head->source_location));
121+
auto assumption = code_assumet(invariant);
122+
assumption.add_source_location().swap(loop_head->source_location);
123+
converter.goto_convert(assumption, havoc_code, mode);
116124

117125
// non-deterministically skip the loop if it is a do-while loop
118126
if(!loop_head->is_goto())
@@ -128,11 +136,14 @@ static void check_apply_invariants(
128136

129137
// assert the invariant at the end of the loop body
130138
{
131-
goto_programt::instructiont a =
132-
goto_programt::make_assertion(invariant, loop_end->source_location);
133-
a.source_location.set_comment("Check that loop invariant is preserved");
134-
goto_function.body.insert_before_swap(loop_end, a);
135-
++loop_end;
139+
auto assertion = code_assertt(invariant);
140+
assertion.add_source_location().swap(loop_end->source_location);
141+
converter.goto_convert(assertion, havoc_code, mode);
142+
havoc_code.instructions.back().source_location.set_comment(
143+
"Check that loop invariant is preserved");
144+
auto offset = havoc_code.instructions.size();
145+
goto_function.body.insert_before_swap(loop_end, havoc_code);
146+
std::advance(loop_end, offset);
136147
}
137148

138149
// change the back edge into assume(false) or assume(guard)
@@ -293,15 +304,24 @@ bool code_contractst::apply_function_contract(
293304
}
294305

295306
void code_contractst::apply_loop_contract(
307+
const irep_idt &function_name,
296308
goto_functionst::goto_functiont &goto_function)
297309
{
298310
local_may_aliast local_may_alias(goto_function);
299311
natural_loops_mutablet natural_loops(goto_function.body);
300312

313+
// setup goto_convertt to convert C expressions to logic
314+
goto_convertt converter(symbol_table, log.get_message_handler());
315+
301316
// iterate over the (natural) loops in the function
302317
for(const auto &loop : natural_loops.loop_map)
303318
check_apply_invariants(
304-
goto_function, local_may_alias, loop.first, loop.second);
319+
goto_function,
320+
converter,
321+
local_may_alias,
322+
loop.first,
323+
loop.second,
324+
symbol_table.lookup_ref(function_name).mode);
305325
}
306326

307327
const symbolt &code_contractst::new_tmp_symbol(
@@ -926,7 +946,7 @@ bool code_contractst::enforce_contracts()
926946
if(has_contract(goto_function.first))
927947
funs_to_enforce.insert(id2string(goto_function.first));
928948
else
929-
apply_loop_contract(goto_function.second);
949+
apply_loop_contract(goto_function.first, goto_function.second);
930950
}
931951
return enforce_contracts(funs_to_enforce);
932952
}
@@ -946,7 +966,7 @@ bool code_contractst::enforce_contracts(
946966
<< messaget::eom;
947967
continue;
948968
}
949-
apply_loop_contract(goto_function->second);
969+
apply_loop_contract(goto_function->first, goto_function->second);
950970

951971
if(!has_contract(fun))
952972
{

src/goto-instrument/code_contracts.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ class code_contractst
133133
goto_programt &created_decls,
134134
std::vector<exprt> &created_references);
135135

136-
void apply_loop_contract(goto_functionst::goto_functiont &goto_function);
136+
void apply_loop_contract(
137+
const irep_idt &function_name,
138+
goto_functionst::goto_functiont &goto_function);
137139

138140
/// \brief Does the named function have a contract?
139141
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)