@@ -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+ << " \n and\n "
1161+ << loop_nesting_graph[inner].head_target ->source_location ()
1162+ << " \n Loops 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}
0 commit comments