Skip to content

Commit e460159

Browse files
committed
Infer loop assigns for DFCC with functions inlined
1 parent 8a32f3f commit e460159

File tree

8 files changed

+174
-32
lines changed

8 files changed

+174
-32
lines changed

regression/contracts-dfcc/loop_assigns_inference-01/test.desc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
KNOWNBUG
1+
CORE dfcc-only
22
main.c
33
--dfcc main --apply-loop-contracts
44
^EXIT=0$
@@ -15,4 +15,3 @@ main.c
1515
--
1616
This test checks loop locals are correctly removed during assigns inference so
1717
that the assign clause is correctly inferred.
18-
This test failed when using dfcc for loop contracts.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
struct hole
2+
{
3+
int pos;
4+
};
5+
6+
void set_len(struct hole *h, unsigned long int new_len)
7+
{
8+
h->pos = new_len;
9+
}
10+
11+
int main()
12+
{
13+
int i = 0;
14+
struct hole h;
15+
h.pos = 0;
16+
for(i = 0; i < 5; i++)
17+
// __CPROVER_assigns(h.pos, i)
18+
__CPROVER_loop_invariant(h.pos == i)
19+
{
20+
set_len(&h, h.pos + 1);
21+
}
22+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE dfcc-only
2+
main.c
3+
--dfcc main --apply-loop-contracts
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^\[main.\d+\] .* Check loop invariant before entry: SUCCESS$
7+
^VERIFICATION SUCCESSFUL$
8+
--
9+
--
10+
This test checks assigns h->pos is inferred correctly.

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

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -309,8 +309,7 @@ static struct contract_clausest default_loop_contract_clauses(
309309
const dfcc_loop_nesting_grapht &loop_nesting_graph,
310310
const std::size_t loop_id,
311311
const irep_idt &function_id,
312-
goto_functiont &goto_function,
313-
local_may_aliast &local_may_alias,
312+
const assignst &inferred_assigns,
314313
const bool check_side_effect,
315314
message_handlert &message_handler,
316315
const namespacet &ns)
@@ -361,18 +360,11 @@ static struct contract_clausest default_loop_contract_clauses(
361360
else
362361
{
363362
// infer assigns clause targets if none given
364-
auto inferred = dfcc_infer_loop_assigns(
365-
local_may_alias,
366-
goto_function,
367-
loop.instructions,
368-
loop.head->source_location(),
369-
message_handler,
370-
ns);
371363
log.warning() << "No assigns clause provided for loop " << function_id
372364
<< "." << loop.latch->loop_number << " at "
373365
<< loop.head->source_location() << ". The inferred set {";
374366
bool first = true;
375-
for(const auto &expr : inferred)
367+
for(const auto &expr : inferred_assigns)
376368
{
377369
if(!first)
378370
{
@@ -384,7 +376,7 @@ static struct contract_clausest default_loop_contract_clauses(
384376
log.warning() << "} might be incomplete or imprecise, please provide an "
385377
"assigns clause if the analysis fails."
386378
<< messaget::eom;
387-
result.assigns.swap(inferred);
379+
result.assigns = inferred_assigns;
388380
}
389381

390382
if(result.decreases_clauses.empty())
@@ -405,7 +397,7 @@ static dfcc_loop_infot gen_dfcc_loop_info(
405397
goto_functiont &goto_function,
406398
const std::map<std::size_t, dfcc_loop_infot> &loop_info_map,
407399
dirtyt &dirty,
408-
local_may_aliast &local_may_alias,
400+
const assignst &inferred_assigns,
409401
const bool check_side_effect,
410402
message_handlert &message_handler,
411403
dfcc_libraryt &library,
@@ -429,8 +421,7 @@ static dfcc_loop_infot gen_dfcc_loop_info(
429421
loop_nesting_graph,
430422
loop_id,
431423
function_id,
432-
goto_function,
433-
local_may_alias,
424+
inferred_assigns,
434425
check_side_effect,
435426
message_handler,
436427
ns);
@@ -481,6 +472,7 @@ static dfcc_loop_infot gen_dfcc_loop_info(
481472
}
482473

483474
dfcc_cfg_infot::dfcc_cfg_infot(
475+
goto_modelt &goto_model,
484476
const irep_idt &function_id,
485477
goto_functiont &goto_function,
486478
const exprt &top_level_write_set,
@@ -499,6 +491,9 @@ dfcc_cfg_infot::dfcc_cfg_infot(
499491
// Clean up possible fake loops that are due to do { ... } while(0);
500492
simplify_gotos(goto_program, ns);
501493

494+
// From loop number to the inferred loop assigns.
495+
std::map<std::size_t, assignst> inferred_loop_assigns_map;
496+
502497
if(loop_contract_config.apply_loop_contracts)
503498
{
504499
messaget log(message_handler);
@@ -523,6 +518,14 @@ dfcc_cfg_infot::dfcc_cfg_infot(
523518
{
524519
topsorted_loops.push_back(idx);
525520
}
521+
522+
// We infer loop assigns for all loops in the function.
523+
dfcc_infer_loop_assigns_for_function(
524+
inferred_loop_assigns_map,
525+
goto_model.goto_functions,
526+
goto_function,
527+
message_handler,
528+
ns);
526529
}
527530

528531
// At this point, either loop contracts were activated and the loop nesting
@@ -541,10 +544,11 @@ dfcc_cfg_infot::dfcc_cfg_infot(
541544

542545
// generate dfcc_cfg_loop_info for loops and add to loop_info_map
543546
dirtyt dirty(goto_function);
544-
local_may_aliast local_may_alias(goto_function);
545547

546548
for(const auto &loop_id : topsorted_loops)
547549
{
550+
auto inferred_loop_assigns =
551+
inferred_loop_assigns_map[loop_nesting_graph[loop_id].latch->loop_number];
548552
loop_info_map.insert(
549553
{loop_id,
550554
gen_dfcc_loop_info(
@@ -554,7 +558,7 @@ dfcc_cfg_infot::dfcc_cfg_infot(
554558
goto_function,
555559
loop_info_map,
556560
dirty,
557-
local_may_alias,
561+
inferred_loop_assigns,
558562
loop_contract_config.check_side_effect,
559563
message_handler,
560564
library,

src/goto-instrument/contracts/dynamic-frames/dfcc_cfg_info.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Date: March 2023
2121
#include <util/std_expr.h>
2222
#include <util/symbol_table.h>
2323

24+
#include <goto-programs/goto_model.h>
2425
#include <goto-programs/goto_program.h>
2526

2627
#include <goto-instrument/contracts/loop_contract_config.h>
@@ -242,6 +243,7 @@ class dfcc_cfg_infot
242243
{
243244
public:
244245
dfcc_cfg_infot(
246+
goto_modelt &goto_model,
245247
const irep_idt &function_id,
246248
goto_functiont &goto_function,
247249
const exprt &top_level_write_set,

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

Lines changed: 100 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@ Author: Remi Delmas, [email protected]
1313
#include <util/pointer_expr.h>
1414
#include <util/std_code.h>
1515

16+
#include <goto-programs/goto_inline.h>
17+
1618
#include <analyses/goto_rw.h>
1719
#include <goto-instrument/contracts/utils.h>
1820
#include <goto-instrument/havoc_utils.h>
1921

22+
#include "dfcc_loop_nesting_graph.h"
2023
#include "dfcc_root_object.h"
2124

2225
/// Builds a call expression `object_whole(expr)`
@@ -117,7 +120,7 @@ std::unordered_set<irep_idt> gen_loop_locals_set(
117120
return loop_locals;
118121
}
119122

120-
assignst dfcc_infer_loop_assigns(
123+
assignst dfcc_infer_loop_assigns_for_loop(
121124
const local_may_aliast &local_may_alias,
122125
goto_functiont &goto_function,
123126
const loopt &loop_instructions,
@@ -132,11 +135,6 @@ assignst dfcc_infer_loop_assigns(
132135
// compute locals
133136
std::unordered_set<irep_idt> loop_locals = gen_loop_locals_set(
134137
irep_idt(), goto_function, loop_instructions, message_handler, ns);
135-
for(const auto &target : loop_instructions)
136-
{
137-
if(target->is_decl())
138-
loop_locals.insert(target->decl_symbol().get_identifier());
139-
}
140138

141139
// widen or drop targets that depend on loop-locals or are non-constant,
142140
// ie. depend on other locations assigned by the loop.
@@ -179,3 +177,99 @@ assignst dfcc_infer_loop_assigns(
179177

180178
return result;
181179
}
180+
181+
/// Replace all assignment to `__CPROVER_dead_object` to avoid aliasing all targets
182+
/// are assigned to `__CPROVER_dead_object`.
183+
static void remove_dead_object_assignment(goto_functiont &goto_function)
184+
{
185+
Forall_goto_program_instructions(i_it, goto_function.body)
186+
{
187+
if(i_it->is_assign())
188+
{
189+
auto &lhs = i_it->assign_lhs();
190+
191+
if(
192+
lhs.id() == ID_symbol &&
193+
to_symbol_expr(lhs).get_identifier() == CPROVER_PREFIX "dead_object")
194+
{
195+
i_it->turn_into_skip();
196+
}
197+
}
198+
}
199+
}
200+
201+
void dfcc_infer_loop_assigns_for_function(
202+
std::map<std::size_t, assignst> &inferred_loop_assigns_map,
203+
goto_functionst &goto_functions,
204+
goto_functiont &goto_function,
205+
message_handlert &message_handler,
206+
const namespacet &ns)
207+
{
208+
messaget log(message_handler);
209+
210+
// We infer loop assigns based on the copy of `goto_function`.
211+
goto_functiont goto_function_copy;
212+
goto_function_copy.copy_from(goto_function);
213+
214+
// Map from targett in `goto_function_copy` to loop number.
215+
std::
216+
unordered_map<goto_programt::const_targett, std::size_t, const_target_hash>
217+
loop_number_map;
218+
219+
// Build the loop id map before inlining attempt.
220+
const auto loop_nesting_graph =
221+
build_loop_nesting_graph(goto_function_copy.body);
222+
{
223+
auto topsorted = loop_nesting_graph.topsort();
224+
// skip function without loop.
225+
if(topsorted.empty())
226+
return;
227+
228+
for(const auto id : topsorted)
229+
{
230+
loop_number_map.emplace(
231+
loop_nesting_graph[id].head, loop_nesting_graph[id].latch->loop_number);
232+
}
233+
}
234+
235+
// We avoid inlining `malloc` and `free` whose variables should not be assign targets.
236+
auto malloc_body = goto_functions.function_map.extract(irep_idt("malloc"));
237+
auto free_body = goto_functions.function_map.extract(irep_idt("free"));
238+
239+
// Inline all function calls in goto_function_copy.
240+
goto_program_inline(
241+
goto_functions, goto_function_copy.body, ns, log.get_message_handler());
242+
goto_function_copy.body.update();
243+
// Build the loop graph after inlining.
244+
const auto inlined_loop_nesting_graph =
245+
build_loop_nesting_graph(goto_function_copy.body);
246+
247+
// Alias analysis.
248+
remove_dead_object_assignment(goto_function_copy);
249+
local_may_aliast local_may_alias(goto_function_copy);
250+
251+
auto inlined_topsorted = inlined_loop_nesting_graph.topsort();
252+
253+
for(const auto inlined_id : inlined_topsorted)
254+
{
255+
// We only infer loop assigns for loops in the original function.
256+
if(
257+
loop_number_map.find(inlined_loop_nesting_graph[inlined_id].head) !=
258+
loop_number_map.end())
259+
{
260+
const auto loop_number =
261+
loop_number_map[inlined_loop_nesting_graph[inlined_id].head];
262+
const auto inlined_loop = inlined_loop_nesting_graph[inlined_id];
263+
264+
inferred_loop_assigns_map[loop_number] = dfcc_infer_loop_assigns_for_loop(
265+
local_may_alias,
266+
goto_function_copy,
267+
inlined_loop.instructions,
268+
inlined_loop.head->source_location(),
269+
message_handler,
270+
ns);
271+
}
272+
}
273+
goto_functions.function_map.insert(std::move(malloc_body));
274+
goto_functions.function_map.insert(std::move(free_body));
275+
}

src/goto-instrument/contracts/dynamic-frames/dfcc_infer_loop_assigns.h

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,31 @@ class messaget;
1919
class namespacet;
2020
class message_handlert;
2121

22+
/// Collect identifiers that are local to this loop only
23+
/// (excluding nested loop).
24+
std::unordered_set<irep_idt> gen_loop_locals_set(
25+
const irep_idt &function_id,
26+
goto_functiont &goto_function,
27+
const loopt &loop,
28+
message_handlert &message_handler,
29+
const namespacet &ns);
30+
2231
// \brief Infer assigns clause targets for a loop from its instructions and a
2332
// may alias analysis at the function level.
24-
assignst dfcc_infer_loop_assigns(
33+
assignst dfcc_infer_loop_assigns_for_loop(
2534
const local_may_aliast &local_may_alias,
26-
goto_functiont &goto_function,
35+
goto_functiont &goto_function,
2736
const loopt &loop_instructions,
2837
const source_locationt &loop_head_location,
2938
message_handlert &message_handler,
3039
const namespacet &ns);
3140

32-
/// Collect identifiers that are local to this loop only
33-
/// (excluding nested loop).
34-
std::unordered_set<irep_idt> gen_loop_locals_set(
35-
const irep_idt &function_id,
36-
goto_functiont &goto_function,
37-
const loopt &loop,
41+
// \brief Infer assigns clause targets for loops in `goto_function` from their
42+
// instructions and a may alias analysis at the function level (with inlining).
43+
void dfcc_infer_loop_assigns_for_function(
44+
std::map<std::size_t, assignst> &inferred_loop_assigns_map,
45+
goto_functionst &goto_functions,
46+
goto_functiont &goto_function,
3847
message_handlert &message_handler,
3948
const namespacet &ns);
4049

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@ void dfcc_instrumentt::instrument_goto_program(
399399

400400
// build control flow graph information
401401
dfcc_cfg_infot cfg_info(
402+
goto_model,
402403
function_id,
403404
goto_function,
404405
write_set,
@@ -448,6 +449,7 @@ void dfcc_instrumentt::instrument_goto_function(
448449

449450
// build control flow graph information
450451
dfcc_cfg_infot cfg_info(
452+
goto_model,
451453
function_id,
452454
goto_function,
453455
write_set,

0 commit comments

Comments
 (0)