Skip to content

Commit 37dc87d

Browse files
committed
Infer loop assigns for DFCC with functions inlined
1 parent 018c61c commit 37dc87d

File tree

8 files changed

+249
-26
lines changed

8 files changed

+249
-26
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
KNOWNBUG
1+
CORE dfcc-only
22
main.c
3-
--dfcc main --apply-loop-contracts
3+
--dfcc main --apply-loop-contracts _ --no-standard-checks
44
^EXIT=0$
55
^SIGNAL=0$
66
^\[body_1.assigns.\d+\] .* Check that j is assignable: SUCCESS$
77
^\[body_2.assigns.\d+\] .* Check that \*i is assignable: SUCCESS$
88
^\[body_3.assigns.\d+\] .* Check that \*i is assignable: SUCCESS$
99
^\[incr.assigns.\d+\] .* Check that \*i is assignable: SUCCESS$
10-
^\[main.\d+\] .* Check loop invariant before entry: SUCCESS$
11-
^\[main.\d+\] .* Check that loop invariant is preserved: SUCCESS$
10+
^\[main.loop_invariant_base.\d+\] line \d+ Check invariant before entry for loop .*: SUCCESS$
11+
^\[main.loop_invariant_step.\d+\] line \d+ Check invariant after step for loop .*: SUCCESS$
12+
^\[main.loop_step_unwinding.\d+\] line \d+ Check step was unwound for loop .*: SUCCESS$
1213
^\[main.assertion.\d+\] .* assertion j == 9: SUCCESS$
1314
^VERIFICATION SUCCESSFUL$
1415
--
1516
--
1617
This test checks loop locals are correctly removed during assigns inference so
1718
that the assign clause is correctly inferred.
18-
This test failed when using dfcc for loop contracts.
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CORE dfcc-only
2+
main.c
3+
--dfcc main --apply-loop-contracts
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^\[main.loop_invariant_base.\d+\] line \d+ Check invariant before entry for loop .*: SUCCESS$
7+
^\[main.loop_invariant_base.\d+\] line \d+ Check invariant before entry for loop .*: SUCCESS$
8+
^\[main.loop_invariant_step.\d+\] line \d+ Check invariant after step for loop .*: SUCCESS$
9+
^\[main.loop_step_unwinding.\d+\] line \d+ Check step was unwound for loop .*: SUCCESS$
10+
^\[set_len.assigns.\d+\] line \d+ Check that h\-\>pos is assignable: SUCCESS
11+
^VERIFICATION SUCCESSFUL$
12+
--
13+
--
14+
This test checks assigns h->pos is inferred correctly.

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

+26-11
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,13 +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, goto_function, loop, message_handler, ns);
366363
log.warning() << "No assigns clause provided for loop " << function_id
367364
<< "." << loop.latch->loop_number << " at "
368365
<< loop.head->source_location() << ". The inferred set {";
369366
bool first = true;
370-
for(const auto &expr : inferred)
367+
for(const auto &expr : inferred_assigns)
371368
{
372369
if(!first)
373370
{
@@ -379,7 +376,7 @@ static struct contract_clausest default_loop_contract_clauses(
379376
log.warning() << "} might be incomplete or imprecise, please provide an "
380377
"assigns clause if the analysis fails."
381378
<< messaget::eom;
382-
result.assigns.swap(inferred);
379+
result.assigns = inferred_assigns;
383380
}
384381

385382
if(result.decreases_clauses.empty())
@@ -400,7 +397,7 @@ static dfcc_loop_infot gen_dfcc_loop_info(
400397
goto_functiont &goto_function,
401398
const std::map<std::size_t, dfcc_loop_infot> &loop_info_map,
402399
dirtyt &dirty,
403-
local_may_aliast &local_may_alias,
400+
const assignst &inferred_assigns,
404401
const bool check_side_effect,
405402
message_handlert &message_handler,
406403
dfcc_libraryt &library,
@@ -436,8 +433,7 @@ static dfcc_loop_infot gen_dfcc_loop_info(
436433
loop_nesting_graph,
437434
loop_id,
438435
function_id,
439-
goto_function,
440-
local_may_alias,
436+
inferred_assigns,
441437
check_side_effect,
442438
message_handler,
443439
ns);
@@ -488,6 +484,7 @@ static dfcc_loop_infot gen_dfcc_loop_info(
488484
}
489485

490486
dfcc_cfg_infot::dfcc_cfg_infot(
487+
goto_modelt &goto_model,
491488
const irep_idt &function_id,
492489
goto_functiont &goto_function,
493490
const exprt &top_level_write_set,
@@ -506,6 +503,9 @@ dfcc_cfg_infot::dfcc_cfg_infot(
506503
// Clean up possible fake loops that are due to do { ... } while(0);
507504
simplify_gotos(goto_program, ns);
508505

506+
// From loop number to the inferred loop assigns.
507+
std::map<std::size_t, assignst> inferred_loop_assigns_map;
508+
509509
if(loop_contract_config.apply_loop_contracts)
510510
{
511511
messaget log(message_handler);
@@ -526,9 +526,23 @@ dfcc_cfg_infot::dfcc_cfg_infot(
526526

527527
auto topsorted = loop_nesting_graph.topsort();
528528

529+
bool has_loops_with_contracts = false;
529530
for(const auto idx : topsorted)
530531
{
531532
topsorted_loops.push_back(idx);
533+
has_loops_with_contracts |= has_contract(
534+
loop_nesting_graph[idx].latch, loop_contract_config.check_side_effect);
535+
}
536+
537+
// We infer loop assigns for all loops in the function.
538+
if(has_loops_with_contracts)
539+
{
540+
dfcc_infer_loop_assigns_for_function(
541+
inferred_loop_assigns_map,
542+
goto_model.goto_functions,
543+
goto_function,
544+
message_handler,
545+
ns);
532546
}
533547
}
534548

@@ -548,10 +562,11 @@ dfcc_cfg_infot::dfcc_cfg_infot(
548562

549563
// generate dfcc_cfg_loop_info for loops and add to loop_info_map
550564
dirtyt dirty(goto_function);
551-
local_may_aliast local_may_alias(goto_function);
552565

553566
for(const auto &loop_id : topsorted_loops)
554567
{
568+
auto inferred_loop_assigns =
569+
inferred_loop_assigns_map[loop_nesting_graph[loop_id].latch->loop_number];
555570
loop_info_map.insert(
556571
{loop_id,
557572
gen_dfcc_loop_info(
@@ -561,7 +576,7 @@ dfcc_cfg_infot::dfcc_cfg_infot(
561576
goto_function,
562577
loop_info_map,
563578
dirty,
564-
local_may_alias,
579+
inferred_loop_assigns,
565580
loop_contract_config.check_side_effect,
566581
message_handler,
567582
library,

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

+2
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

+167-1
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)`
@@ -154,10 +157,67 @@ std::unordered_set<irep_idt> gen_loop_locals_set(
154157
return loop_locals;
155158
}
156159

157-
assignst dfcc_infer_loop_assigns(
160+
/// Find all identifiers in `src`.
161+
static std::unordered_set<irep_idt>
162+
find_symbol_identifiers(const goto_programt &src)
163+
{
164+
std::unordered_set<irep_idt> identifiers;
165+
for(const auto &instruction : src.instructions)
166+
{
167+
// compute forward edges first
168+
switch(instruction.type())
169+
{
170+
case ASSERT:
171+
case ASSUME:
172+
case GOTO:
173+
find_symbols(instruction.condition(), identifiers);
174+
break;
175+
176+
case FUNCTION_CALL:
177+
find_symbols(instruction.call_lhs(), identifiers);
178+
for(const auto &e : instruction.call_arguments())
179+
find_symbols(e, identifiers);
180+
break;
181+
case ASSIGN:
182+
case OTHER:
183+
184+
case SET_RETURN_VALUE:
185+
case DECL:
186+
case DEAD:
187+
for(const auto &e : instruction.code().operands())
188+
{
189+
find_symbols(e, identifiers);
190+
}
191+
break;
192+
193+
case END_THREAD:
194+
case END_FUNCTION:
195+
case ATOMIC_BEGIN:
196+
case ATOMIC_END:
197+
case SKIP:
198+
case LOCATION:
199+
case CATCH:
200+
case THROW:
201+
case START_THREAD:
202+
break;
203+
case NO_INSTRUCTION_TYPE:
204+
case INCOMPLETE_GOTO:
205+
UNREACHABLE;
206+
break;
207+
}
208+
}
209+
return identifiers;
210+
}
211+
212+
/// Infer loop assigns in the given `loop`. Loop assigns should depend
213+
/// on some identifiers in `candidate_targets`. `function_assigns_map`
214+
/// contains the function contracts used to infer loop assigns of
215+
/// function_call instructions.
216+
static assignst dfcc_infer_loop_assigns_for_loop(
158217
const local_may_aliast &local_may_alias,
159218
goto_functiont &goto_function,
160219
const dfcc_loop_nesting_graph_nodet &loop,
220+
const std::unordered_set<irep_idt> &candidate_targets,
161221
message_handlert &message_handler,
162222
const namespacet &ns)
163223
{
@@ -176,6 +236,12 @@ assignst dfcc_infer_loop_assigns(
176236
assignst result;
177237
for(const auto &expr : assigns)
178238
{
239+
// Skip targets that only depend on non-visible identifiers.
240+
if(!depends_on(expr, candidate_targets))
241+
{
242+
continue;
243+
}
244+
179245
if(depends_on(expr, loop_locals))
180246
{
181247
// Target depends on loop locals, attempt widening to the root object
@@ -210,3 +276,103 @@ assignst dfcc_infer_loop_assigns(
210276

211277
return result;
212278
}
279+
280+
/// Remove assignments to `__CPROVER_dead_object` to avoid aliasing all targets
281+
/// that are assigned to `__CPROVER_dead_object`.
282+
static void remove_dead_object_assignment(goto_functiont &goto_function)
283+
{
284+
Forall_goto_program_instructions(i_it, goto_function.body)
285+
{
286+
if(i_it->is_assign())
287+
{
288+
auto &lhs = i_it->assign_lhs();
289+
290+
if(
291+
lhs.id() == ID_symbol &&
292+
to_symbol_expr(lhs).get_identifier() == CPROVER_PREFIX "dead_object")
293+
{
294+
i_it->turn_into_skip();
295+
}
296+
}
297+
}
298+
}
299+
300+
void dfcc_infer_loop_assigns_for_function(
301+
std::map<std::size_t, assignst> &inferred_loop_assigns_map,
302+
goto_functionst &goto_functions,
303+
const goto_functiont &goto_function,
304+
message_handlert &message_handler,
305+
const namespacet &ns)
306+
{
307+
messaget log(message_handler);
308+
309+
// Collect all candidate targets---identifiers visible in `goto_function`.
310+
const auto candidate_targets = find_symbol_identifiers(goto_function.body);
311+
312+
// We infer loop assigns based on the copy of `goto_function`.
313+
goto_functiont goto_function_copy;
314+
goto_function_copy.copy_from(goto_function);
315+
316+
// Build the loop id map before inlining attempt. So that we can later
317+
// distinguish loops in the original body and loops added by inlining.
318+
const auto loop_nesting_graph =
319+
build_loop_nesting_graph(goto_function_copy.body);
320+
auto topsorted = loop_nesting_graph.topsort();
321+
// skip function without loop.
322+
if(topsorted.empty())
323+
return;
324+
325+
// Map from targett in `goto_function_copy` to loop number.
326+
std::
327+
unordered_map<goto_programt::const_targett, std::size_t, const_target_hash>
328+
loop_number_map;
329+
330+
for(const auto id : topsorted)
331+
{
332+
loop_number_map.emplace(
333+
loop_nesting_graph[id].head, loop_nesting_graph[id].latch->loop_number);
334+
}
335+
336+
// We avoid inlining `malloc` and `free` whose variables are not assigns.
337+
auto malloc_body = goto_functions.function_map.extract(irep_idt("malloc"));
338+
auto free_body = goto_functions.function_map.extract(irep_idt("free"));
339+
340+
// Inline all function calls in goto_function_copy.
341+
goto_program_inline(
342+
goto_functions, goto_function_copy.body, ns, log.get_message_handler());
343+
// Update the body to make sure all goto correctly jump to valid targets.
344+
goto_function_copy.body.update();
345+
// Build the loop graph after inlining.
346+
const auto inlined_loop_nesting_graph =
347+
build_loop_nesting_graph(goto_function_copy.body);
348+
349+
// Alias analysis.
350+
remove_dead_object_assignment(goto_function_copy);
351+
local_may_aliast local_may_alias(goto_function_copy);
352+
353+
auto inlined_topsorted = inlined_loop_nesting_graph.topsort();
354+
355+
for(const auto inlined_id : inlined_topsorted)
356+
{
357+
// We only infer loop assigns for loops in the original function.
358+
if(
359+
loop_number_map.find(inlined_loop_nesting_graph[inlined_id].head) !=
360+
loop_number_map.end())
361+
{
362+
const auto loop_number =
363+
loop_number_map[inlined_loop_nesting_graph[inlined_id].head];
364+
const auto inlined_loop = inlined_loop_nesting_graph[inlined_id];
365+
366+
inferred_loop_assigns_map[loop_number] = dfcc_infer_loop_assigns_for_loop(
367+
local_may_alias,
368+
goto_function_copy,
369+
inlined_loop,
370+
candidate_targets,
371+
message_handler,
372+
ns);
373+
}
374+
}
375+
// Restore the function boyd of `malloc` and `free`.
376+
goto_functions.function_map.insert(std::move(malloc_body));
377+
goto_functions.function_map.insert(std::move(free_body));
378+
}

0 commit comments

Comments
 (0)