Skip to content

Commit 7aa1044

Browse files
committed
Allow instrumentation of loops with "holes"
Our assigns clause instrumentation works over a sequence of instructions ignoring control flow edges. In #6701 a safety check was added to bail out on attempting to instrument loops that contain "holes" -- instructions that appear within the [head, end] sequence for the loop, but are not part of the computed "natural loop" because of control flow. In this PR, we remove this restriction and allow instrumenting such loops by masking out instructions outside of the computed natural loop.
1 parent 67f5d09 commit 7aa1044

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`.
@@ -1070,11 +1071,9 @@ void code_contractst::apply_loop_contract(
10701071
// By definition the `loop_content` is a set of instructions computed
10711072
// by `natural_loops` based on the CFG.
10721073
// Since we perform assigns clause instrumentation by sequentially
1073-
// traversing instructions from `loop_head` to `loop_end`, here check that:
1074-
// 1. All instructions in `loop_content` are contained within the
1075-
// [loop_head, loop_end] iterator range
1076-
// 2. All instructions in the [loop_head, loop_end] range are contained
1077-
// in the `loop_content` set, except for the exceptions explained below.
1074+
// traversing instructions from `loop_head` to `loop_end`,
1075+
// here we ensure that all instructions in `loop_content` belong within
1076+
// the [loop_head, loop_end] target range
10781077

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

11371093
for(size_t outer = 0; outer < loop_nesting_graph.size(); ++outer)
@@ -1194,13 +1150,22 @@ void code_contractst::apply_loop_contract(
11941150
loop_node.decreases_clause.is_nil())
11951151
continue;
11961152

1153+
// Computed loop "contents" needs to be refreshed to include any newly
1154+
// introduced instrumentation, e.g. havoc instructions for assigns clause,
1155+
// on inner loops in to the outer loop's contents.
1156+
// Else, during the outer loop instrumentation these instructions will be
1157+
// "masked" just as any other instruction not within its "contents."
1158+
1159+
goto_functions.update();
1160+
natural_loops_mutablet updated_loops(goto_function.body);
1161+
11971162
check_apply_loop_contracts(
11981163
function_name,
11991164
goto_function,
12001165
local_may_alias,
12011166
loop_node.head_target,
12021167
loop_node.end_target,
1203-
loop_node.content,
1168+
updated_loops.loop_map[loop_node.head_target],
12041169
loop_node.assigns_clause,
12051170
loop_node.invariant,
12061171
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)