Skip to content

Commit 1debdc7

Browse files
committed
Fix side-effect check on loop invariants
Loop invariants were rejected during compilation if any intermediate goto instructions were introduced during the cleaning process. However, this check is overly conservative -- it ends up rejecting some complex, but pure, Boolean expressions that are compiled to multiple goto instructions. We replace this logic with a call to `has_subexpr` with `ID_side_effect`.
1 parent 9860129 commit 1debdc7

File tree

6 files changed

+86
-37
lines changed

6 files changed

+86
-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: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ Date: February 2016
2020

2121
#include <ansi-c/c_expr.h>
2222

23-
#include <goto-programs/goto_convert_class.h>
2423
#include <goto-programs/remove_skip.h>
2524

2625
#include <util/arith_tools.h>
@@ -34,8 +33,6 @@ Date: February 2016
3433
#include <util/pointer_predicates.h>
3534
#include <util/replace_symbol.h>
3635

37-
#include "loop_utils.h"
38-
3936
/// Predicate to be used with the exprt::visit() function. The function
4037
/// found_return_value() will return `true` iff this predicate is called on an
4138
/// expr that contains `__CPROVER_return_value`.
@@ -74,11 +71,12 @@ exprt get_size(const typet &type, const namespacet &ns, messaget &log)
7471
return result;
7572
}
7673

77-
static void check_apply_invariants(
74+
void code_contractst::check_apply_invariants(
7875
goto_functionst::goto_functiont &goto_function,
7976
const local_may_aliast &local_may_alias,
8077
const goto_programt::targett loop_head,
81-
const loopt &loop)
78+
const loopt &loop,
79+
const irep_idt &mode)
8280
{
8381
PRECONDITION(!loop.empty());
8482

@@ -96,16 +94,18 @@ static void check_apply_invariants(
9694
if(invariant.is_nil())
9795
return;
9896

99-
// change H: loop; E: ...
97+
// change
98+
// H: loop;
99+
// E: ...
100100
// to
101-
// H: assert(invariant);
102-
// havoc;
103-
// assume(invariant);
104-
// if(guard) goto E:
105-
// loop;
106-
// assert(invariant);
107-
// assume(false);
108-
// E: ...
101+
// H: assert(invariant);
102+
// havoc;
103+
// assume(invariant);
104+
// if(guard) goto E:
105+
// loop;
106+
// assert(invariant);
107+
// assume(false);
108+
// E: ...
109109

110110
// find out what can get changed in the loop
111111
modifiest modifies;
@@ -116,17 +116,20 @@ static void check_apply_invariants(
116116

117117
// assert the invariant
118118
{
119-
goto_programt::targett a = havoc_code.add(
120-
goto_programt::make_assertion(invariant, loop_head->source_location));
121-
a->source_location.set_comment("Check loop invariant before entry");
119+
code_assertt assertion{invariant};
120+
assertion.add_source_location() = loop_head->source_location;
121+
converter.goto_convert(assertion, havoc_code, mode);
122+
havoc_code.instructions.back().source_location.set_comment(
123+
"Check loop invariant before entry");
122124
}
123125

124126
// havoc variables being written to
125127
build_havoc_code(loop_head, modifies, havoc_code);
126128

127129
// assume the invariant
128-
havoc_code.add(
129-
goto_programt::make_assumption(invariant, loop_head->source_location));
130+
code_assumet assumption{invariant};
131+
assumption.add_source_location() = loop_head->source_location;
132+
converter.goto_convert(assumption, havoc_code, mode);
130133

131134
// non-deterministically skip the loop if it is a do-while loop
132135
if(!loop_head->is_goto())
@@ -142,11 +145,14 @@ static void check_apply_invariants(
142145

143146
// assert the invariant at the end of the loop body
144147
{
145-
goto_programt::instructiont a =
146-
goto_programt::make_assertion(invariant, loop_end->source_location);
147-
a.source_location.set_comment("Check that loop invariant is preserved");
148-
goto_function.body.insert_before_swap(loop_end, a);
149-
++loop_end;
148+
code_assertt assertion{invariant};
149+
assertion.add_source_location() = loop_end->source_location;
150+
converter.goto_convert(assertion, havoc_code, mode);
151+
havoc_code.instructions.back().source_location.set_comment(
152+
"Check that loop invariant is preserved");
153+
auto offset = havoc_code.instructions.size();
154+
goto_function.body.insert_before_swap(loop_end, havoc_code);
155+
std::advance(loop_end, offset);
150156
}
151157

152158
// change the back edge into assume(false) or assume(guard)
@@ -474,15 +480,21 @@ bool code_contractst::apply_function_contract(
474480
}
475481

476482
void code_contractst::apply_loop_contract(
483+
const irep_idt &function_name,
477484
goto_functionst::goto_functiont &goto_function)
478485
{
479486
local_may_aliast local_may_alias(goto_function);
480487
natural_loops_mutablet natural_loops(goto_function.body);
481488

482-
// iterate over the (natural) loops in the function
489+
// Iterate over the (natural) loops in the function,
490+
// and apply any invariant annotations that we find.
483491
for(const auto &loop : natural_loops.loop_map)
484492
check_apply_invariants(
485-
goto_function, local_may_alias, loop.first, loop.second);
493+
goto_function,
494+
local_may_alias,
495+
loop.first,
496+
loop.second,
497+
symbol_table.lookup_ref(function_name).mode);
486498
}
487499

488500
const symbolt &code_contractst::new_tmp_symbol(
@@ -1069,7 +1081,7 @@ bool code_contractst::enforce_contracts()
10691081
if(has_contract(goto_function.first))
10701082
funs_to_enforce.insert(id2string(goto_function.first));
10711083
else
1072-
apply_loop_contract(goto_function.second);
1084+
apply_loop_contract(goto_function.first, goto_function.second);
10731085
}
10741086
return enforce_contracts(funs_to_enforce);
10751087
}
@@ -1089,7 +1101,7 @@ bool code_contractst::enforce_contracts(
10891101
<< messaget::eom;
10901102
continue;
10911103
}
1092-
apply_loop_contract(goto_function->second);
1104+
apply_loop_contract(goto_function->first, goto_function->second);
10931105

10941106
if(!has_contract(fun))
10951107
{

src/goto-instrument/code_contracts.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ Date: February 2016
2727
#include <util/namespace.h>
2828
#include <util/pointer_expr.h>
2929

30+
#include "loop_utils.h"
31+
3032
class assigns_clauset;
33+
class local_may_aliast;
3134
class replace_symbolt;
3235

3336
class code_contractst
@@ -91,6 +94,13 @@ class code_contractst
9194
const irep_idt &function_id,
9295
const irep_idt &mode);
9396

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+
94104
namespacet ns;
95105

96106
protected:
@@ -155,7 +165,9 @@ class code_contractst
155165
const exprt &lhs,
156166
std::vector<exprt> &aliasable_references);
157167

158-
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);
159171

160172
/// \brief Does the named function have a contract?
161173
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_subexpr(invariant, ID_side_effect))
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_class.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ class goto_convertt:public messaget
9797

9898
void rewrite_boolean(exprt &dest);
9999

100-
static bool has_sideeffect(const exprt &expr);
101100
static bool has_function_call(const exprt &expr);
102101

103102
void remove_side_effect(

0 commit comments

Comments
 (0)