Skip to content

Commit 19fc213

Browse files
authored
Merge pull request #8095 from tautschnig/bugfixes/contracts-loop-normalisation
Code contracts: do not interleave checking and instrumenting
2 parents c266ea3 + e2e3e0e commit 19fc213

File tree

8 files changed

+111
-45
lines changed

8 files changed

+111
-45
lines changed

regression/contracts-dfcc/loop_contracts_do_while/main.c

+6-4
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ int main()
55
int x = 0;
66

77
do
8-
{
9-
x++;
10-
} while(x < 10) __CPROVER_loop_invariant(0 <= x && x <= 10);
8+
__CPROVER_loop_invariant(0 <= x && x <= 10)
9+
{
10+
x++;
11+
}
12+
while(x < 10);
1113

12-
assert(x == 10);
14+
assert(x <= 10);
1315
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#include <assert.h>
2+
3+
int main()
4+
{
5+
int x = 0;
6+
7+
do
8+
{
9+
do
10+
{
11+
x++;
12+
} while(0);
13+
} while(0);
14+
15+
assert(x <= 10);
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
KNOWNBUG
2+
nested.c
3+
--dfcc main --apply-loop-contracts
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
We spuriously report that x is not assignable.
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
CORE dfcc-only
22
main.c
33
--dfcc main --apply-loop-contracts
4-
^Found loop with more than one latch instruction$
5-
^EXIT=(6|10)$
4+
^EXIT=10$
65
^SIGNAL=0$
76
--
7+
^Found loop with more than one latch instruction$
88
--
9-
This test checks that our loop contract instrumentation verifies that loops
10-
nested in loops with contracts also have contracts.
9+
This test checks that our loop contract instrumentation first transforms loops
10+
so as to only have a single loop latch.

src/goto-instrument/contracts/contracts.cpp

+45-28
Original file line numberDiff line numberDiff line change
@@ -888,18 +888,24 @@ void code_contractst::apply_loop_contract(
888888

889889
std::list<size_t> to_check_contracts_on_children;
890890

891+
std::map<
892+
goto_programt::targett,
893+
std::pair<goto_programt::targett, natural_loops_mutablet::loopt>,
894+
goto_programt::target_less_than>
895+
loop_head_ends;
896+
891897
for(const auto &loop_head_and_content : natural_loops.loop_map)
892898
{
893-
const auto &loop_content = loop_head_and_content.second;
899+
const auto &loop_body = loop_head_and_content.second;
894900
// Skip empty loops and self-looped node.
895-
if(loop_content.size() <= 1)
901+
if(loop_body.size() <= 1)
896902
continue;
897903

898904
auto loop_head = loop_head_and_content.first;
899905
auto loop_end = loop_head;
900906

901907
// Find the last back edge to `loop_head`
902-
for(const auto &t : loop_content)
908+
for(const auto &t : loop_body)
903909
{
904910
if(
905911
t->is_goto() && t->get_target() == loop_head &&
@@ -914,6 +920,41 @@ void code_contractst::apply_loop_contract(
914920
throw 0;
915921
}
916922

923+
// By definition the `loop_body` is a set of instructions computed
924+
// by `natural_loops` based on the CFG.
925+
// Since we perform assigns clause instrumentation by sequentially
926+
// traversing instructions from `loop_head` to `loop_end`,
927+
// here we ensure that all instructions in `loop_body` belong within
928+
// the [loop_head, loop_end] target range.
929+
930+
// Check 1. (i \in loop_body) ==> loop_head <= i <= loop_end
931+
for(const auto &i : loop_body)
932+
{
933+
if(
934+
loop_head->location_number > i->location_number ||
935+
i->location_number > loop_end->location_number)
936+
{
937+
log.conditional_output(
938+
log.error(), [&i, &loop_head](messaget::mstreamt &mstream) {
939+
mstream << "Computed loop at " << loop_head->source_location()
940+
<< "contains an instruction beyond [loop_head, loop_end]:"
941+
<< messaget::eom;
942+
i->output(mstream);
943+
mstream << messaget::eom;
944+
});
945+
throw 0;
946+
}
947+
}
948+
949+
if(!loop_head_ends.emplace(loop_head, std::make_pair(loop_end, loop_body))
950+
.second)
951+
UNREACHABLE;
952+
}
953+
954+
for(auto &loop_head_end : loop_head_ends)
955+
{
956+
auto loop_head = loop_head_end.first;
957+
auto loop_end = loop_head_end.second.first;
917958
// After loop-contract instrumentation, jumps to the `loop_head` will skip
918959
// some instrumented instructions. So we want to make sure that there is
919960
// only one jump targeting `loop_head` from `loop_end` before loop-contract
@@ -961,7 +1002,7 @@ void code_contractst::apply_loop_contract(
9611002
}
9621003

9631004
const auto idx = loop_nesting_graph.add_node(
964-
loop_content,
1005+
loop_head_end.second.second,
9651006
loop_head,
9661007
loop_end,
9671008
assigns_clause,
@@ -974,30 +1015,6 @@ void code_contractst::apply_loop_contract(
9741015
continue;
9751016

9761017
to_check_contracts_on_children.push_back(idx);
977-
978-
// By definition the `loop_content` is a set of instructions computed
979-
// by `natural_loops` based on the CFG.
980-
// Since we perform assigns clause instrumentation by sequentially
981-
// traversing instructions from `loop_head` to `loop_end`,
982-
// here we ensure that all instructions in `loop_content` belong within
983-
// the [loop_head, loop_end] target range
984-
985-
// Check 1. (i \in loop_content) ==> loop_head <= i <= loop_end
986-
for(const auto &i : loop_content)
987-
{
988-
if(std::distance(loop_head, i) < 0 || std::distance(i, loop_end) < 0)
989-
{
990-
log.conditional_output(
991-
log.error(), [&i, &loop_head](messaget::mstreamt &mstream) {
992-
mstream << "Computed loop at " << loop_head->source_location()
993-
<< "contains an instruction beyond [loop_head, loop_end]:"
994-
<< messaget::eom;
995-
i->output(mstream);
996-
mstream << messaget::eom;
997-
});
998-
throw 0;
999-
}
1000-
}
10011018
}
10021019

10031020
for(size_t outer = 0; outer < loop_nesting_graph.size(); ++outer)

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

+14
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ void dfcc_check_loop_normal_form(goto_programt &goto_program, messaget &log)
1919
// instruction span for each loop
2020
std::vector<std::pair<goto_programt::targett, goto_programt::targett>> spans;
2121

22+
std::map<
23+
goto_programt::targett,
24+
goto_programt::targett,
25+
goto_programt::target_less_than>
26+
latch_head_pairs;
27+
2228
for(auto &loop : natural_loops.loop_map)
2329
{
2430
auto head = loop.first;
@@ -132,7 +138,15 @@ void dfcc_check_loop_normal_form(goto_programt &goto_program, messaget &log)
132138
"nested loop head and latch are in outer loop");
133139
}
134140

141+
if(!latch_head_pairs.emplace(latch, head).second)
142+
UNREACHABLE;
143+
}
144+
135145
// all checks passed, now we perform some instruction rewriting
146+
for(auto &entry_pair : latch_head_pairs)
147+
{
148+
auto latch = entry_pair.first;
149+
auto head = entry_pair.second;
136150

137151
// Convert conditional latch into exiting + unconditional latch.
138152
// ```

src/goto-instrument/goto_instrument_parse_options.cpp

+7-5
Original file line numberDiff line numberDiff line change
@@ -1174,6 +1174,8 @@ void goto_instrument_parse_optionst::instrument_goto_program()
11741174
}
11751175

11761176
do_indirect_call_and_rtti_removal();
1177+
log.status() << "Trying to force one backedge per target" << messaget::eom;
1178+
ensure_one_backedge_per_target(goto_model);
11771179

11781180
const irep_idt harness_id(cmdline.get_value(FLAG_DFCC));
11791181

@@ -1232,13 +1234,13 @@ void goto_instrument_parse_optionst::instrument_goto_program()
12321234
to_exclude_from_nondet_static,
12331235
log.get_message_handler());
12341236
}
1235-
1236-
if(
1237-
!use_dfcc &&
1238-
(cmdline.isset(FLAG_LOOP_CONTRACTS) || cmdline.isset(FLAG_REPLACE_CALL) ||
1239-
cmdline.isset(FLAG_ENFORCE_CONTRACT)))
1237+
else if((cmdline.isset(FLAG_LOOP_CONTRACTS) ||
1238+
cmdline.isset(FLAG_REPLACE_CALL) ||
1239+
cmdline.isset(FLAG_ENFORCE_CONTRACT)))
12401240
{
12411241
do_indirect_call_and_rtti_removal();
1242+
log.status() << "Trying to force one backedge per target" << messaget::eom;
1243+
ensure_one_backedge_per_target(goto_model);
12421244
code_contractst contracts(goto_model, log);
12431245

12441246
std::set<std::string> to_replace(

src/goto-programs/restrict_function_pointers.cpp

+10-4
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ void for_each_function_call(GotoFunctionT &&goto_function, Handler handler)
3535
handler);
3636
}
3737

38-
static void restrict_function_pointer(
38+
[[nodiscard]] static bool restrict_function_pointer(
3939
message_handlert &message_handler,
4040
symbol_tablet &symbol_table,
4141
goto_programt &goto_program,
@@ -54,7 +54,7 @@ static void restrict_function_pointer(
5454
const auto &original_function = location->call_function();
5555

5656
if(!can_cast_expr<dereference_exprt>(original_function))
57-
return;
57+
return false;
5858

5959
// because we run the label function pointer calls transformation pass before
6060
// this stage a dereference can only dereference a symbol expression
@@ -66,7 +66,7 @@ static void restrict_function_pointer(
6666
restrictions.restrictions.find(pointer_symbol.get_identifier());
6767

6868
if(restriction_iterator == restrictions.restrictions.end())
69-
return;
69+
return false;
7070

7171
const namespacet ns(symbol_table);
7272
std::unordered_set<symbol_exprt, irep_hash> candidates;
@@ -80,6 +80,8 @@ static void restrict_function_pointer(
8080
function_id,
8181
location,
8282
candidates);
83+
84+
return true;
8385
}
8486
} // namespace
8587

@@ -180,15 +182,19 @@ void restrict_function_pointers(
180182
{
181183
goto_functiont &goto_function = function_item.second;
182184

185+
bool did_something = false;
183186
for_each_function_call(goto_function, [&](const goto_programt::targett it) {
184-
restrict_function_pointer(
187+
did_something |= restrict_function_pointer(
185188
message_handler,
186189
goto_model.symbol_table,
187190
goto_function.body,
188191
function_item.first,
189192
restrictions,
190193
it);
191194
});
195+
196+
if(did_something)
197+
goto_function.body.update();
192198
}
193199
}
194200

0 commit comments

Comments
 (0)