Skip to content

Commit 8f543a2

Browse files
committed
added lots of sanity checks to loop contracts
Our loop contracts instrumentation routine makes some assumptions regarding the structure of loops. In this commit, I have added checks to verify them before we start instrumenting.
1 parent d7a5d97 commit 8f543a2

File tree

4 files changed

+221
-64
lines changed

4 files changed

+221
-64
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include <assert.h>
2+
3+
int main()
4+
{
5+
int i = 0, j = 0, k = 0;
6+
7+
while(j < 10)
8+
{
9+
while(k < 10)
10+
__CPROVER_loop_invariant(1 == 1)
11+
{
12+
while(i < 10)
13+
{
14+
i++;
15+
}
16+
}
17+
j++;
18+
}
19+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE
2+
main.c
3+
--apply-loop-contracts
4+
activate-multi-line-match
5+
Inner loop at: file main.c line 12 function main does not have contracts, but an enclosing loop does.\nPlease provide contracts for this loop, or unwind it first.
6+
^EXIT=(6|10)$
7+
^SIGNAL=0$
8+
--
9+
--
10+
This test checks that our instrumentation routines verify that inner loops
11+
either have contracts or are unwound, if any enclosing loop has contracts.

src/goto-instrument/contracts/contracts.cpp

Lines changed: 187 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -140,62 +140,15 @@ void code_contractst::check_apply_loop_contracts(
140140
goto_functionst::goto_functiont &goto_function,
141141
const local_may_aliast &local_may_alias,
142142
goto_programt::targett loop_head,
143+
goto_programt::targett loop_end,
143144
const loopt &loop,
145+
exprt assigns_clause,
146+
exprt invariant,
147+
exprt decreases_clause,
144148
const irep_idt &mode)
145149
{
146-
PRECONDITION(!loop.empty());
147-
148150
const auto loop_head_location = loop_head->source_location();
149151

150-
// find the last back edge
151-
goto_programt::targett loop_end = loop_head;
152-
for(const auto &t : loop)
153-
{
154-
if(
155-
t->is_goto() && t->get_target() == loop_head &&
156-
t->location_number > loop_end->location_number)
157-
loop_end = t;
158-
}
159-
160-
// check for assigns, invariant, and decreases clauses
161-
auto assigns_clause = static_cast<const exprt &>(
162-
loop_end->get_condition().find(ID_C_spec_assigns));
163-
auto invariant = static_cast<const exprt &>(
164-
loop_end->get_condition().find(ID_C_spec_loop_invariant));
165-
auto decreases_clause = static_cast<const exprt &>(
166-
loop_end->get_condition().find(ID_C_spec_decreases));
167-
168-
if(invariant.is_nil())
169-
{
170-
if(decreases_clause.is_nil() && assigns_clause.is_nil())
171-
{
172-
// no contracts to enforce, so return
173-
return;
174-
}
175-
else
176-
{
177-
invariant = true_exprt();
178-
// assigns clause is missing; we will try to automatic inference
179-
log.warning() << "The loop at " << loop_head_location.as_string()
180-
<< " does not have an invariant in its contract.\n"
181-
<< "Hence, a default invariant ('true') is being used.\n"
182-
<< "This choice is sound, but verification may fail"
183-
<< " if it is be too weak to prove the desired properties."
184-
<< messaget::eom;
185-
}
186-
}
187-
else
188-
{
189-
invariant = conjunction(invariant.operands());
190-
if(decreases_clause.is_nil())
191-
{
192-
log.warning() << "The loop at " << loop_head_location.as_string()
193-
<< " does not have a decreases clause in its contract.\n"
194-
<< "Termination of this loop will not be verified."
195-
<< messaget::eom;
196-
}
197-
}
198-
199152
// Vector representing a (possibly multidimensional) decreases clause
200153
const auto &decreases_clause_exprs = decreases_clause.operands();
201154

@@ -1022,20 +975,144 @@ void code_contractst::apply_loop_contract(
1022975

1023976
struct loop_graph_nodet : public graph_nodet<empty_edget>
1024977
{
1025-
typedef const goto_programt::targett &targett;
1026-
typedef const typename natural_loops_mutablet::loopt &loopt;
1027-
1028-
targett target;
1029-
loopt loop;
1030-
1031-
loop_graph_nodet(targett t, loopt l) : target(t), loop(l)
978+
const typename natural_loops_mutablet::loopt &content;
979+
const goto_programt::targett head_target, end_target;
980+
exprt assigns_clause, invariant, decreases_clause;
981+
982+
loop_graph_nodet(
983+
const typename natural_loops_mutablet::loopt &loop,
984+
const goto_programt::targett head,
985+
const goto_programt::targett end,
986+
const exprt &assigns,
987+
const exprt &inv,
988+
const exprt &decreases)
989+
: content(loop),
990+
head_target(head),
991+
end_target(end),
992+
assigns_clause(assigns),
993+
invariant(inv),
994+
decreases_clause(decreases)
1032995
{
1033996
}
1034997
};
1035998
grapht<loop_graph_nodet> loop_nesting_graph;
1036999

1037-
for(const auto &loop : natural_loops.loop_map)
1038-
loop_nesting_graph.add_node(loop.first, loop.second);
1000+
std::list<size_t> to_check_contracts_on_children;
1001+
1002+
for(const auto &loop_head_and_content : natural_loops.loop_map)
1003+
{
1004+
const auto &loop_content = loop_head_and_content.second;
1005+
if(loop_content.empty())
1006+
continue;
1007+
1008+
auto loop_head = loop_head_and_content.first;
1009+
auto loop_end = loop_head;
1010+
1011+
// Find the last back edge to `loop_head`
1012+
for(const auto &t : loop_content)
1013+
{
1014+
if(
1015+
t->is_goto() && t->get_target() == loop_head &&
1016+
t->location_number > loop_end->location_number)
1017+
loop_end = t;
1018+
}
1019+
1020+
if(loop_end == loop_head)
1021+
{
1022+
log.error() << "Could not find end of the loop starting at: "
1023+
<< loop_head->source_location() << messaget::eom;
1024+
throw 0;
1025+
}
1026+
1027+
exprt assigns_clause =
1028+
static_cast<const exprt &>(loop_end->condition().find(ID_C_spec_assigns));
1029+
exprt invariant = static_cast<const exprt &>(
1030+
loop_end->get_condition().find(ID_C_spec_loop_invariant));
1031+
exprt decreases_clause = static_cast<const exprt &>(
1032+
loop_end->get_condition().find(ID_C_spec_decreases));
1033+
1034+
if(invariant.is_nil())
1035+
{
1036+
if(decreases_clause.is_not_nil() || assigns_clause.is_not_nil())
1037+
{
1038+
invariant = true_exprt{};
1039+
// assigns clause is missing; we will try to automatic inference
1040+
log.warning()
1041+
<< "The loop at " << loop_head->source_location().as_string()
1042+
<< " does not have an invariant in its contract.\n"
1043+
<< "Hence, a default invariant ('true') is being used.\n"
1044+
<< "This choice is sound, but verification may fail"
1045+
<< " if it is be too weak to prove the desired properties."
1046+
<< messaget::eom;
1047+
}
1048+
}
1049+
else
1050+
{
1051+
invariant = conjunction(invariant.operands());
1052+
if(decreases_clause.is_nil())
1053+
{
1054+
log.warning() << "The loop at "
1055+
<< loop_head->source_location().as_string()
1056+
<< " does not have a decreases clause in its contract.\n"
1057+
<< "Termination of this loop will not be verified."
1058+
<< messaget::eom;
1059+
}
1060+
}
1061+
1062+
const auto idx = loop_nesting_graph.add_node(
1063+
loop_content,
1064+
loop_head,
1065+
loop_end,
1066+
assigns_clause,
1067+
invariant,
1068+
decreases_clause);
1069+
1070+
if(
1071+
assigns_clause.is_nil() && invariant.is_nil() &&
1072+
decreases_clause.is_nil())
1073+
continue;
1074+
1075+
to_check_contracts_on_children.push_back(idx);
1076+
1077+
// If the loop has contracts to be enforced, then
1078+
// reject irregular loops that have "holes" within their body,
1079+
// while allowing instructions that are okay to be "out of loop".
1080+
for(auto i = loop_head; i < loop_end; ++i)
1081+
{
1082+
if(loop_content.contains(i))
1083+
continue;
1084+
1085+
// `break` or `goto` out of loop, are okay
1086+
if(i->is_goto() && !loop_content.contains(i->get_target()))
1087+
continue;
1088+
1089+
// `assume false`, is okay
1090+
if(i->is_assume() && i->get_condition().is_false())
1091+
continue;
1092+
1093+
// `set return value` followed by `dead`s, are okay
1094+
if(i->is_set_return_value())
1095+
{
1096+
do
1097+
i++;
1098+
while(i->is_dead());
1099+
1100+
// because we increment `i` in the outer `for` loop
1101+
i--;
1102+
1103+
// the `goto` for `return` will be handled in next iteration
1104+
continue;
1105+
}
1106+
1107+
log.error() << "Cannot enforce contracts on irregular loop at: "
1108+
<< loop_head->source_location()
1109+
<< ".\nLoop body must be a contiguous set of instructions."
1110+
<< messaget::eom;
1111+
log.debug() << "Out-of-loop-body instruction:" << messaget::eom;
1112+
goto_function.body.output_instruction(ns, function_name, log.debug(), *i);
1113+
throw 0;
1114+
}
1115+
}
10391116

10401117
for(size_t outer = 0; outer < loop_nesting_graph.size(); ++outer)
10411118
{
@@ -1044,23 +1121,69 @@ void code_contractst::apply_loop_contract(
10441121
if(inner == outer)
10451122
continue;
10461123

1047-
if(loop_nesting_graph[outer].loop.contains(
1048-
loop_nesting_graph[inner].target))
1124+
if(loop_nesting_graph[outer].content.contains(
1125+
loop_nesting_graph[inner].head_target))
1126+
{
1127+
if(!loop_nesting_graph[outer].content.contains(
1128+
loop_nesting_graph[inner].end_target))
1129+
{
1130+
log.error()
1131+
<< "Overlapping loops at:\n"
1132+
<< loop_nesting_graph[outer].head_target->source_location()
1133+
<< "\nand\n"
1134+
<< loop_nesting_graph[inner].head_target->source_location()
1135+
<< "\nLoops must be nested or sequential for contracts to be "
1136+
"enforced."
1137+
<< messaget::eom;
1138+
}
10491139
loop_nesting_graph.add_edge(inner, outer);
1140+
}
1141+
}
1142+
}
1143+
1144+
// make sure all children of a contractified loop also have contracts
1145+
while(!to_check_contracts_on_children.empty())
1146+
{
1147+
const auto loop_idx = to_check_contracts_on_children.front();
1148+
to_check_contracts_on_children.pop_front();
1149+
1150+
const auto &loop_node = loop_nesting_graph[loop_idx];
1151+
if(
1152+
loop_node.assigns_clause.is_nil() && loop_node.invariant.is_nil() &&
1153+
loop_node.decreases_clause.is_nil())
1154+
{
1155+
log.error()
1156+
<< "Inner loop at: " << loop_node.head_target->source_location()
1157+
<< " does not have contracts, but an enclosing loop does.\n"
1158+
<< "Please provide contracts for this loop, or unwind it first."
1159+
<< messaget::eom;
1160+
throw 0;
10501161
}
1162+
1163+
for(const auto child_idx : loop_nesting_graph.get_predecessors(loop_idx))
1164+
to_check_contracts_on_children.push_back(child_idx);
10511165
}
10521166

10531167
// Iterate over the (natural) loops in the function, in topo-sorted order,
10541168
// and apply any loop contracts that we find.
10551169
for(const auto &idx : loop_nesting_graph.topsort())
10561170
{
10571171
const auto &loop_node = loop_nesting_graph[idx];
1172+
if(
1173+
loop_node.assigns_clause.is_nil() && loop_node.invariant.is_nil() &&
1174+
loop_node.decreases_clause.is_nil())
1175+
continue;
1176+
10581177
check_apply_loop_contracts(
10591178
function_name,
10601179
goto_function,
10611180
local_may_alias,
1062-
loop_node.target,
1063-
loop_node.loop,
1181+
loop_node.head_target,
1182+
loop_node.end_target,
1183+
loop_node.content,
1184+
loop_node.assigns_clause,
1185+
loop_node.invariant,
1186+
loop_node.decreases_clause,
10641187
symbol_table.lookup_ref(function_name).mode);
10651188
}
10661189
}

src/goto-instrument/contracts/contracts.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,11 @@ class code_contractst
102102
goto_functionst::goto_functiont &goto_function,
103103
const local_may_aliast &local_may_alias,
104104
goto_programt::targett loop_head,
105+
goto_programt::targett loop_end,
105106
const loopt &loop,
107+
exprt assigns_clause,
108+
exprt invariant,
109+
exprt decreases_clause,
106110
const irep_idt &mode);
107111

108112
// for "helper" classes to update symbol table.

0 commit comments

Comments
 (0)