Skip to content

Commit 0ea7f13

Browse files
authored
Merge pull request #5942 from padhi-aws-forks/side_effects_fix
Invariant predicates were being cleaned (C -> logic via `clean_expr`) within `convert_loop_invariant` and were rejected 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`.
2 parents 972a993 + 1debdc7 commit 0ea7f13

File tree

6 files changed

+97
-61
lines changed

6 files changed

+97
-61
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: 46 additions & 43 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)
@@ -158,15 +164,6 @@ static void check_apply_invariants(
158164
loop_end->set_condition(boolean_negate(loop_end->get_condition()));
159165
}
160166

161-
void code_contractst::convert_to_goto(
162-
const codet &code,
163-
const irep_idt &mode,
164-
goto_programt &program)
165-
{
166-
goto_convertt converter(symbol_table, log.get_message_handler());
167-
converter.goto_convert(code, program, mode);
168-
}
169-
170167
bool code_contractst::has_contract(const irep_idt fun_name)
171168
{
172169
const symbolt &function_symbol = ns.lookup(fun_name);
@@ -322,7 +319,7 @@ code_contractst::create_ensures_instruction(
322319

323320
// Create instructions corresponding to the ensures clause
324321
goto_programt ensures_program;
325-
convert_to_goto(expression, mode, ensures_program);
322+
converter.goto_convert(expression, ensures_program, mode);
326323

327324
// return a pair containing:
328325
// 1. instructions corresponding to the ensures clause
@@ -424,10 +421,10 @@ bool code_contractst::apply_function_contract(
424421
if(requires.is_not_nil())
425422
{
426423
goto_programt assertion;
427-
convert_to_goto(
424+
converter.goto_convert(
428425
code_assertt(requires),
429-
symbol_table.lookup_ref(function).mode,
430-
assertion);
426+
assertion,
427+
symbol_table.lookup_ref(function).mode);
431428
auto lines_to_iterate = assertion.instructions.size();
432429
goto_program.insert_before_swap(target, assertion);
433430
std::advance(target, lines_to_iterate);
@@ -483,15 +480,21 @@ bool code_contractst::apply_function_contract(
483480
}
484481

485482
void code_contractst::apply_loop_contract(
483+
const irep_idt &function_name,
486484
goto_functionst::goto_functiont &goto_function)
487485
{
488486
local_may_aliast local_may_alias(goto_function);
489487
natural_loops_mutablet natural_loops(goto_function.body);
490488

491-
// 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.
492491
for(const auto &loop : natural_loops.loop_map)
493492
check_apply_invariants(
494-
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);
495498
}
496499

497500
const symbolt &code_contractst::new_tmp_symbol(
@@ -966,8 +969,8 @@ void code_contractst::add_contract_check(
966969
replace(requires_cond);
967970

968971
goto_programt assumption;
969-
convert_to_goto(
970-
code_assumet(requires_cond), function_symbol.mode, assumption);
972+
converter.goto_convert(
973+
code_assumet(requires_cond), assumption, function_symbol.mode);
971974
check.destructive_append(assumption);
972975
}
973976

@@ -1078,7 +1081,7 @@ bool code_contractst::enforce_contracts()
10781081
if(has_contract(goto_function.first))
10791082
funs_to_enforce.insert(id2string(goto_function.first));
10801083
else
1081-
apply_loop_contract(goto_function.second);
1084+
apply_loop_contract(goto_function.first, goto_function.second);
10821085
}
10831086
return enforce_contracts(funs_to_enforce);
10841087
}
@@ -1098,7 +1101,7 @@ bool code_contractst::enforce_contracts(
10981101
<< messaget::eom;
10991102
continue;
11001103
}
1101-
apply_loop_contract(goto_function->second);
1104+
apply_loop_contract(goto_function->first, goto_function->second);
11021105

11031106
if(!has_contract(fun))
11041107
{

src/goto-instrument/code_contracts.h

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,18 @@ Date: February 2016
1919
#include <string>
2020
#include <unordered_set>
2121

22+
#include <goto-programs/goto_convert_class.h>
2223
#include <goto-programs/goto_functions.h>
2324
#include <goto-programs/goto_model.h>
2425

2526
#include <util/message.h>
2627
#include <util/namespace.h>
2728
#include <util/pointer_expr.h>
2829

30+
#include "loop_utils.h"
31+
2932
class assigns_clauset;
33+
class local_may_aliast;
3034
class replace_symbolt;
3135

3236
class code_contractst
@@ -36,8 +40,9 @@ class code_contractst
3640
: ns(goto_model.symbol_table),
3741
symbol_table(goto_model.symbol_table),
3842
goto_functions(goto_model.goto_functions),
39-
temporary_counter(0),
40-
log(log)
43+
log(log),
44+
converter(symbol_table, log.get_message_handler())
45+
4146
{
4247
}
4348

@@ -89,26 +94,27 @@ class code_contractst
8994
const irep_idt &function_id,
9095
const irep_idt &mode);
9196

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+
92104
namespacet ns;
93105

94106
protected:
95107
symbol_tablet &symbol_table;
96108
goto_functionst &goto_functions;
97109

98-
unsigned temporary_counter;
99110
messaget &log;
111+
goto_convertt converter;
100112

101113
std::unordered_set<irep_idt> summarized;
102114

103115
/// \brief Enforce contract of a single function
104116
bool enforce_contract(const std::string &);
105117

106-
/// \brief Create goto instructions based on code and add them to program.
107-
void convert_to_goto(
108-
const codet &code,
109-
const irep_idt &mode,
110-
goto_programt &program);
111-
112118
/// Insert assertion statements into the goto program to ensure that
113119
/// assigned memory is within the assignable memory frame.
114120
bool add_pointer_checks(const std::string &);
@@ -159,7 +165,9 @@ class code_contractst
159165
const exprt &lhs,
160166
std::vector<exprt> &aliasable_references);
161167

162-
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);
163171

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