-
Notifications
You must be signed in to change notification settings - Fork 274
CONTRACTS: Hotfix for is_fresh performance #7425
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: Hotfix for is_fresh performance #7425
Conversation
@@ -11,8 +11,8 @@ void resize_vec(vect *v, size_t incr) | |||
// 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.
IMHO, It seems counter-productive to ask users to add an extra assumption in their contracts to make sure the size will be smaller than the maximum malloc size for CBMC. This is outside of the scope of this PR, but we should have a separate discussion why this is necessary in general.
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 with Felipe on this one. Within CBMC if it is fresh it must also be within that size because malloc can only produce things of that size without failing, so I think world would be a better place if we just moved this into the is_fresh predicate - it can return false if the size is too big.
Also, then when we change the memory model we don't have to change all the assumptions in proofs to get rid of this.
Codecov ReportBase: 78.39% // Head: 78.38% // Decreases project coverage by
Additional details and impacted files@@ Coverage Diff @@
## develop #7425 +/- ##
===========================================
- Coverage 78.39% 78.38% -0.02%
===========================================
Files 1655 1655
Lines 190281 190304 +23
===========================================
- Hits 149172 149166 -6
- Misses 41109 41138 +29
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report at Codecov. |
@@ -11,8 +11,8 @@ void resize_vec(vect *v, size_t incr) | |||
// 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.
I'm with Felipe on this one. Within CBMC if it is fresh it must also be within that size because malloc can only produce things of that size without failing, so I think world would be a better place if we just moved this into the is_fresh predicate - it can return false if the size is too big.
Also, then when we change the memory model we don't have to change all the assumptions in proofs to get rid of this.
Agreed, but moving this check inside the predicate thereby adding a control flow branch is what caused the drastic performance degradation we observed. With CBMC the pointer model is also leaking its implementation details: Right now, without malloc failure modes, CBMC lets you allocate up to SIZE_MAX but trying to access it will result in a OOB failure: #include <stdlib.h>
int main()
{
size_t size;
char *a = malloc(size);
if(a && size > 0) {
assert(size <= __CPROVER_max_malloc_size);
a[size-1] = 0;
}
} ❯ cbmc toto.c --pointer-check
** Results:
<builtin-library-malloc> function malloc
[malloc.assertion.1] line 28 max allocation size exceeded: SUCCESS
[malloc.assertion.2] line 33 max allocation may fail: SUCCESS
toto.c function main
[main.assertion.1] line 8 assertion size <= __CPROVER_max_malloc_size: FAILURE
[main.pointer_dereference.1] line 9 dereference failure: pointer NULL in a[(signed long int)(size - (unsigned long int)1)]: SUCCESS
[main.pointer_dereference.2] line 9 dereference failure: pointer invalid in a[(signed long int)(size - (unsigned long int)1)]: SUCCESS
[main.pointer_dereference.3] line 9 dereference failure: deallocated dynamic object in a[(signed long int)(size - (unsigned long int)1)]: SUCCESS
[main.pointer_dereference.4] line 9 dereference failure: dead object in a[(signed long int)(size - (unsigned long int)1)]: SUCCESS
[main.pointer_dereference.5] line 9 dereference failure: pointer outside object bounds in a[(signed long int)(size - (unsigned long int)1)]: FAILURE
[main.pointer_dereference.6] line 9 dereference failure: invalid integer address in a[(signed long int)(size - (unsigned long int)1)]: SUCCESS
** 2 of 9 failed (2 iterations)
VERIFICATION FAILED Activating the ❯ cbmc toto.c --pointer-check --malloc-may-fail --malloc-fail-null
** Results:
<builtin-library-malloc> function malloc
[malloc.assertion.1] line 28 max allocation size exceeded: SUCCESS
[malloc.assertion.2] line 33 max allocation may fail: SUCCESS
toto.c function main
[main.assertion.1] line 8 assertion size <= __CPROVER_max_malloc_size: SUCCESS
[main.pointer_dereference.1] line 9 dereference failure: pointer NULL in a[(signed long int)(size - (unsigned long int)1)]: SUCCESS
[main.pointer_dereference.2] line 9 dereference failure: pointer invalid in a[(signed long int)(size - (unsigned long int)1)]: SUCCESS
[main.pointer_dereference.3] line 9 dereference failure: deallocated dynamic object in a[(signed long int)(size - (unsigned long int)1)]: SUCCESS
[main.pointer_dereference.4] line 9 dereference failure: dead object in a[(signed long int)(size - (unsigned long int)1)]: SUCCESS
[main.pointer_dereference.5] line 9 dereference failure: pointer outside object bounds in a[(signed long int)(size - (unsigned long int)1)]: SUCCESS
[main.pointer_dereference.6] line 9 dereference failure: invalid integer address in a[(signed long int)(size - (unsigned long int)1)]: SUCCESS
** 0 of 9 failed (1 iterations)
VERIFICATION SUCCESSFUL Last, the This is the behaviour I implemented for
EDIT: after thinking about it I could actually replicate these three behaviours in |
ec3be0f
to
cb543d5
Compare
The latest update solves the performance issue and proper handling of malloc failure modes to mirror that of malloc with respect to size limits. In particular, when using |
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.
Thank you for addressing the issue with __CPROVER_max_malloc_size
.
|
||
1. **No failure mode** (no flags): | ||
In this mode, `malloc` and `__CPROVER_is_fresh` never fail | ||
and will accept a size parameter up to `SIZE_MAX` without triggerring errors. |
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.
and will accept a size parameter up to `SIZE_MAX` without triggerring errors. | |
and will accept a size parameter up to `SIZE_MAX` without triggering errors. |
|
||
// record the object size for non-determistic bounds checking | ||
__CPROVER_bool record_malloc = __VERIFIER_nondet___CPROVER_bool(); | ||
__CPROVER_malloc_is_new_array = |
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've tested your new approach in some contracts and I get the following warning:
file <builtin-library-free> line 10: symbol '__CPROVER_malloc_is_new_array' already has an initial value
Is it something that could be suppressed? Does it affect the analysis?
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.
With #6590 having been merged I'd suggest to use __CPROVER_bool __CPROVER_malloc_is_new_array = 0;
instead of the extern
declaration.
What Felipe said! Thanks Remi. |
I have created #7426 to track the (likely unrelated) CSmith failure. |
// detect memory leaks | ||
__CPROVER_bool record_may_leak = __VERIFIER_nondet___CPROVER_bool(); | ||
__CPROVER_memory_leak = record_may_leak ? ptr : __CPROVER_memory_leak; |
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.
Should uses of is_fresh
really participate in memory-leak checking?
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_fresh
allocates when assumed
- in a requires clause of a contract being checked;
- should not participate in leak checking because case 1. models what's provided by the open environmnent of the contract being checked.
- in an ensures clause of a contract being used to abstract a call.
- should participate in leak checking because it models an allocation happening in the user program, not the environment
Does that seem reasonable ?
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.
Sounds good to me.
|
||
// record the object size for non-determistic bounds checking | ||
__CPROVER_bool record_malloc = __VERIFIER_nondet___CPROVER_bool(); | ||
__CPROVER_malloc_is_new_array = |
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.
With #6590 having been merged I'd suggest to use __CPROVER_bool __CPROVER_malloc_is_new_array = 0;
instead of the extern
declaration.
@@ -129,6 +129,38 @@ int foo() | |||
} | |||
``` | |||
|
|||
#### Influence of memory allocation failure modes flags in assumption contexts | |||
|
|||
CBMC offers three memory allocation failure modes which result in different |
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.
Cross-ref to doc/cprover-manual/memory-primitives.md
?
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
@tautschnig Should I also initialise other extern variables declared in extern __CPROVER_size_t __CPROVER_max_malloc_size;
extern const void *__CPROVER_alloca_object;
extern const void *__CPROVER_deallocated;
extern const void *__CPROVER_new_object;
extern const void *__CPROVER_memory_leak; |
Yes, with the exception of |
Solves a major performance issue and aligns the behavior of is_fresh with that of malloc with respect to size assertions/assumptions. malloc is now inlined in the body of `__CPROVER_contract_is_fresh` and the failure logic is simplified in assumption contexts.
cb543d5
to
3a1a5ac
Compare
This is a hotfix for a performance regression introduced by control flow branches and malloc failure modes.
Solves a major performance issue and aligns the
behavior of is_fresh with that of malloc with
respect to size assertions/assumptions.
malloc is now inlined in the body of
__CPROVER_contract_is_fresh
and thefailure logic is simplified in assumption
contexts.