-
Notifications
You must be signed in to change notification settings - Fork 274
Fix havocking of dependent assigns clause targets #6540
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
Fix havocking of dependent assigns clause targets #6540
Conversation
ee4780c
to
5ac6ab4
Compare
Codecov Report
@@ Coverage Diff @@
## develop #6540 +/- ##
=========================================
Coverage 75.99% 75.99%
=========================================
Files 1578 1579 +1
Lines 180944 181103 +159
=========================================
+ Hits 137500 137621 +121
- Misses 43444 43482 +38
Continue to review full report at Codecov.
|
5ac6ab4
to
ea923be
Compare
b789494
to
d6aa06a
Compare
const auto &id = parameter.id(); | ||
if( | ||
parameter.id() == ID_dereference || parameter.id() == ID_member || | ||
parameter.id() == ID_symbol || parameter.id() == ID_ptrmember) | ||
id == ID_dereference || id == ID_member || id == ID_symbol || | ||
id == ID_ptrmember || id == ID_constant || id == ID_typecast) | ||
{ |
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 a test that demonstrates the need for this?
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 tests added in this PR will fail without this.
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.
added specific tests
@@ -3,7 +3,7 @@ main.c | |||
--enforce-contract foo --replace-call-with-contract bar _ --pointer-primitive-check | |||
^EXIT=10$ | |||
^SIGNAL=0$ | |||
\[foo.\d+\] line \d+ Check that \*z is assignable: FAILURE$ | |||
\[foo.\d+\] line \d+ Check that \*z assigned by replaced bar is assignable: FAILURE$ |
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'm struggling to parse this - should it perhaps say "replaced function bar?"
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.
How about Check that \*z is assignable (function bar replacement):
?
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.
yes
#include <util/namespace.h> | ||
#include <util/symbol_table.h> |
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.
Forward declarations (class namespacet;
, class symbol_tablet;
) will do the trick here. (This is to reduce compilation times where possible.)
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.
fixed
#ifndef CPROVER_GOTO_INSTRUMENT_CONTRACTS_HAVOC_ASSIGNS_CLAUSE_TARGETS_H | ||
#define CPROVER_GOTO_INSTRUMENT_CONTRACTS_HAVOC_ASSIGNS_CLAUSE_TARGETS_H | ||
|
||
#include <set> |
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.
This isn't used here.
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.
fixed
|
||
#include <set> | ||
|
||
#include <goto-programs/goto_program.h> |
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 think you'll only need #include <util/expr.h>
, everything else can be forward-declared.
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.
indeed this works
const auto &size = size_of_expr(target.type(), ns); | ||
|
||
INVARIANT( | ||
size.has_value(), |
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.
Please move this into the INVARIANT
to avoid a situation where size
ends up as an unused value (when completely disabling invariants). That is: INVARIANT(size_of_expr(target.type(), ns).has_value(), ...)
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.
fixed
"`sizeof` must always be computable on assignable assigns clause " | ||
"targets."); | ||
|
||
return address_of_exprt{target, pointer_type(target.type())}; |
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.
Wouldn't is_assignable
be true for something like a[5]
, but taking the address thereof would not actually be the "base address" (or at least not how I'd understand that term).
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.
This computes the start address of the thing that gets assigned. If contract assigns a[5]
the base address of the thing that gets assigned is &(a[5])
. If the contract says it assigns __CPROVER_pointer_object(a[5])
then the base address will be the address pointed to by a-5
.
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'd then find something along the lines of "build_address_of" a more natural function name, but this might be a matter of personal preference.
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.
As a side, note that address_of_exprt
has a convenience constructor that takes just the object, i.e., the above is equivalent to address_of_exprt{target}
. The constructor will create the pointer type from the type of target
.
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.
renamed to build_address_of
dest.add(goto_programt::make_decl(target_valid_var)); | ||
|
||
dest.add(goto_programt::make_decl(target_snapshot_var)); | ||
|
||
dest.add(goto_programt::make_assignment( | ||
target_valid_var, all_dereferences_are_valid(target_pointer, ns))); | ||
|
||
dest.add(goto_programt::make_assignment( | ||
target_snapshot_var, | ||
null_pointer_exprt{to_pointer_type(target_pointer.type())})); |
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.
For debugging purposes it would be helpful if all these had source locations that relate them in some way to the location a snapshot is taken at. Hmm, you're doing this after the fact. That's a bit confusing, in my opinion.
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.
fixed
goto_programt skip_program; | ||
const auto skip_target = skip_program.add(goto_programt::make_skip()); | ||
|
||
dest.add(goto_programt::make_goto(skip_target, not_exprt{target_valid_var})); | ||
|
||
dest.add(goto_programt::make_assignment(target_snapshot_var, target_pointer)); | ||
|
||
dest.destructive_append(skip_program); |
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.
You could use insert_before_swap
to avoid the skip_program
. But then it probably doesn't make a whole lot of a difference.
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.
it seems less natural, since after inserting the skip statement in dest, you would have to use a mix of insert_before_swap and skip_target++ or to insert instructions in reverse, or to create another program and insert_before_swap and then ++ the skip_target
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.
Ok!
codet code(ID_havoc_object); | ||
code.add_to_operands(target_snapshot_var); |
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.
codet code(ID_havoc_object, {target_snapshot_var});
would avoid the two-step construction.
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.
fixed
d6aa06a
to
ecb5269
Compare
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.
Only minor comments.
@@ -3,7 +3,7 @@ main.c | |||
--enforce-contract foo --replace-call-with-contract bar _ --pointer-primitive-check | |||
^EXIT=10$ | |||
^SIGNAL=0$ | |||
\[foo.\d+\] line \d+ Check that \*z is assignable: FAILURE$ | |||
\[foo.\d+\] line \d+ Check that \*z assigned by replaced bar is assignable: FAILURE$ |
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.
How about Check that \*z is assignable (function bar replacement):
?
// clang-format off | ||
__CPROVER_requires( | ||
__CPROVER_is_fresh(v, sizeof(vect)) && | ||
0 < v->size && v->size <= __CPROVER_max_malloc_size && |
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.
Why do you need to check whether v->size
is smaller than __CPROVER_max_malloc_size
? Haven't this problem been solved (see #6130)?
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.
Whatever the behaviour of CBMC is when this bound is exceeded, I want to avoid it in this example.
havoc_assigns_targetst havoc_gen(assigns, ns); | ||
havoc_gen.append_full_havoc_code(location, havoc_instructions); | ||
// Havoc all targets in the assigns clause | ||
// TODO handle local statics possibly touched by this function |
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 a issue to track this?
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.
yes
// Havoc all targets in the assigns clause | ||
// TODO handle local statics possibly touched by this function |
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.
nit:
// Havoc all targets in the assigns clause | |
// TODO handle local statics possibly touched by this function | |
// Havoc all targets in the assigns clause. | |
// TODO: handle local statics possibly touched by this function. |
^SIGNAL=0$ | ||
-- | ||
-- | ||
Verifies the contract being replaced in `replace_resize_vec.desc`. |
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 that the correct file name? I didn't find it.
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.
fixed
// clang-format off | ||
__CPROVER_requires( | ||
__CPROVER_is_fresh(v, sizeof(vect)) && | ||
0 < v->size && v->size <= __CPROVER_max_malloc_size && |
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.
See #6130.
Hasn't this problem been fixed? Why do you need v->size <= __CPROVER_max_malloc_size
here?
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.
Whatever the behaviour of CBMC is when this bound is exceeded, I want to avoid it in this example.
/// ``` | ||
/// typedef struct vect { int *arr; int size; } vect; | ||
/// void resize(vect *v) | ||
/// __CPOVER_assigns(v->arr, v->size, __CPROVER_POINTER_OBJECT(v->arr)) |
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.
typo: __CPROVER_assigns
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.
fixed
3ee6728
to
40e8121
Compare
…eturn_value` variables introduced by contract replacement (these would previously appear as global variables).
40e8121
to
4e5adcf
Compare
auto res = minus_exprt{ptr, pointer_offset(ptr)}; | ||
res.type() = ptr.type(); | ||
return std::move(res); |
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.
Scratch my earlier comment, the following will actually do the right thing: return minus_exprt{ptr, pointer_offset(ptr)};
(the type of the minus_exprt
will be ptr.type()
when constructed in this way).
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.
thanks
that works when there are dependencies between targets. We now take a snapshot of the target addresses if they are valid and havoc them in a second step if they are valid. Previously we were havocking targets directly in an arbitrary sequence, and havocking s->ptr before *(s->ptr) before __CPROVER_POINTER_OBJECT(s->ptr) would cause spurious errors.
4e5adcf
to
22c020b
Compare
I took into account all pending comments, it should be good to go now. |
Requires approval from @peterschrammel @martin-cs @chrisr-diffblue as a new file was added to |
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.
It needs code owner approval from @peterschrammel @chrisr-diffblue or @martin-cs.
@@ -0,0 +1,15 @@ | |||
#include <stdio.h> |
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.
Why do we need this include?
@@ -0,0 +1,23 @@ | |||
#include <stdio.h> |
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.
Why do we need this include?
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.
Approving the Makefile changes. Nothing to add beyond the other detailed reviews.
The first commit fixes a minor issue with history variables (allow history variables for constant and typecast expressions).
The second commit fixes missing declarations for return value variables introduced during replacement of function calls by contracts.
The third commit fixes a problem with assigns clause havocking when replacing calls by contracts.
When replacing a function call by its contract, we would directly havoc the targets of the assigns clause in an unspecified order. If the assigns clause contained dependent targets such as
assigns(s->ptr, *(s->ptr))
, havockings->ptr
before*s->ptr
would lead to errors and incorrect results.We now introduce independent snapshot variables storing the address of each target and havoc them in a second time to avoid this dependency on the ordering of havoc instructions.