Skip to content

Commit ac6b8a9

Browse files
authored
Merge pull request #6808 from padhi-aws-forks/loop-holes-fix
Allow instrumentation of loops with "holes"
2 parents 9c7b03c + 7aa1044 commit ac6b8a9

File tree

10 files changed

+82
-63
lines changed

10 files changed

+82
-63
lines changed

regression/contracts/loop_assigns-04/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ The alias analysis in this case returns `unknown`,
1515
and so we must manually annotate an assigns clause on the loop.
1616

1717
Note that the annotated assigns clause in this case is an underapproximation,
18-
because `i`assigns. is also assigned in the loop and should be marked as assignable.
18+
because `i` is also assigned in the loop and should be marked as assignable.

regression/contracts/loop_assigns-05/test.desc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ main.c
77
^\[body_2.assigns.\d+\] .* Check that \*i is assignable: SUCCESS$
88
^\[body_3.assigns.\d+\] .* Check that \*i is assignable: SUCCESS$
99
^\[incr.assigns.\d+\] .* Check that \*i is assignable: SUCCESS$
10-
^\[main.assigns.\d+\] .* Check that i is assignable: SUCCESS$
1110
^\[main.\d+\] .* Check loop invariant before entry: SUCCESS$
1211
^\[main.\d+\] .* Check that loop invariant is preserved: SUCCESS$
1312
^\[main.assertion.\d+\] .* assertion j == 9: SUCCESS$

regression/contracts/loop_assigns_scoped_local_statics/test.desc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ main.c
77
^\[body_2.assigns.\d+\] .* Check that \*i is assignable: SUCCESS$
88
^\[body_3.assigns.\d+\] .* Check that \*i is assignable: SUCCESS$
99
^\[incr.assigns.\d+\] .* Check that \*i is assignable: SUCCESS$
10-
^\[main.assigns.\d+\] .* Check that i is assignable: SUCCESS$
1110
^\[main.\d+\] .* Check loop invariant before entry: SUCCESS$
1211
^\[main.\d+\] .* Check that loop invariant is preserved: SUCCESS$
1312
^\[main.assertion.\d+\] .* assertion j == 9: SUCCESS$
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#include <assert.h>
2+
#include <stdbool.h>
3+
4+
int main()
5+
{
6+
unsigned n, k, sum_to_k = 0;
7+
bool flag = false;
8+
9+
for(unsigned i = 0; i < n; ++i)
10+
// clang-format off
11+
__CPROVER_loop_invariant(0 <= i && i <= n)
12+
// clang-format on
13+
{
14+
if(i == k)
15+
{
16+
flag = true;
17+
break;
18+
}
19+
sum_to_k += i;
20+
}
21+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
CORE
2+
main.c
3+
--apply-loop-contracts
4+
^\[main\.\d+\] line \d+ Check loop invariant before entry: SUCCESS$
5+
^\[main\.\d+\] line \d+ Check that loop invariant is preserved: SUCCESS$
6+
^\[main\.\d+\] line \d+ Check that loop instrumentation was not truncated: SUCCESS$
7+
^\[main\.assigns\.\d+\] line \d+ Check that i is valid: SUCCESS$
8+
^\[main\.assigns\.\d+\] line \d+ Check that i is assignable: SUCCESS$
9+
^\[main\.assigns\.\d+\] line \d+ Check that sum_to_k is valid: SUCCESS$
10+
^\[main\.assigns\.\d+\] line \d+ Check that sum_to_k is assignable: SUCCESS$
11+
^EXIT=0$
12+
^SIGNAL=0$
13+
--
14+
^\[main\.assigns\.\d+\] line \d+ Check that flag is assignable: .*$
15+
--
16+
This test checks that we only instrument instructions contained within
17+
the computed natural loops. In a previous commit, an exception was added to
18+
ensure this and it was triggered whenever an instruction within [head,end]
19+
of a loop was not contained within the computed natural loop's "contents".
20+
In this commit, we remove the exception and instead mask out such instructions.
21+
22+
The statement
23+
flag = true;
24+
in main.c generates such an assignment instruction.
25+
We have added a negative regex to ensure that this instruction is not instrumented.

regression/contracts/loop_guard_with_side_effects/test.desc

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,8 @@ main.c
88
\[main\.\d+\] line \d+ Check loop invariant before entry: SUCCESS$
99
\[main\.\d+\] line \d+ Check that loop invariant is preserved: SUCCESS$
1010
\[main.assigns\.\d+\] line \d+ Check that i is assignable: SUCCESS$
11-
\[main.assigns\.\d+\] line \d+ Check that j is assignable: SUCCESS$
12-
\[main.assigns\.\d+\] line \d+ Check that k is assignable: SUCCESS$
1311
\[main.assigns\.\d+\] line \d+ Check that i is valid: SUCCESS$
1412
\[main.assigns\.\d+\] line \d+ Check that j is valid: SUCCESS$
15-
\[main.assigns\.\d+\] line \d+ Check that i is assignable: SUCCESS$
1613
\[main.overflow\.\d+\] line \d+ arithmetic overflow on unsigned \+ in i \+ 1u: SUCCESS
1714
\[main.assertion\.\d+\] line \d+ assertion i == k: SUCCESS$
1815
\[main.overflow\.\d+\] line \d+ arithmetic overflow on unsigned \+ in k \+ \(unsigned int\)1: SUCCESS

regression/contracts/loop_guard_with_side_effects_fail/test.desc

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@ main.c
66
\[main\.\d+\] line \d+ Check loop invariant before entry: SUCCESS$
77
\[main\.\d+\] line \d+ Check that loop invariant is preserved: SUCCESS$
88
\[main.assigns\.\d+\] line \d+ Check that i is assignable: SUCCESS$
9-
\[main.assigns\.\d+\] line \d+ Check that j is assignable: SUCCESS$
10-
\[main.assigns\.\d+\] line \d+ Check that k is assignable: SUCCESS$
119
\[main.assigns\.\d+\] line \d+ Check that i is valid: SUCCESS$
12-
\[main.assigns\.\d+\] line \d+ Check that i is assignable: SUCCESS$
1310
\[main.assertion\.\d+\] line \d+ assertion i == k: SUCCESS$
1411
^EXIT=(6|10)$
1512
^SIGNAL=0$

src/goto-instrument/contracts/contracts.cpp

Lines changed: 15 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,8 @@ void code_contractst::check_apply_loop_contracts(
449449
loop_end,
450450
skip_function_paramst::NO,
451451
// do not use CFG info for now
452-
cfg_empty_info);
452+
cfg_empty_info,
453+
[&loop](const goto_programt::targett &t) { return loop.contains(t); });
453454

454455
// Now we begin instrumenting at the loop_end.
455456
// `pre_loop_end_instrs` are to be inserted before `loop_end`.
@@ -1081,11 +1082,9 @@ void code_contractst::apply_loop_contract(
10811082
// By definition the `loop_content` is a set of instructions computed
10821083
// by `natural_loops` based on the CFG.
10831084
// Since we perform assigns clause instrumentation by sequentially
1084-
// traversing instructions from `loop_head` to `loop_end`, here check that:
1085-
// 1. All instructions in `loop_content` are contained within the
1086-
// [loop_head, loop_end] iterator range
1087-
// 2. All instructions in the [loop_head, loop_end] range are contained
1088-
// in the `loop_content` set, except for the exceptions explained below.
1085+
// traversing instructions from `loop_head` to `loop_end`,
1086+
// here we ensure that all instructions in `loop_content` belong within
1087+
// the [loop_head, loop_end] target range
10891088

10901089
// Check 1. (i \in loop_content) ==> loop_head <= i <= loop_end
10911090
for(const auto &i : loop_content)
@@ -1100,49 +1099,6 @@ void code_contractst::apply_loop_contract(
11001099
throw 0;
11011100
}
11021101
}
1103-
1104-
// Check 2. (loop_head <= i <= loop_end) ==> (i \in loop_content)
1105-
//
1106-
// We allow the following exceptions in this check:
1107-
// - `SKIP` or `LOCATION` instructions which are no-op
1108-
// - `ASSUME(false)` instructions which are introduced by function pointer
1109-
// or nested loop transformations, and have no successor instructions
1110-
// - `SET_RETURN_VALUE` instructions followed by an uninterrupted sequence
1111-
// of `DEAD` instructions and a `GOTO` jump out of the loop,
1112-
// which model C `return` statements.
1113-
// - `GOTO` jumps out of the loops, which model C `break` statements.
1114-
// These instructions are allowed to be missing from `loop_content`,
1115-
// and may be safely ignored for the purpose of our instrumentation.
1116-
for(auto i = loop_head; i != loop_end; ++i)
1117-
{
1118-
if(loop_content.contains(i))
1119-
continue;
1120-
1121-
if(i->is_skip() || i->is_location())
1122-
continue;
1123-
1124-
if(i->is_goto() && !loop_content.contains(i->get_target()))
1125-
continue;
1126-
1127-
if(i->is_assume() && i->get_condition().is_false())
1128-
continue;
1129-
1130-
if(i->is_set_return_value())
1131-
{
1132-
do
1133-
i++;
1134-
while(i != loop_end && i->is_dead());
1135-
1136-
// because we increment `i` in the outer `for` loop
1137-
i--;
1138-
continue;
1139-
}
1140-
1141-
log.error() << "Computed loop at: " << loop_head->source_location()
1142-
<< "is missing an instruction:" << messaget::eom;
1143-
goto_function.body.output_instruction(ns, function_name, log.error(), *i);
1144-
throw 0;
1145-
}
11461102
}
11471103

11481104
for(size_t outer = 0; outer < loop_nesting_graph.size(); ++outer)
@@ -1205,13 +1161,22 @@ void code_contractst::apply_loop_contract(
12051161
loop_node.decreases_clause.is_nil())
12061162
continue;
12071163

1164+
// Computed loop "contents" needs to be refreshed to include any newly
1165+
// introduced instrumentation, e.g. havoc instructions for assigns clause,
1166+
// on inner loops in to the outer loop's contents.
1167+
// Else, during the outer loop instrumentation these instructions will be
1168+
// "masked" just as any other instruction not within its "contents."
1169+
1170+
goto_functions.update();
1171+
natural_loops_mutablet updated_loops(goto_function.body);
1172+
12081173
check_apply_loop_contracts(
12091174
function_name,
12101175
goto_function,
12111176
local_may_alias,
12121177
loop_node.head_target,
12131178
loop_node.end_target,
1214-
loop_node.content,
1179+
updated_loops.loop_map[loop_node.head_target],
12151180
loop_node.assigns_clause,
12161181
loop_node.invariant,
12171182
loop_node.decreases_clause,

src/goto-instrument/contracts/instrument_spec_assigns.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,12 +314,15 @@ void instrument_spec_assignst::instrument_instructions(
314314
goto_programt::targett instruction_it,
315315
const goto_programt::targett &instruction_end,
316316
skip_function_paramst skip_function_params,
317-
optionalt<cfg_infot> &cfg_info_opt)
317+
optionalt<cfg_infot> &cfg_info_opt,
318+
const std::function<bool(const goto_programt::targett &)> &pred)
318319
{
319320
while(instruction_it != instruction_end)
320321
{
321322
// Skip instructions marked as disabled for assigns clause checking
322-
if(has_disable_assigns_check_pragma(instruction_it))
323+
if(
324+
has_disable_assigns_check_pragma(instruction_it) ||
325+
(pred && !pred(instruction_it)))
323326
{
324327
instruction_it++;
325328
if(cfg_info_opt.has_value())
@@ -541,7 +544,7 @@ exprt instrument_spec_assignst::target_validity_expr(
541544
// the target's `start_address` pointer satisfies w_ok with the expected size
542545
// (or is NULL if we allow it explicitly).
543546
// This assertion will be falsified whenever `start_address` is invalid or
544-
// not of the right size (or is NULL if we dot not allow it expliclitly).
547+
// not of the right size (or is NULL if we do not allow it explicitly).
545548
auto result =
546549
or_exprt{not_exprt{car.condition()},
547550
w_ok_exprt{car.target_start_address(), car.target_size()}};

src/goto-instrument/contracts/instrument_spec_assigns.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,12 +444,25 @@ class instrument_spec_assignst
444444
optionalt<cfg_infot> &cfg_info_opt,
445445
goto_programt &dest);
446446

447+
/// Instruments a sequence of instructions with inclusion checks.
448+
/// If `pred` is not provided,
449+
/// then all instructions are instrumented.
450+
/// If `pred` is provided,
451+
/// then only the instructions that satisfy pred are instrumented.
452+
///
453+
/// \param body goto program containing the instructions
454+
/// \param instruction_it target to the first instruction of the sequence
455+
/// \param instruction_end target to the last instruction of the sequence
456+
/// \param skip_function_params the argument to the free operator
457+
/// \param cfg_info_opt allows target set pruning if available
458+
/// \param pred a predicate on targets to check if they should be instrumented
447459
void instrument_instructions(
448460
goto_programt &body,
449461
goto_programt::targett instruction_it,
450462
const goto_programt::targett &instruction_end,
451463
skip_function_paramst skip_function_params,
452-
optionalt<cfg_infot> &cfg_info_opt);
464+
optionalt<cfg_infot> &cfg_info_opt,
465+
const std::function<bool(const goto_programt::targett &)> &pred = {});
453466

454467
/// Inserts the detected static local symbols into a target container.
455468
/// \tparam C The type of the target container

0 commit comments

Comments
 (0)