-
Notifications
You must be signed in to change notification settings - Fork 263
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] Detect loop locals with goto_rw in DFCC #8489
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ void main() | |
x1 = &z1; | ||
|
||
while(y1 > 0) | ||
__CPROVER_loop_invariant(*x1 == __CPROVER_loop_entry(*x1)) | ||
__CPROVER_loop_invariant(y1 >= 0 && *x1 == __CPROVER_loop_entry(*x1)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this (and the other changes in this file) now necessary? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is for the same reason why we add |
||
{ | ||
--y1; | ||
*x1 = *x1 + 1; | ||
|
@@ -24,7 +24,7 @@ void main() | |
x2 = z2; | ||
|
||
while(y2 > 0) | ||
__CPROVER_loop_invariant(x2 == __CPROVER_loop_entry(x2)) | ||
__CPROVER_loop_invariant(y2 >= 0 && x2 == __CPROVER_loop_entry(x2)) | ||
{ | ||
--y2; | ||
x2 = x2 + 1; | ||
|
@@ -34,11 +34,12 @@ void main() | |
|
||
int y3; | ||
s s0, s1, *s2 = &s0; | ||
s0.n = malloc(sizeof(int)); | ||
s2->n = malloc(sizeof(int)); | ||
s1.n = s2->n; | ||
|
||
while(y3 > 0) | ||
__CPROVER_loop_invariant(s2->n == __CPROVER_loop_entry(s2->n)) | ||
__CPROVER_loop_invariant(y3 >= 0 && s2->n == __CPROVER_loop_entry(s2->n)) | ||
{ | ||
--y3; | ||
s0.n = s0.n + 1; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ Author: Remi Delmas, delmasrd@amazon.com | |
#include <util/pointer_expr.h> | ||
#include <util/std_code.h> | ||
|
||
#include <analyses/goto_rw.h> | ||
#include <goto-instrument/contracts/utils.h> | ||
#include <goto-instrument/havoc_utils.h> | ||
|
||
|
@@ -46,23 +47,127 @@ depends_on(const exprt &expr, std::unordered_set<irep_idt> identifiers) | |
return false; | ||
} | ||
|
||
/// Collect identifiers that are local to this loop. | ||
/// A identifier is or is equivalent to a loop local if | ||
/// 1. it is declared inside the loop, or | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is that a sufficient condition? Don't we also need to have it marked There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We want to identify identifiers that are equivalent to loop locals in the sense that DFCC can safely ignore them for havocing and checking assignability. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I think you might just not really care about declarations very much and instead focus on all reads/writes being within the set of instructions that make up the loop body. |
||
/// 2. there is no write or read of it outside the loop. | ||
/// 3. it is not used in loop contracts. | ||
std::unordered_set<irep_idt> gen_loop_locals_set( | ||
const irep_idt &function_id, | ||
goto_functiont &goto_function, | ||
const dfcc_loop_nesting_graph_nodet &loop_node, | ||
message_handlert &message_handler, | ||
const namespacet &ns) | ||
{ | ||
std::unordered_set<irep_idt> loop_locals; | ||
std::unordered_set<irep_idt> non_loop_locals; | ||
|
||
const auto &loop = loop_node.instructions; | ||
|
||
// All identifiers declared outside the loop. | ||
std::unordered_set<irep_idt> non_loop_decls; | ||
// Ranges of all read/write outside the loop. | ||
rw_range_sett non_loop_rw_range_set(ns, message_handler); | ||
|
||
Forall_goto_program_instructions(i_it, goto_function.body) | ||
{ | ||
// All variables declared in loops are loop locals. | ||
if(i_it->is_decl() && loop.contains(i_it)) | ||
{ | ||
loop_locals.insert(i_it->decl_symbol().get_identifier()); | ||
} | ||
// Record all other declared variables and their ranges. | ||
else if(i_it->is_decl()) | ||
{ | ||
non_loop_decls.insert(i_it->decl_symbol().get_identifier()); | ||
} | ||
// Record all writing/reading outside the loop. | ||
else if( | ||
(i_it->is_assign() || i_it->is_function_call()) && !loop.contains(i_it)) | ||
{ | ||
goto_rw(function_id, i_it, non_loop_rw_range_set); | ||
} | ||
} | ||
|
||
// Check if declared variables are loop locals. | ||
for(const auto &decl_id : non_loop_decls) | ||
{ | ||
bool is_loop_local = true; | ||
// No write to the declared variable. | ||
for(const auto &writing_rw : non_loop_rw_range_set.get_w_set()) | ||
{ | ||
if(decl_id == writing_rw.first) | ||
{ | ||
is_loop_local = false; | ||
break; | ||
} | ||
} | ||
|
||
// No read to the declared variable. | ||
for(const auto &writing_rw : non_loop_rw_range_set.get_r_set()) | ||
{ | ||
if(decl_id == writing_rw.first) | ||
{ | ||
is_loop_local = false; | ||
break; | ||
} | ||
} | ||
|
||
const auto latch_target = loop_node.latch; | ||
|
||
// Loop locals are not used in loop contracts. | ||
for(const auto &id : | ||
find_symbol_identifiers(get_loop_assigns(latch_target))) | ||
{ | ||
if(decl_id == id) | ||
{ | ||
is_loop_local = false; | ||
break; | ||
} | ||
} | ||
|
||
for(const auto &id : | ||
find_symbol_identifiers(get_loop_invariants(latch_target, false))) | ||
{ | ||
if(decl_id == id) | ||
{ | ||
is_loop_local = false; | ||
break; | ||
} | ||
} | ||
|
||
for(const auto &id : | ||
find_symbol_identifiers(get_loop_decreases(latch_target, false))) | ||
{ | ||
if(decl_id == id) | ||
{ | ||
is_loop_local = false; | ||
break; | ||
} | ||
} | ||
|
||
// Collect all loop locals. | ||
if(is_loop_local) | ||
loop_locals.insert(decl_id); | ||
} | ||
|
||
return loop_locals; | ||
} | ||
|
||
assignst dfcc_infer_loop_assigns( | ||
const local_may_aliast &local_may_alias, | ||
const loopt &loop_instructions, | ||
const source_locationt &loop_head_location, | ||
goto_functiont &goto_function, | ||
const dfcc_loop_nesting_graph_nodet &loop, | ||
message_handlert &message_handler, | ||
const namespacet &ns) | ||
{ | ||
// infer | ||
assignst assigns; | ||
infer_loop_assigns(local_may_alias, loop_instructions, assigns); | ||
infer_loop_assigns(local_may_alias, loop.instructions, assigns); | ||
|
||
// compute locals | ||
std::unordered_set<irep_idt> loop_locals; | ||
for(const auto &target : loop_instructions) | ||
{ | ||
if(target->is_decl()) | ||
loop_locals.insert(target->decl_symbol().get_identifier()); | ||
} | ||
std::unordered_set<irep_idt> loop_locals = | ||
gen_loop_locals_set(irep_idt(), goto_function, loop, message_handler, ns); | ||
|
||
// widen or drop targets that depend on loop-locals or are non-constant, | ||
// ie. depend on other locations assigned by the loop. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please elaborate as to why this is newly necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason is that if we only declare variables but not initialize them or use them outside the loop, they will be identified as loop locals by the new algorithm. And hence DFCC will not check if they are assignable according to the assigns clauses.
I think the issue here is that in C, declaring a variable also initializes them with a nondet value. And we usually just use declarations in harness for deterministically initializing variables. To avoid being identified as loop locals and ignored by DFCC, I explicitly initialize them with nondet.
Shouldn't a C declaration be converted to a GOTO DECL and a GOTO ASSIGN with nondet?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there anything wrong with taking this view? That is, if a variable is declared outside the loop yet never read outside the loop then it is indistinguishable from one that is declared inside the loop, I'd say?!
One could do that, and I have made such an attempt in #35. There just wasn't an obvious benefit to doing so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In C program, it is distinguishable. Because declaration is also a havocing in C. In the tests I updated in this PR, they expect the variables that are only declared outside the loop but no read/write outside the loop to be checked by DFCC inside the loop. That's why I have to explicitly assign to them to avoid them being identified as loop locals.
I think adding a condition that a variable should also not be read before first write in the loop can make them really indistinguishable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, the issue here is that also they are not used outside the loops, but they are used in loop contracts, which will be instrumented as R/W outside the loop. So, a loop local should also not be used in loop contracts.