Skip to content

Commit af24259

Browse files
committed
CONTRACTS: Lots of sanity checks on 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, so as to avoid potentially unsound instrumentation.
1 parent 4eed7a9 commit af24259

File tree

4 files changed

+248
-64
lines changed

4 files changed

+248
-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: 214 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,171 @@ 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+
// By definition the `loop_content` is a set of instructions computed
1078+
// by `natural_loops` based on the CFG.
1079+
// Since we perform assigns clause instrumentation by sequentially
1080+
// traversing instructions from `loop_head` to `loop_end`, here check that:
1081+
// 1. All instructions in `loop_content` are contained within the
1082+
// [loop_head, loop_end] iterator range
1083+
// 2. All instructions in the [loop_head, loop_end] range are contained
1084+
// in the `loop_content` set, except for the exceptions explained below.
1085+
1086+
// Check 1. (i \in loop_content) ==> loop_head <= i <= loop_end
1087+
for(const auto &i : loop_content)
1088+
{
1089+
if(std::distance(loop_head, i) < 0 || std::distance(i, loop_end) < 0)
1090+
{
1091+
log.error() << "Computed loop at " << loop_head->source_location()
1092+
<< "contains an instruction beyond [loop_head, loop_end]:"
1093+
<< messaget::eom;
1094+
goto_function.body.output_instruction(
1095+
ns, function_name, log.error(), *i);
1096+
throw 0;
1097+
}
1098+
}
1099+
1100+
// Check 2. (loop_head <= i <= loop_end) ==> (i \in loop_content)
1101+
//
1102+
// We allow the following exceptions in this check:
1103+
// - `SKIP` or `LOCATION` instructions which are no-op
1104+
// - `ASSUME(false)` instructions which are introduced by function pointer
1105+
// or nested loop transformations, and have no successor instructions
1106+
// - `SET_RETURN_VALUE` instructions followed by an uninterrupted sequence
1107+
// of `DEAD` instructions and a `GOTO` jump out of the loop,
1108+
// which model C `return` statements.
1109+
// - `GOTO` jumps out of the loops, which model C `break` statements.
1110+
// These instructions are allowed to be missing from `loop_content`,
1111+
// and may be safely ignored for the purpose of our instrumentation.
1112+
for(auto i = loop_head; i < loop_end; ++i)
1113+
{
1114+
if(loop_content.contains(i))
1115+
continue;
1116+
1117+
if(i->is_skip() || i->is_location())
1118+
continue;
1119+
1120+
if(i->is_goto() && !loop_content.contains(i->get_target()))
1121+
continue;
1122+
1123+
if(i->is_assume() && i->get_condition().is_false())
1124+
continue;
1125+
1126+
if(i->is_set_return_value())
1127+
{
1128+
do
1129+
i++;
1130+
while(i->is_dead());
1131+
1132+
// because we increment `i` in the outer `for` loop
1133+
i--;
1134+
continue;
1135+
}
1136+
1137+
log.error() << "Computed loop at: " << loop_head->source_location()
1138+
<< "is missing an instruction:" << messaget::eom;
1139+
goto_function.body.output_instruction(ns, function_name, log.error(), *i);
1140+
throw 0;
1141+
}
1142+
}
10391143

10401144
for(size_t outer = 0; outer < loop_nesting_graph.size(); ++outer)
10411145
{
@@ -1044,23 +1148,69 @@ void code_contractst::apply_loop_contract(
10441148
if(inner == outer)
10451149
continue;
10461150

1047-
if(loop_nesting_graph[outer].loop.contains(
1048-
loop_nesting_graph[inner].target))
1151+
if(loop_nesting_graph[outer].content.contains(
1152+
loop_nesting_graph[inner].head_target))
1153+
{
1154+
if(!loop_nesting_graph[outer].content.contains(
1155+
loop_nesting_graph[inner].end_target))
1156+
{
1157+
log.error()
1158+
<< "Overlapping loops at:\n"
1159+
<< loop_nesting_graph[outer].head_target->source_location()
1160+
<< "\nand\n"
1161+
<< loop_nesting_graph[inner].head_target->source_location()
1162+
<< "\nLoops must be nested or sequential for contracts to be "
1163+
"enforced."
1164+
<< messaget::eom;
1165+
}
10491166
loop_nesting_graph.add_edge(inner, outer);
1167+
}
10501168
}
10511169
}
10521170

1171+
// make sure all children of a contractified loop also have contracts
1172+
while(!to_check_contracts_on_children.empty())
1173+
{
1174+
const auto loop_idx = to_check_contracts_on_children.front();
1175+
to_check_contracts_on_children.pop_front();
1176+
1177+
const auto &loop_node = loop_nesting_graph[loop_idx];
1178+
if(
1179+
loop_node.assigns_clause.is_nil() && loop_node.invariant.is_nil() &&
1180+
loop_node.decreases_clause.is_nil())
1181+
{
1182+
log.error()
1183+
<< "Inner loop at: " << loop_node.head_target->source_location()
1184+
<< " does not have contracts, but an enclosing loop does.\n"
1185+
<< "Please provide contracts for this loop, or unwind it first."
1186+
<< messaget::eom;
1187+
throw 0;
1188+
}
1189+
1190+
for(const auto child_idx : loop_nesting_graph.get_predecessors(loop_idx))
1191+
to_check_contracts_on_children.push_back(child_idx);
1192+
}
1193+
10531194
// Iterate over the (natural) loops in the function, in topo-sorted order,
10541195
// and apply any loop contracts that we find.
10551196
for(const auto &idx : loop_nesting_graph.topsort())
10561197
{
10571198
const auto &loop_node = loop_nesting_graph[idx];
1199+
if(
1200+
loop_node.assigns_clause.is_nil() && loop_node.invariant.is_nil() &&
1201+
loop_node.decreases_clause.is_nil())
1202+
continue;
1203+
10581204
check_apply_loop_contracts(
10591205
function_name,
10601206
goto_function,
10611207
local_may_alias,
1062-
loop_node.target,
1063-
loop_node.loop,
1208+
loop_node.head_target,
1209+
loop_node.end_target,
1210+
loop_node.content,
1211+
loop_node.assigns_clause,
1212+
loop_node.invariant,
1213+
loop_node.decreases_clause,
10641214
symbol_table.lookup_ref(function_name).mode);
10651215
}
10661216
}

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)