Skip to content

Commit

Permalink
Support for Kani Loop Contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
qinheping committed Apr 23, 2024
1 parent 1ca18f2 commit af195ba
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 6 deletions.
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 @@ Date: February 2016
#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 @@ void code_contractst::check_apply_loop_contracts(
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 @@ void code_contractst::check_apply_loop_contracts(
.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 @@ void code_contractst::check_apply_loop_contracts(
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 @@ void code_contractst::check_apply_loop_contracts(
// 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 @@ void code_contractst::check_apply_loop_contracts(
// 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 @@ void code_contractst::apply_loop_contract(
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 @@ void code_contractst::apply_loop_contract(
{
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 @@ void code_contractst::apply_loop_contract(
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 @@ void code_contractst::apply_loop_contract(
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 @@ void code_contractst::apply_loop_contract(
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 @@ void code_contractst::apply_loop_contract(
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 @@ void code_contractst::apply_loop_contract(
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

0 comments on commit af195ba

Please sign in to comment.