Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CONTRACTS] Support for Kani Loop Contracts #8260

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 69 additions & 2 deletions src/goto-instrument/contracts/contracts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "instrument_spec_assigns.h"
#include "memory_predicates.h"
#include "utils.h"
#include "utils_kani.h"

#include <algorithm>
#include <map>
Expand All @@ -53,6 +54,7 @@
goto_programt::targett loop_head,
goto_programt::targett loop_end,
const loopt &loop,
const goto_programt::instructionst &eval_ins,
exprt assigns_clause,
exprt invariant,
exprt decreases_clause,
Expand Down Expand Up @@ -165,6 +167,15 @@
.symbol_expr();
pre_loop_head_instrs.add(
goto_programt::make_decl(initial_invariant_val, loop_head_location));

// instrument instructions to evaluate invariants before assert(inv)
// or assume(inv)

Check warning on line 172 in src/goto-instrument/contracts/contracts.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/contracts.cpp#L172

Added line #L172 was not covered by tests
for(const auto &in : eval_ins)
{
auto new_in = goto_programt::instructiont(in);
pre_loop_head_instrs.add(std::move(new_in));

Check warning on line 176 in src/goto-instrument/contracts/contracts.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/contracts.cpp#L175-L176

Added lines #L175 - L176 were not covered by tests
}

{
// Although the invariant at this point will not have side effects,
// it is still a C expression, and needs to be "goto_convert"ed.
Expand Down Expand Up @@ -326,6 +337,14 @@
pre_loop_head_instrs.add(
goto_programt::make_assignment(in_loop_havoc_block, false_exprt{}));

// instrument instructions to evaluate invariants before assert(inv)
// or assume(inv)

Check warning on line 341 in src/goto-instrument/contracts/contracts.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/contracts.cpp#L340-L341

Added lines #L340 - L341 were not covered by tests
for(const auto &in : eval_ins)
{
auto new_in = goto_programt::instructiont(in);
pre_loop_head_instrs.add(std::move(new_in));

Check warning on line 345 in src/goto-instrument/contracts/contracts.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/contracts.cpp#L344-L345

Added lines #L344 - L345 were not covered by tests
}

// Insert the second block of pre_loop_head_instrs: the havocing code.
// We do not `add_pragma_disable_assigns_check`,
// so that the enclosing scope's assigns clause instrumentation
Expand All @@ -336,6 +355,13 @@
// We use a block scope for assumption, since it's immediately goto converted,
// and doesn't need to be kept around.
{
// instrument instructions to evaluate invariants before assert(inv)
// or assume(inv)

Check warning on line 359 in src/goto-instrument/contracts/contracts.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/contracts.cpp#L358-L359

Added lines #L358 - L359 were not covered by tests
for(const auto &in : eval_ins)
{
auto new_in = goto_programt::instructiont(in);
pre_loop_head_instrs.add(std::move(new_in));

Check warning on line 363 in src/goto-instrument/contracts/contracts.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/contracts.cpp#L362-L363

Added lines #L362 - L363 were not covered by tests
}
code_assumet assumption{invariant};
assumption.add_source_location() = loop_head_location;
converter.goto_convert(assumption, pre_loop_head_instrs, mode);
Expand Down Expand Up @@ -437,6 +463,13 @@
// We use a block scope for assertion, since it's immediately goto converted,
// and doesn't need to be kept around.
{
// instrument instructions to evaluate invariants before assert(inv)

Check warning on line 466 in src/goto-instrument/contracts/contracts.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/contracts.cpp#L466

Added line #L466 was not covered by tests
// or assume(inv)
for(const auto &in : eval_ins)
{
auto new_in = goto_programt::instructiont(in);
pre_loop_end_instrs.add(std::move(new_in));

Check warning on line 471 in src/goto-instrument/contracts/contracts.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/contracts.cpp#L470-L471

Added lines #L470 - L471 were not covered by tests
}
code_assertt assertion{invariant};
assertion.add_source_location() = loop_head_location;
assertion.add_source_location().set_comment(
Expand Down Expand Up @@ -839,8 +872,15 @@
return;

inlining_decoratort decorated(log.get_message_handler());
goto_function_inline(
goto_functions, function_name, ns, log.get_message_handler());

// For kani loop contracts, we postponed the inlining after
// get the loop invariants as they are a sequence of instructions
// rather than only an expression

Check warning on line 878 in src/goto-instrument/contracts/contracts.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/contracts.cpp#L878

Added line #L878 was not covered by tests
if(!apply_kani_loop_contracts)
{
goto_function_inline(
goto_functions, function_name, ns, log.get_message_handler());
}

INVARIANT(
decorated.get_recursive_call_set().size() == 0,
Expand All @@ -866,18 +906,21 @@
{
const typename natural_loops_mutablet::loopt &content;
const goto_programt::targett head_target, end_target;
goto_programt::instructionst eval_instrs;
exprt assigns_clause, invariant, decreases_clause;

loop_graph_nodet(
const typename natural_loops_mutablet::loopt &loop,
const goto_programt::targett head,
const goto_programt::targett end,
const goto_programt::instructionst ins,

Check warning on line 916 in src/goto-instrument/contracts/contracts.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/contracts.cpp#L916

Added line #L916 was not covered by tests
const exprt &assigns,
const exprt &inv,
const exprt &decreases)
: content(loop),
head_target(head),
end_target(end),
eval_instrs(ins),
assigns_clause(assigns),
invariant(inv),
decreases_clause(decreases)
Expand All @@ -894,6 +937,12 @@
goto_programt::target_less_than>
loop_head_ends;

// Preprocess loops from kani
if(apply_kani_loop_contracts)
{
annotate_kani_loop_invariants(goto_function.body, log);

Check warning on line 943 in src/goto-instrument/contracts/contracts.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/contracts.cpp#L943

Added line #L943 was not covered by tests
}

for(const auto &loop_head_and_content : natural_loops.loop_map)
{
const auto &loop_body = loop_head_and_content.second;
Expand Down Expand Up @@ -966,13 +1015,22 @@
goto_function.body, loop_head, goto_programt::make_skip());
loop_end->set_target(loop_head);

goto_programt::instructionst eval_instrs;

exprt assigns_clause =
static_cast<const exprt &>(loop_end->condition().find(ID_C_spec_assigns));
exprt invariant = static_cast<const exprt &>(
loop_end->condition().find(ID_C_spec_loop_invariant));
exprt decreases_clause = static_cast<const exprt &>(
loop_end->condition().find(ID_C_spec_decreases));

// get kani loop invariants and the evaluation instructions.

Check warning on line 1027 in src/goto-instrument/contracts/contracts.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/contracts.cpp#L1027

Added line #L1027 was not covered by tests
if(apply_kani_loop_contracts)
{
get_kani_invariants(
goto_function, loop_head, invariant.operands(), eval_instrs);

Check warning on line 1031 in src/goto-instrument/contracts/contracts.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/contracts.cpp#L1030-L1031

Added lines #L1030 - L1031 were not covered by tests
}

if(invariant.is_nil())
{
if(decreases_clause.is_not_nil() || assigns_clause.is_not_nil())
Expand Down Expand Up @@ -1005,6 +1063,7 @@
loop_head_end.second.second,
loop_head,
loop_end,
eval_instrs,

Check warning on line 1066 in src/goto-instrument/contracts/contracts.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/contracts.cpp#L1066

Added line #L1066 was not covered by tests
assigns_clause,
invariant,
decreases_clause);
Expand All @@ -1017,6 +1076,13 @@
to_check_contracts_on_children.push_back(idx);
}

// The postponed inlining
if(apply_kani_loop_contracts)
{
goto_function_inline(
goto_functions, function_name, ns, log.get_message_handler());

Check warning on line 1083 in src/goto-instrument/contracts/contracts.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/contracts.cpp#L1082-L1083

Added lines #L1082 - L1083 were not covered by tests
}

for(size_t outer = 0; outer < loop_nesting_graph.size(); ++outer)
{
for(size_t inner = 0; inner < loop_nesting_graph.size(); ++inner)
Expand Down Expand Up @@ -1104,6 +1170,7 @@
loop_node.head_target,
loop_node.end_target,
updated_loops.loop_map[loop_node.head_target],
loop_node.eval_instrs,
loop_node.assigns_clause,
loop_node.invariant,
loop_node.decreases_clause,
Expand Down
8 changes: 8 additions & 0 deletions src/goto-instrument/contracts/contracts.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ Date: February 2016
#define HELP_LOOP_CONTRACTS \
" {y--apply-loop-contracts} \t check and use loop contracts when provided\n"

#define FLAG_KANI_LOOP_CONTRACTS "apply-kani-loop-contracts"
#define HELP_KANI_LOOP_CONTRACTS \
" {y--apply-kani-loop-contracts} \t check loop contracts from kani\n"

#define FLAG_LOOP_CONTRACTS_NO_UNWIND "loop-contracts-no-unwind"
#define HELP_LOOP_CONTRACTS_NO_UNWIND \
" {y--loop-contracts-no-unwind} \t do not unwind transformed loops\n"
Expand Down Expand Up @@ -122,6 +126,7 @@ class code_contractst
goto_programt::targett loop_head,
goto_programt::targett loop_end,
const loopt &loop,
const goto_programt::instructionst &eval_ins,
exprt assigns_clause,
exprt invariant,
exprt decreases_clause,
Expand All @@ -144,6 +149,9 @@ class code_contractst
// Unwind transformed loops after applying loop contracts or not.
bool unwind_transformed_loops = true;

// Unwind transformed loops after applying loop contracts or not.
bool apply_kani_loop_contracts = false;

protected:
goto_modelt &goto_model;
symbol_tablet &symbol_table;
Expand Down
156 changes: 156 additions & 0 deletions src/goto-instrument/contracts/utils_kani.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*******************************************************************\

Module: Utility functions for Kani code contracts.

Author: Qinheping Hu, qinhh@amazon.com

\*******************************************************************/

#include "utils_kani.h"

#include <analyses/natural_loops.h>

bool is_kani_loop_invariant(const goto_programt::instructiont instr)

Check warning on line 13 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L13

Added line #L13 was not covered by tests
{
if(!instr.is_function_call())
return false;

auto s = id2string(to_symbol_expr(instr.call_function()).get_identifier());
return (s.find("kani_loop_invariant") != std::string::npos) &&
(s.find("kani_loop_invariant_") == std::string::npos);

Check warning on line 20 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L18-L20

Added lines #L18 - L20 were not covered by tests
}

bool is_kani_loop_invariant_begin(const goto_programt::instructiont instr)

Check warning on line 23 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L23

Added line #L23 was not covered by tests
{
if(!instr.is_function_call())
return false;

auto s = id2string(to_symbol_expr(instr.call_function()).get_identifier());
return s.find("kani_loop_invariant_begin") != std::string::npos;

Check warning on line 29 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L28-L29

Added lines #L28 - L29 were not covered by tests
}

bool is_kani_loop_invariant_end(const goto_programt::instructiont instr)

Check warning on line 32 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L32

Added line #L32 was not covered by tests
{
if(!instr.is_function_call())
return false;

auto s = id2string(to_symbol_expr(instr.call_function()).get_identifier());
return s.find("kani_loop_invariant_end") != std::string::npos;

Check warning on line 38 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L37-L38

Added lines #L37 - L38 were not covered by tests
}

void annotate_kani_loop_invariants(goto_programt &body, messaget &log)

Check warning on line 41 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L41

Added line #L41 was not covered by tests
{
// The place holder function call
// kani_loop_invariant(cond)
// indicate its successor loop latch contains loop invariants.
// So for each such function call, we find the loop end for it and
// annotate loop contracts to the loop end.
// Note that here we only annotate 'true' to indicate it contains
// loop contracts. And will later get the loop contracts with another pass.

natural_loops_mutablet natural_loops(body);
std::set<goto_programt::targett, goto_programt::target_less_than>
loop_latches;

Check warning on line 53 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L51-L53

Added lines #L51 - L53 were not covered by tests

// Collect all latches
for(const auto &loop_head_and_content : natural_loops.loop_map)
{
const auto &loop_content = loop_head_and_content.second;

Check warning on line 58 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L58

Added line #L58 was not covered by tests
// Skip empty loops and self-looped node.
if(loop_content.size() <= 1)
continue;

Check warning on line 61 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L61

Added line #L61 was not covered by tests

auto loop_head = loop_head_and_content.first;
auto loop_end = loop_head;

Check warning on line 64 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L63-L64

Added lines #L63 - L64 were not covered by tests

// Find the last back edge to `loop_head`
for(const auto &t : loop_content)
{
if(
t->is_goto() && t->get_target() == loop_head &&
t->location_number > loop_end->location_number)
loop_end = t;

Check warning on line 72 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L69-L72

Added lines #L69 - L72 were not covered by tests
}

if(loop_end == loop_head)
{
log.error() << "Could not find end of the loop starting at: "
<< loop_head->source_location() << messaget::eom;
throw 0;

Check warning on line 79 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L77-L79

Added lines #L77 - L79 were not covered by tests
}
loop_latches.insert(loop_end);

Check warning on line 81 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L81

Added line #L81 was not covered by tests
}

// Go through all instructions in the body to find the placeholder
// kani_loop_invariant()
for(auto it = body.instructions.begin(); it != body.instructions.end(); it++)
{
if(is_kani_loop_invariant(*it))
{
auto temp_it = it;
while(!loop_latches.count(temp_it))

Check warning on line 91 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L91

Added line #L91 was not covered by tests
{
temp_it = body.get_successors(temp_it).front();

Check warning on line 93 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L93

Added line #L93 was not covered by tests
}

auto op = and_exprt(true_exprt(), true_exprt());

Check warning on line 96 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L96

Added line #L96 was not covered by tests

temp_it->condition_nonconst().add(ID_C_spec_loop_invariant).swap(op);

Check warning on line 98 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L98

Added line #L98 was not covered by tests
}
}
}

void get_kani_invariants(

Check warning on line 103 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L103

Added line #L103 was not covered by tests
const goto_functiont &goto_function,
const goto_programt::const_targett &loop_head,
exprt::operandst &invariant_clauses,
goto_programt::instructionst &eval_instructions)
{
// The loop invariants in kani goto are passed as the instructions
// right before the loop head, and between two placeholder function
// calls kani_loop_invariant_begin() and kani_loop_invariant_end().
auto &body = goto_function.body;

Check warning on line 112 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L112

Added line #L112 was not covered by tests

for(auto it = body.instructions.begin(); it != body.instructions.end(); it++)

Check warning on line 114 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L114

Added line #L114 was not covered by tests
{
// first find the loop head
if(it == loop_head)

Check warning on line 117 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L117

Added line #L117 was not covered by tests
{
// skip irrelevant instructions until we see the placeholder
while(!is_kani_loop_invariant_end(*it))

Check warning on line 120 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L120

Added line #L120 was not covered by tests
{
it--;

Check warning on line 122 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L122

Added line #L122 was not covered by tests
}
// skip the placeholder
it--;

Check warning on line 125 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L125

Added line #L125 was not covered by tests

// copy and store all instructions between the two placeholders
while(!is_kani_loop_invariant_begin(*it))

Check warning on line 128 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L128

Added line #L128 was not covered by tests
{
// except for goto and dead
// because here the goto is not a real goto, but just goto the next
// instruction.
// Dead instructions are for those temporary variables and can be
// safely ignored here.
if(!it->is_goto() && !it->is_dead())

Check warning on line 135 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L135

Added line #L135 was not covered by tests
{
auto new_instruction = goto_programt::instructiont(*it);
new_instruction.target_number =

Check warning on line 138 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L137-L138

Added lines #L137 - L138 were not covered by tests
goto_programt::instructiont::nil_target;
eval_instructions.push_front(new_instruction);

Check warning on line 140 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L140

Added line #L140 was not covered by tests
}

// The only function call assign the evaluated result to a boolean
// variable. We will use the result variable as loop invariants.
if(it->is_function_call())

Check warning on line 145 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L145

Added line #L145 was not covered by tests
{
invariant_clauses.push_back(
typecast_exprt(it->call_lhs(), bool_typet()));

Check warning on line 148 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L147-L148

Added lines #L147 - L148 were not covered by tests
}
it--;

Check warning on line 150 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L150

Added line #L150 was not covered by tests
}
return;

Check warning on line 152 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L152

Added line #L152 was not covered by tests
}
}
UNREACHABLE;

Check warning on line 155 in src/goto-instrument/contracts/utils_kani.cpp

View check run for this annotation

Codecov / codecov/patch

src/goto-instrument/contracts/utils_kani.cpp#L155

Added line #L155 was not covered by tests
}
Loading
Loading