Skip to content

Commit 7e1f9c1

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 e794caf commit 7e1f9c1

File tree

6 files changed

+106
-37
lines changed

6 files changed

+106
-37
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#include <assert.h>
2+
#include <stdlib.h>
3+
4+
int main()
5+
{
6+
unsigned N, *a = malloc(sizeof(unsigned int));
7+
8+
*a = 0;
9+
while(*a < N)
10+
__CPROVER_loop_invariant((0 <= *a) && (*a <= N))
11+
{
12+
++(*a);
13+
}
14+
15+
assert(*a == N);
16+
}
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: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ Date: February 2016
1515

1616
#include <algorithm>
1717

18-
#include <analyses/local_may_alias.h>
19-
18+
#include <goto-programs/goto_program.h>
2019
#include <goto-programs/remove_skip.h>
2120

2221
#include <util/arith_tools.h>
@@ -30,8 +29,6 @@ Date: February 2016
3029
#include <util/pointer_predicates.h>
3130
#include <util/replace_symbol.h>
3231

33-
#include "loop_utils.h"
34-
3532
/// Predicate to be used with the exprt::visit() function. The function
3633
/// found_return_value() will return `true` iff this predicate is called on an
3734
/// expr that contains `__CPROVER_return_value`.
@@ -70,11 +67,12 @@ exprt get_size(const typet &type, const namespacet &ns, messaget &log)
7067
return result;
7168
}
7269

73-
static void check_apply_invariants(
70+
void code_contractst::check_apply_invariants(
7471
goto_functionst::goto_functiont &goto_function,
7572
const local_may_aliast &local_may_alias,
7673
const goto_programt::targett loop_head,
77-
const loopt &loop)
74+
const loopt &loop,
75+
const irep_idt &mode)
7876
{
7977
PRECONDITION(!loop.empty());
8078

@@ -92,16 +90,18 @@ static void check_apply_invariants(
9290
if(invariant.is_nil())
9391
return;
9492

95-
// change H: loop; E: ...
93+
// change
94+
// H: loop;
95+
// E: ...
9696
// to
97-
// H: assert(invariant);
98-
// havoc;
99-
// assume(invariant);
100-
// if(guard) goto E:
101-
// loop;
102-
// assert(invariant);
103-
// assume(false);
104-
// E: ...
97+
// H: assert(invariant);
98+
// havoc;
99+
// assume(invariant);
100+
// if(guard) goto E:
101+
// loop;
102+
// assert(invariant);
103+
// assume(false);
104+
// E: ...
105105

106106
// find out what can get changed in the loop
107107
modifiest modifies;
@@ -112,17 +112,20 @@ static void check_apply_invariants(
112112

113113
// assert the invariant
114114
{
115-
goto_programt::targett a = havoc_code.add(
116-
goto_programt::make_assertion(invariant, loop_head->source_location));
117-
a->source_location.set_comment("Check loop invariant before entry");
115+
auto assertion = code_assertt(invariant);
116+
assertion.add_source_location().swap(loop_head->source_location);
117+
converter.goto_convert(assertion, havoc_code, mode);
118+
havoc_code.instructions.back().source_location.set_comment(
119+
"Check loop invariant before entry");
118120
}
119121

120122
// havoc variables being written to
121123
build_havoc_code(loop_head, modifies, havoc_code);
122124

123125
// assume the invariant
124-
havoc_code.add(
125-
goto_programt::make_assumption(invariant, loop_head->source_location));
126+
auto assumption = code_assumet(invariant);
127+
assumption.add_source_location().swap(loop_head->source_location);
128+
converter.goto_convert(assumption, havoc_code, mode);
126129

127130
// non-deterministically skip the loop if it is a do-while loop
128131
if(!loop_head->is_goto())
@@ -138,11 +141,14 @@ static void check_apply_invariants(
138141

139142
// assert the invariant at the end of the loop body
140143
{
141-
goto_programt::instructiont a =
142-
goto_programt::make_assertion(invariant, loop_end->source_location);
143-
a.source_location.set_comment("Check that loop invariant is preserved");
144-
goto_function.body.insert_before_swap(loop_end, a);
145-
++loop_end;
144+
auto assertion = code_assertt(invariant);
145+
assertion.add_source_location().swap(loop_end->source_location);
146+
converter.goto_convert(assertion, havoc_code, mode);
147+
havoc_code.instructions.back().source_location.set_comment(
148+
"Check that loop invariant is preserved");
149+
auto offset = havoc_code.instructions.size();
150+
goto_function.body.insert_before_swap(loop_end, havoc_code);
151+
std::advance(loop_end, offset);
146152
}
147153

148154
// change the back edge into assume(false) or assume(guard)
@@ -370,15 +376,21 @@ bool code_contractst::apply_function_contract(
370376
}
371377

372378
void code_contractst::apply_loop_contract(
379+
const irep_idt &function_name,
373380
goto_functionst::goto_functiont &goto_function)
374381
{
375382
local_may_aliast local_may_alias(goto_function);
376383
natural_loops_mutablet natural_loops(goto_function.body);
377384

378-
// iterate over the (natural) loops in the function
385+
// Iterate over the (natural) loops in the function,
386+
// and apply any invariant annotations that we find.
379387
for(const auto &loop : natural_loops.loop_map)
380388
check_apply_invariants(
381-
goto_function, local_may_alias, loop.first, loop.second);
389+
goto_function,
390+
local_may_alias,
391+
loop.first,
392+
loop.second,
393+
symbol_table.lookup_ref(function_name).mode);
382394
}
383395

384396
const symbolt &code_contractst::new_tmp_symbol(
@@ -953,7 +965,7 @@ bool code_contractst::enforce_contracts()
953965
if(has_contract(goto_function.first))
954966
funs_to_enforce.insert(id2string(goto_function.first));
955967
else
956-
apply_loop_contract(goto_function.second);
968+
apply_loop_contract(goto_function.first, goto_function.second);
957969
}
958970
return enforce_contracts(funs_to_enforce);
959971
}
@@ -973,7 +985,7 @@ bool code_contractst::enforce_contracts(
973985
<< messaget::eom;
974986
continue;
975987
}
976-
apply_loop_contract(goto_function->second);
988+
apply_loop_contract(goto_function->first, goto_function->second);
977989

978990
if(!has_contract(fun))
979991
{

src/goto-instrument/code_contracts.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ Date: February 2016
1818
#include <string>
1919
#include <unordered_set>
2020

21+
#include <analyses/local_may_alias.h>
22+
2123
#include <goto-programs/goto_convert_class.h>
2224
#include <goto-programs/goto_functions.h>
2325
#include <goto-programs/goto_model.h>
@@ -26,6 +28,8 @@ Date: February 2016
2628
#include <util/namespace.h>
2729
#include <util/pointer_expr.h>
2830

31+
#include "loop_utils.h"
32+
2933
class assigns_clauset;
3034
class replace_symbolt;
3135

@@ -90,6 +94,13 @@ class code_contractst
9094
const irep_idt &function_id,
9195
const irep_idt &mode);
9296

97+
void check_apply_invariants(
98+
goto_functionst::goto_functiont &goto_function,
99+
const local_may_aliast &local_may_alias,
100+
const goto_programt::targett loop_head,
101+
const loopt &loop,
102+
const irep_idt &mode);
103+
93104
namespacet ns;
94105

95106
protected:
@@ -154,7 +165,9 @@ class code_contractst
154165
const exprt &lhs,
155166
std::vector<exprt> &aliasable_references);
156167

157-
void apply_loop_contract(goto_functionst::goto_functiont &goto_function);
168+
void apply_loop_contract(
169+
const irep_idt &function_name,
170+
goto_functionst::goto_functiont &goto_function);
158171

159172
/// \brief Does the named function have a contract?
160173
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+
if(has_sideeffect(invariant))
872+
{
873+
throw incorrect_goto_program_exceptiont(
874+
"Loop invariant is not side-effect free.", code.find_source_location());
875+
}
878876

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

src/goto-programs/goto_convert_side_effect.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,24 @@ bool goto_convertt::has_function_call(const exprt &expr)
3535
return false;
3636
}
3737

38+
bool goto_convertt::has_sideeffect(const exprt &expr)
39+
{
40+
// TODO: In the future, we want to allow some function calls,
41+
// specifically those which have an empty ASSIGNS clause annotation.
42+
if(expr.id() == ID_side_effect)
43+
{
44+
return true;
45+
}
46+
47+
for(const auto &op : expr.operands())
48+
{
49+
if(goto_convertt::has_sideeffect(op))
50+
return true;
51+
}
52+
53+
return false;
54+
}
55+
3856
void goto_convertt::remove_assignment(
3957
side_effect_exprt &expr,
4058
goto_programt &dest,

0 commit comments

Comments
 (0)