Skip to content

CONTRACTS: Add enumerative loop invariant synthesizer #7393

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

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
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
CORE
main.c
--synthesize-loop-invariants --apply-loop-contracts
--synthesize-loop-invariants
^EXIT=0$
^SIGNAL=0$
^\[main\.\d+\] line 10 Check loop invariant before entry: SUCCESS$
Expand Down
17 changes: 17 additions & 0 deletions regression/contracts/loop_contract_synthesis_02/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#define SIZE 80

void main()
{
unsigned len;
__CPROVER_assume(len <= SIZE);
__CPROVER_assume(len >= 8);
char *array = malloc(len);
unsigned s = 0;

for(unsigned i = 0; i < SIZE; ++i)
{
if(i == len - 1)
break;
s += array[i];
}
}
11 changes: 11 additions & 0 deletions regression/contracts/loop_contract_synthesis_02/test.desc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
CORE
main.c
--pointer-check --synthesize-loop-invariants
^EXIT=0$
^SIGNAL=0$
^\[main.pointer\_dereference.\d+\] .* SUCCESS$
^VERIFICATION SUCCESSFUL$
--
--
This test shows that loop invariant with form of range predicates can be correctly
synthesized for programs with only pointer checks but no other assertions.
Comment on lines +10 to +11
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what happens when there are additional assertions?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The synthesizer tries to solve violation one by one. If there are other violations beyond pointer checks and invariant checks, it will throw an exception saying the type of violation is unsupported. My plan is focusing on pointer checks in this PR, and create another PR to add the enumerating-and-check mode that keeps enumerating until all checks pass for other type of violations.

16 changes: 16 additions & 0 deletions regression/contracts/loop_contract_synthesis_03/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#define SIZE 80

void main()
{
unsigned long len;
__CPROVER_assume(len <= SIZE);
__CPROVER_assume(len >= 8);
char *array = malloc(len);
const char *end = array + len;
unsigned s = 0;

while(array != end)
{
s += *array++;
}
}
12 changes: 12 additions & 0 deletions regression/contracts/loop_contract_synthesis_03/test.desc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
CORE
main.c
--pointer-check --synthesize-loop-invariants
^EXIT=0$
^SIGNAL=0$
^\[main.pointer\_dereference.\d+\] .* SUCCESS$
^VERIFICATION SUCCESSFUL$
--
--
This test shows that loop invariant with form of range predicates and same
object predicates can be correctly synthesized for programs with only pointer
checks but no other assertions.
17 changes: 17 additions & 0 deletions regression/contracts/loop_contract_synthesis_04/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#define SIZE 80

void main()
{
unsigned long len;
__CPROVER_assume(len <= SIZE);
__CPROVER_assume(len >= 8);
char *array = malloc(len);
unsigned long s = 0;

unsigned long j = 0;
for(unsigned long i = 0; i < len; i++)
{
s += array[j];
j++;
}
}
11 changes: 11 additions & 0 deletions regression/contracts/loop_contract_synthesis_04/test.desc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
CORE
main.c
--pointer-check --synthesize-loop-invariants
^EXIT=0$
^SIGNAL=0$
^\[main.pointer\_dereference.\d+\] .* SUCCESS$
^VERIFICATION SUCCESSFUL$
--
--
This test shows that the loop-invariant synthesizer can enumerate
strengthening clauses for invariant-not-preserved violation.
3 changes: 2 additions & 1 deletion src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ goto-harness.dir: util.dir goto-programs.dir langapi.dir linking.dir \
goto-instrument.dir

goto-instrument.dir: languages goto-programs.dir pointer-analysis.dir \
goto-symex.dir linking.dir analyses.dir solvers.dir
goto-symex.dir linking.dir analyses.dir solvers.dir \
goto-checker.dir

goto-checker.dir: solvers.dir goto-symex.dir goto-programs.dir

Expand Down
47 changes: 47 additions & 0 deletions src/analyses/dependence_graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,53 @@ void dependence_grapht::add_dep(
nodes[n_to].in[n_from].add(kind);
}

bool dependence_grapht::is_flow_dependent(
const goto_programt::const_targett &from,
const goto_programt::const_targett &to)
{
std::set<node_indext> visited = std::set<node_indext>();
const dep_graph_domaint from_domain = static_cast<const dep_graph_domaint &>(
*storage->abstract_state_before(from, *domain_factory));
const dep_graph_domaint to_domain = static_cast<const dep_graph_domaint &>(
*storage->abstract_state_before(to, *domain_factory));
return is_flow_dependent(from_domain, to_domain, visited);
}

bool dependence_grapht::is_flow_dependent(
const dep_graph_domaint &from,
const dep_graph_domaint &to,
std::set<node_indext> &visited)
{
// `to` is control dependent on `from`?
for(const auto node : to.get_control_deps())
{
if(visited.count((*this)[node].get_node_id()))
continue;

visited.insert((*this)[node].get_node_id());

if(
from.get_node_id() == (*this)[node].get_node_id() ||
is_flow_dependent(from, (*this)[node], visited))
return true;
}

// `to` is data dependent on `from`?
for(const auto node : to.get_data_deps())
{
if(visited.count((*this)[node].get_node_id()))
continue;

visited.insert((*this)[node].get_node_id());

if(
from.get_node_id() == (*this)[node].get_node_id() ||
is_flow_dependent(from, (*this)[node], visited))
return true;
}
return false;
}

void dep_graph_domaint::populate_dep_graph(
dependence_grapht &dep_graph, goto_programt::const_targett this_loc) const
{
Expand Down
21 changes: 19 additions & 2 deletions src/analyses/dependence_graph.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class dep_graph_domaint:public ai_domain_baset
{
public:
typedef grapht<dep_nodet>::node_indext node_indext;
typedef std::set<goto_programt::const_targett> depst;

explicit dep_graph_domaint(node_indext id)
: has_values(false), node_id(id), has_changed(false)
Expand Down Expand Up @@ -167,13 +168,20 @@ class dep_graph_domaint:public ai_domain_baset
void populate_dep_graph(
dependence_grapht &, goto_programt::const_targett) const;

depst get_control_deps() const
{
return control_deps;
}
depst get_data_deps() const
{
return data_deps;
}

private:
tvt has_values;
node_indext node_id;
bool has_changed;

typedef std::set<goto_programt::const_targett> depst;

// Set of locations with control instructions on which the instruction at this
// location has a control dependency on
depst control_deps;
Expand Down Expand Up @@ -279,7 +287,16 @@ class dependence_grapht:
return rd;
}

/// Decide whether the instruction `to` is flow dependent on `from`.
bool is_flow_dependent(
const goto_programt::const_targett &from,
const goto_programt::const_targett &to);

protected:
bool is_flow_dependent(
const dep_graph_domaint &from,
const dep_graph_domaint &to,
std::set<node_indext> &visited);
friend dep_graph_domain_factoryt;
friend dep_graph_domaint;
const namespacet &ns;
Expand Down
1 change: 1 addition & 0 deletions src/goto-instrument/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ target_link_libraries(goto-instrument-lib
linking
big-int
goto-programs
goto-checker
goto-symex
assembler
pointer-analysis
Expand Down
2 changes: 2 additions & 0 deletions src/goto-instrument/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ SRC = accelerate/accelerate.cpp \
splice_call.cpp \
stack_depth.cpp \
synthesizer/enumerative_loop_invariant_synthesizer.cpp \
synthesizer/cegis_verifier.cpp \
synthesizer/expr_enumerator.cpp \
synthesizer/synthesizer_utils.cpp \
thread_instrumentation.cpp \
Expand All @@ -108,6 +109,7 @@ OBJ += ../ansi-c/ansi-c$(LIBEXT) \
../cpp/cpp$(LIBEXT) \
../linking/linking$(LIBEXT) \
../big-int/big-int$(LIBEXT) \
../goto-checker/goto-checker$(LIBEXT) \
../goto-programs/goto-programs$(LIBEXT) \
../goto-symex/goto-symex$(LIBEXT) \
../assembler/assembler$(LIBEXT) \
Expand Down
10 changes: 8 additions & 2 deletions src/goto-instrument/contracts/contracts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ void code_contractst::check_apply_loop_contracts(
// i.e., the loop guard was satisfied.
const auto entered_loop =
new_tmp_symbol(
bool_typet(), loop_head_location, mode, symbol_table, "__entered_loop")
bool_typet(), loop_head_location, mode, symbol_table, ENTERED_LOOP)
.symbol_expr();
pre_loop_head_instrs.add(
goto_programt::make_decl(entered_loop, loop_head_location));
Expand Down Expand Up @@ -173,7 +173,7 @@ void code_contractst::check_apply_loop_contracts(
// instrumentation of the loop.
const auto in_base_case =
new_tmp_symbol(
bool_typet(), loop_head_location, mode, symbol_table, "__in_base_case")
bool_typet(), loop_head_location, mode, symbol_table, IN_BASE_CASE)
.symbol_expr();
pre_loop_head_instrs.add(
goto_programt::make_decl(in_base_case, loop_head_location));
Expand Down Expand Up @@ -294,6 +294,12 @@ void code_contractst::check_apply_loop_contracts(
// Generate havocing code for assignment targets.
havoc_assigns_targetst havoc_gen(to_havoc, ns);
havoc_gen.append_full_havoc_code(loop_head_location, pre_loop_head_instrs);
// Add loop number to the havoc_code
for(auto &havoc_instr : pre_loop_head_instrs.instructions)
{
if(is_loop_havoc(havoc_instr))
havoc_instr.loop_number = loop_end->loop_number;
}

// Insert the second block of pre_loop_head_instrs: the havocing code.
// We do not `add_pragma_disable_assigns_check`,
Expand Down
33 changes: 33 additions & 0 deletions src/goto-instrument/contracts/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -447,3 +447,36 @@ void generate_history_variables_initialization(
// Add all the history variable initialization instructions
program.destructive_append(history);
}

bool is_transformed_loop_end(const goto_programt::const_targett &target)
{
// The end of the loop end of transformed loop is
// ASSIGN entered_loop = true
if(!target->is_assign())
return false;

return from_expr(target->assign_lhs()).find("__entered_loop") !=
std::string::npos &&
target->assign_rhs() == true_exprt();
}

bool is_assignment_to_instrumented_variable(
const goto_programt::const_targett &target,
std::string var_name)
{
INVARIANT(
var_name == IN_BASE_CASE || var_name == ENTERED_LOOP,
"var_name is not of instrumented variables.");

if(!target->is_assign())
return false;

if(can_cast_expr<symbol_exprt>(target->assign_lhs()))
{
const auto &lhs = to_symbol_expr(target->assign_lhs());
return id2string(lhs.get_identifier()).find("::" + var_name) !=
std::string::npos;
}

return false;
}
12 changes: 12 additions & 0 deletions src/goto-instrument/contracts/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ Date: September 2021

#include <goto-programs/goto_convert_class.h>

#define IN_BASE_CASE "__in_base_case"
#define ENTERED_LOOP "__entered_loop"

/// \brief A class that overrides the low-level havocing functions in the base
/// utility class, to havoc only when expressions point to valid memory,
/// i.e. if all dereferences within an expression are valid
Expand Down Expand Up @@ -205,4 +208,13 @@ void generate_history_variables_initialization(
const irep_idt &mode,
goto_programt &program);

/// Return true if `target` is the loop end of some transformed code.
bool is_transformed_loop_end(const goto_programt::const_targett &target);

/// Return true if `target` is an assignment to an instrumented variable with
/// name `var_name`.
bool is_assignment_to_instrumented_variable(
const goto_programt::const_targett &target,
std::string var_name);

#endif // CPROVER_GOTO_INSTRUMENT_CONTRACTS_UTILS_H
33 changes: 23 additions & 10 deletions src/goto-instrument/goto_instrument_parse_options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1148,16 +1148,6 @@ void goto_instrument_parse_optionst::instrument_goto_program()
goto_model.goto_functions.update();
}

if(cmdline.isset("synthesize-loop-invariants"))
{
log.warning() << "Loop invariant synthesizer is still work in progress. "
"It only generates TRUE as invariants."
<< messaget::eom;

// Synthesize loop invariants and annotate them into `goto_model`
enumerative_loop_invariant_synthesizert synthesizer(goto_model, log);
annotate_invariants(synthesizer.synthesize_all(), goto_model, log);
}

bool use_dfcc = cmdline.isset(FLAG_DFCC);
if(use_dfcc)
Expand Down Expand Up @@ -1432,6 +1422,29 @@ void goto_instrument_parse_optionst::instrument_goto_program()
goto_check_c(options, goto_model, ui_message_handler);
transform_assertions_assumptions(options, goto_model);

if(cmdline.isset(FLAG_SYNTHESIZE_LOOP_INVARIANTS))
{
if(cmdline.isset(FLAG_LOOP_CONTRACTS))
{
throw invalid_command_line_argument_exceptiont(
"Incompatible options detected",
"--" FLAG_SYNTHESIZE_LOOP_INVARIANTS " and --" FLAG_LOOP_CONTRACTS,
"Use either --" FLAG_SYNTHESIZE_LOOP_INVARIANTS
" or --" FLAG_LOOP_CONTRACTS);
}

// Synthesize loop invariants and annotate them into `goto_model`
enumerative_loop_invariant_synthesizert synthesizer(goto_model, log);
annotate_invariants(synthesizer.synthesize_all(), goto_model);

// Apply loop contracts.
std::set<std::string> to_exclude_from_nondet_static(
cmdline.get_values("nondet-static-exclude").begin(),
cmdline.get_values("nondet-static-exclude").end());
code_contractst contracts(goto_model, log);
contracts.apply_loop_contracts(to_exclude_from_nondet_static);
}

// check for uninitalized local variables
if(cmdline.isset("uninitialized-check"))
{
Expand Down
Loading