Skip to content

Commit 2d416e1

Browse files
committed
Check side effect of loop contracts during instrumentation
1 parent 66ae03f commit 2d416e1

File tree

16 files changed

+193
-65
lines changed

16 files changed

+193
-65
lines changed

doc/man/goto-instrument.1

+7-1
Original file line numberDiff line numberDiff line change
@@ -1015,7 +1015,13 @@ force aggressive slicer to preserve all direct paths
10151015
.SS "Code contracts:"
10161016
.TP
10171017
\fB\-\-apply\-loop\-contracts\fR
1018-
check and use loop contracts when provided
1018+
.TP
1019+
\fB\-disable\-loop\-contracts\-side\-effect\-check\fR
1020+
UNSOUND OPTION. Disable checking the absence of side effects in loop
1021+
contract clauses. In absence of such checking, loop contracts clauses will
1022+
accept more expressions, such as pure functions and statement expressions.
1023+
But user have to make sure the loop contracts are side-effect free by them self
1024+
to get a sound result.
10191025
.TP
10201026
\fB\-loop\-contracts\-no\-unwind\fR
10211027
do not unwind transformed loops
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
int main()
2+
{
3+
unsigned i;
4+
5+
while(i > 1)
6+
__CPROVER_loop_invariant(({
7+
unsigned b = i >= 1;
8+
goto label;
9+
b = i < 1;
10+
label:
11+
b;
12+
}))
13+
{
14+
i--;
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
main.c
3+
--dfcc main --apply-loop-contracts --disable-loop-contracts-side-effect-check
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
This test demonstrates a case where the loop invariant
10+
is a side-effect free statement expression.

regression/contracts-dfcc/variant_function_call_fail/test.desc

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
CORE
22
main.c
33
--dfcc main --apply-loop-contracts
4-
^Decreases clause is not side-effect free. \(at: file main.c line .* function main\)$
5-
^EXIT=70$
4+
^Decreases clause is not side-effect free.$
5+
^EXIT=6$
66
^SIGNAL=0$
77
--
88
--

regression/contracts-dfcc/variant_side_effects_fail/test.desc

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
CORE
22
main.c
33
--dfcc main --apply-loop-contracts
4-
^Decreases clause is not side-effect free. \(at: file main.c line .* function main\)$
5-
^EXIT=70$
4+
^Decreases clause is not side-effect free.$
5+
^EXIT=6$
66
^SIGNAL=0$
77
--
88
--
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
int main()
2+
{
3+
unsigned i;
4+
5+
while(i > 1)
6+
__CPROVER_loop_invariant(({
7+
unsigned b = i >= 1;
8+
goto label;
9+
b = i < 1;
10+
label:
11+
b;
12+
}))
13+
{
14+
i--;
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
main.c
3+
--apply-loop-contracts --disable-loop-contracts-side-effect-check
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
This test demonstrates a case where the loop invariant
10+
is a side-effect free statement expression.

src/ansi-c/goto-conversion/goto_convert.cpp

-13
Original file line numberDiff line numberDiff line change
@@ -1036,12 +1036,6 @@ void goto_convertt::convert_loop_contracts(
10361036
static_cast<const exprt &>(code.find(ID_C_spec_loop_invariant));
10371037
if(!invariant.is_nil())
10381038
{
1039-
if(has_subexpr(invariant, ID_side_effect))
1040-
{
1041-
throw incorrect_goto_program_exceptiont(
1042-
"Loop invariant is not side-effect free.", code.find_source_location());
1043-
}
1044-
10451039
PRECONDITION(loop->is_goto() || loop->is_incomplete_goto());
10461040
loop->condition_nonconst().add(ID_C_spec_loop_invariant).swap(invariant);
10471041
}
@@ -1050,13 +1044,6 @@ void goto_convertt::convert_loop_contracts(
10501044
static_cast<const exprt &>(code.find(ID_C_spec_decreases));
10511045
if(!decreases_clause.is_nil())
10521046
{
1053-
if(has_subexpr(decreases_clause, ID_side_effect))
1054-
{
1055-
throw incorrect_goto_program_exceptiont(
1056-
"Decreases clause is not side-effect free.",
1057-
code.find_source_location());
1058-
}
1059-
10601047
PRECONDITION(loop->is_goto() || loop->is_incomplete_goto());
10611048
loop->condition_nonconst().add(ID_C_spec_decreases).swap(decreases_clause);
10621049
}

src/cprover/instrument_given_invariants.cpp

+11
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Author: Daniel Kroening, [email protected]
1111

1212
#include "instrument_given_invariants.h"
1313

14+
#include <util/expr_util.h>
15+
1416
#include <goto-programs/goto_model.h>
1517

1618
void instrument_given_invariants(goto_functionst::function_mapt::value_type &f)
@@ -25,6 +27,15 @@ void instrument_given_invariants(goto_functionst::function_mapt::value_type &f)
2527
{
2628
const auto &invariants = static_cast<const exprt &>(
2729
it->condition().find(ID_C_spec_loop_invariant));
30+
if(!invariants.is_nil())
31+
{
32+
if(has_subexpr(invariants, ID_side_effect))
33+
{
34+
throw incorrect_goto_program_exceptiont(
35+
"Loop invariant is not side-effect free.",
36+
it->condition().find_source_location());
37+
}
38+
}
2839

2940
for(const auto &invariant : invariants.operands())
3041
{

src/goto-instrument/contracts/contracts.cpp

+5-6
Original file line numberDiff line numberDiff line change
@@ -983,12 +983,11 @@ void code_contractst::apply_loop_contract(
983983
goto_function.body, loop_head, goto_programt::make_skip());
984984
loop_end->set_target(loop_head);
985985

986-
exprt assigns_clause =
987-
static_cast<const exprt &>(loop_end->condition().find(ID_C_spec_assigns));
988-
exprt invariant = static_cast<const exprt &>(
989-
loop_end->condition().find(ID_C_spec_loop_invariant));
990-
exprt decreases_clause = static_cast<const exprt &>(
991-
loop_end->condition().find(ID_C_spec_decreases));
986+
exprt assigns_clause = get_loop_assigns(loop_end);
987+
exprt invariant =
988+
get_loop_invariants(loop_end, loop_contract_config.check_side_effect);
989+
exprt decreases_clause =
990+
get_loop_decreases(loop_end, loop_contract_config.check_side_effect);
992991

993992
if(invariant.is_nil())
994993
{

src/goto-instrument/contracts/contracts.h

+5
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ Date: February 2016
3333
#define HELP_LOOP_CONTRACTS \
3434
" {y--apply-loop-contracts} \t check and use loop contracts when provided\n"
3535

36+
#define FLAG_DISABLE_SIDE_EFFECT_CHECK \
37+
"disable-loop-contracts-side-effect-check"
38+
#define HELP_DISABLE_SIDE_EFFECT_CHECK \
39+
" {y--disable-loop-contracts-side-effect-check} \t UNSOUND OPTION.\t " \
40+
" disable the check of side-effect of loop contracts\n"
3641
#define FLAG_LOOP_CONTRACTS_NO_UNWIND "loop-contracts-no-unwind"
3742
#define HELP_LOOP_CONTRACTS_NO_UNWIND \
3843
" {y--loop-contracts-no-unwind} \t do not unwind transformed loops\n"

src/goto-instrument/contracts/dynamic-frames/dfcc_cfg_info.cpp

+28-40
Original file line numberDiff line numberDiff line change
@@ -29,40 +29,15 @@ Date: March 2023
2929
#include "dfcc_root_object.h"
3030
#include "dfcc_utils.h"
3131

32-
/// Extracts the assigns clause expression from the latch condition
33-
static const exprt::operandst &
34-
get_assigns(const goto_programt::const_targett &latch_target)
35-
{
36-
return static_cast<const exprt &>(
37-
latch_target->condition().find(ID_C_spec_assigns))
38-
.operands();
39-
}
40-
41-
/// Extracts the invariant clause expression from the latch condition
42-
static const exprt::operandst &
43-
get_invariants(const goto_programt::const_targett &latch_target)
44-
{
45-
return static_cast<const exprt &>(
46-
latch_target->condition().find(ID_C_spec_loop_invariant))
47-
.operands();
48-
}
49-
50-
/// Extracts the decreases clause expression from the latch condition
51-
static const exprt::operandst &
52-
get_decreases(const goto_programt::const_targett &latch_target)
53-
{
54-
return static_cast<const exprt &>(
55-
latch_target->condition().find(ID_C_spec_decreases))
56-
.operands();
57-
}
58-
5932
/// Returns true iff some contract clause expression is attached
6033
/// to the latch condition of this loop
61-
static bool has_contract(const goto_programt::const_targett &latch_target)
34+
static bool has_contract(
35+
const goto_programt::const_targett &latch_target,
36+
const bool check_side_effect)
6237
{
63-
return !get_assigns(latch_target).empty() ||
64-
!get_invariants(latch_target).empty() ||
65-
!get_decreases(latch_target).empty();
38+
return get_loop_assigns(latch_target).is_not_nil() ||
39+
get_loop_invariants(latch_target, check_side_effect).is_not_nil() ||
40+
get_loop_decreases(latch_target, check_side_effect).is_not_nil();
6641
}
6742

6843
void dfcc_loop_infot::output(std::ostream &out) const
@@ -155,16 +130,20 @@ dfcc_loop_infot::find_latch(goto_programt &goto_program) const
155130
static std::optional<goto_programt::targett> check_has_contract_rec(
156131
const dfcc_loop_nesting_grapht &loop_nesting_graph,
157132
const std::size_t loop_idx,
158-
const bool must_have_contract)
133+
const bool must_have_contract,
134+
const bool check_side_effect)
159135
{
160136
const auto &node = loop_nesting_graph[loop_idx];
161-
if(must_have_contract && !has_contract(node.latch))
137+
if(must_have_contract && !has_contract(node.latch, check_side_effect))
162138
return node.head;
163139

164140
for(const auto pred_idx : loop_nesting_graph.get_predecessors(loop_idx))
165141
{
166142
auto result = check_has_contract_rec(
167-
loop_nesting_graph, pred_idx, has_contract(node.latch));
143+
loop_nesting_graph,
144+
pred_idx,
145+
has_contract(node.latch, check_side_effect),
146+
check_side_effect);
168147
if(result.has_value())
169148
return result;
170149
}
@@ -175,13 +154,15 @@ static std::optional<goto_programt::targett> check_has_contract_rec(
175154
/// loops nested in loops that have contracts also have contracts.
176155
/// Return the head of the first offending loop if it exists, nothing otherwise.
177156
static std::optional<goto_programt::targett> check_inner_loops_have_contracts(
178-
const dfcc_loop_nesting_grapht &loop_nesting_graph)
157+
const dfcc_loop_nesting_grapht &loop_nesting_graph,
158+
const bool check_side_effect)
179159
{
180160
for(std::size_t idx = 0; idx < loop_nesting_graph.size(); idx++)
181161
{
182162
if(loop_nesting_graph.get_successors(idx).empty())
183163
{
184-
auto result = check_has_contract_rec(loop_nesting_graph, idx, false);
164+
auto result = check_has_contract_rec(
165+
loop_nesting_graph, idx, false, check_side_effect);
185166
if(result.has_value())
186167
return result;
187168
}
@@ -349,18 +330,21 @@ static struct contract_clausest default_loop_contract_clauses(
349330
const std::size_t loop_id,
350331
const irep_idt &function_id,
351332
local_may_aliast &local_may_alias,
333+
const bool check_side_effect,
352334
message_handlert &message_handler,
353335
const namespacet &ns)
354336
{
355337
messaget log(message_handler);
356338
const auto &loop = loop_nesting_graph[loop_id];
357339

358340
// Process loop contract clauses
359-
exprt::operandst invariant_clauses = get_invariants(loop.latch);
360-
exprt::operandst assigns_clauses = get_assigns(loop.latch);
341+
exprt::operandst invariant_clauses =
342+
get_loop_invariants(loop.latch, check_side_effect).operands();
343+
exprt::operandst assigns_clauses = get_loop_assigns(loop.latch).operands();
361344

362345
// Initialise defaults
363-
struct contract_clausest result(get_decreases(loop.latch));
346+
struct contract_clausest result(
347+
get_loop_decreases(loop.latch, check_side_effect).operands());
364348

365349
// Generate defaults for all clauses if at least one type of clause is defined
366350
if(
@@ -435,6 +419,7 @@ static dfcc_loop_infot gen_dfcc_loop_info(
435419
const std::map<std::size_t, dfcc_loop_infot> &loop_info_map,
436420
dirtyt &dirty,
437421
local_may_aliast &local_may_alias,
422+
const bool check_side_effect,
438423
message_handlert &message_handler,
439424
dfcc_libraryt &library,
440425
symbol_table_baset &symbol_table)
@@ -454,6 +439,7 @@ static dfcc_loop_infot gen_dfcc_loop_info(
454439
loop_id,
455440
function_id,
456441
local_may_alias,
442+
check_side_effect,
457443
message_handler,
458444
ns);
459445

@@ -523,7 +509,8 @@ dfcc_cfg_infot::dfcc_cfg_infot(
523509
dfcc_check_loop_normal_form(goto_program, log);
524510
loop_nesting_graph = build_loop_nesting_graph(goto_program);
525511

526-
const auto head = check_inner_loops_have_contracts(loop_nesting_graph);
512+
const auto head = check_inner_loops_have_contracts(
513+
loop_nesting_graph, loop_contract_config.check_side_effect);
527514
if(head.has_value())
528515
{
529516
throw invalid_source_file_exceptiont(
@@ -571,6 +558,7 @@ dfcc_cfg_infot::dfcc_cfg_infot(
571558
loop_info_map,
572559
dirty,
573560
local_may_alias,
561+
loop_contract_config.check_side_effect,
574562
message_handler,
575563
library,
576564
symbol_table)});

src/goto-instrument/contracts/utils.cpp

+60
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,66 @@ get_loop_head(const unsigned int target_loop_number, goto_functiont &function)
682682
return get_loop_head_or_end(target_loop_number, function, true);
683683
}
684684

685+
/// Extract loop invariants from loop end without any checks.
686+
static exprt
687+
extract_loop_invariants(const goto_programt::const_targett &loop_end)
688+
{
689+
return static_cast<const exprt &>(
690+
loop_end->condition().find(ID_C_spec_loop_invariant));
691+
}
692+
693+
static exprt extract_loop_assigns(const goto_programt::const_targett &loop_end)
694+
{
695+
return static_cast<const exprt &>(
696+
loop_end->condition().find(ID_C_spec_assigns));
697+
}
698+
699+
static exprt
700+
extract_loop_decreases(const goto_programt::const_targett &loop_end)
701+
{
702+
return static_cast<const exprt &>(
703+
loop_end->condition().find(ID_C_spec_decreases));
704+
}
705+
706+
exprt get_loop_invariants(
707+
const goto_programt::const_targett &loop_end,
708+
const bool check_side_effect)
709+
{
710+
auto invariant = extract_loop_invariants(loop_end);
711+
if(!invariant.is_nil() && check_side_effect)
712+
{
713+
if(has_subexpr(invariant, ID_side_effect))
714+
{
715+
throw incorrect_goto_program_exceptiont(
716+
"Loop invariant is not side-effect free.",
717+
loop_end->condition().find_source_location());
718+
}
719+
}
720+
return invariant;
721+
}
722+
723+
exprt get_loop_assigns(const goto_programt::const_targett &loop_end)
724+
{
725+
return extract_loop_assigns(loop_end);
726+
}
727+
728+
exprt get_loop_decreases(
729+
const goto_programt::const_targett &loop_end,
730+
const bool check_side_effect)
731+
{
732+
auto decreases_clause = extract_loop_decreases(loop_end);
733+
if(!decreases_clause.is_nil() && check_side_effect)
734+
{
735+
if(has_subexpr(decreases_clause, ID_side_effect))
736+
{
737+
throw incorrect_goto_program_exceptiont(
738+
"Decreases clause is not side-effect free.",
739+
loop_end->condition().find_source_location());
740+
}
741+
}
742+
return decreases_clause;
743+
}
744+
685745
void annotate_invariants(
686746
const invariant_mapt &invariant_map,
687747
goto_modelt &goto_model)

0 commit comments

Comments
 (0)