-
Notifications
You must be signed in to change notification settings - Fork 274
Fix max malloc size #5298
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 max malloc size #5298
Conversation
68adf73
to
86e4944
Compare
86e4944
to
9a4c18e
Compare
max_malloc_size(std::size_t pointer_width, std::size_t object_bits) | ||
{ | ||
PRECONDITION(pointer_width >= 1); | ||
PRECONDITION(object_bits < pointer_width - 1); |
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 this should be object_bits < pointer_width
. Just one bit for the offset would still be ok.
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 not: you need one bit for the sign?
Pointer width = 8
Object bits = 7
1-1 = 0 bits for the offset?
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.
Just saw second comment - OK I misunderstood what you said - I thought the signed bit was used for something else, rather than the offset?
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 offset is signed. So with just one bit, 0 (in binary) would be 0 (in decimal), and 1 (in binary) would be -1 (in decimal).
PRECONDITION(pointer_width >= 1); | ||
PRECONDITION(object_bits < pointer_width - 1); | ||
PRECONDITION(object_bits >= 1); | ||
const auto bits_for_offset = pointer_width - object_bits - 1; |
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 naming is not quite right as the bits for the offset is pointer_width - object_bits
. The offset is signed and the sign bit is considered to be part of the offset.
@@ -120,6 +122,25 @@ static std::string architecture_string(T value, const char *s) | |||
std::string(s) + "=" + std::to_string(value) + ";\n"; | |||
} | |||
|
|||
using max_alloc_sizet = uint64_t; |
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.
Shouldn't this be size_t
? That's the type with which __CPROVER_max_malloc_size
is 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.
Yes this makes sense!
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.
Though the advantage of using a platform agnostic width is writing the unit tests is easier
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.
@danpoe The problem is that cbmcs size_t
that we analyze with isn't necessarily the same as that which we get when compiling cbmc itself.
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 pointed out by @thk123 in private communication, when using size_t
instead of uint64_t
, the computation would overflow when running on a 32-bit machine and using e.g. --LLP64
.
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.
Could we please use mp_integer
here instead of any architecturally limited type? We do not want to run into limitations of the host or compiler with this computation.
PRECONDITION(object_bits >= 1); | ||
const auto bits_for_offset = pointer_width - object_bits - 1; | ||
PRECONDITION(bits_for_offset < std::numeric_limits<max_alloc_sizet>::digits); | ||
return ((max_alloc_sizet)1) << (max_alloc_sizet)bits_for_offset; |
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 casting bits_for_offset
would be unnecessary.
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 prefer to be explicit here, since the specific rules of what type gets promoted are fiddly.
bfc4e00
to
566b57c
Compare
/// are left in the pointer of width \p pointer_width. | ||
/// | ||
/// The offset needs to be able to represent up to the allocation_size, and | ||
/// down to -allocation_size, hence 2^(pointer_width - object_bits - 1) |
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 the description has it backwards. The allocation size is a result of how we represent the offset, and not the other way round.
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 my head - they are simply intertwined via the following relationship
max_alloc_size = 2^(bits_for_offset - 1)
That said I didn't like how the function returns max_alloc_size, but the description implies you input the max allocation size. I just found this a clearer way to think about the relationship - I'll have another go.
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 that makes sense. The current description looks good to me. My reasoning for why I preferred offset -> allocation size was that this is the direction of the computation in the method.
PRECONDITION(object_bits >= 1); | ||
const auto offset_bits = pointer_width - object_bits; | ||
// We require the offset to be able to express upto allocation_size - 1, | ||
// but also down to -allocation_size + 1, therefore the size is allowable |
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 this be just -allocation_size
? Since we can represent one more negative value than positive values.
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 you're right - a pointer to the end of the memory would be valid, and you'd need to subtract the full allocation size to get back to the start.
Pulled out into a fnuction and assert that the max allocation size does not exceed 64 bits. Do not attempt to do the left shift in a 32 bit int, instead cast to 64 bits before doing the shift.
8789d7e
to
9438b49
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.
Thank you for fixing the overflow bug, but as said in my comment I believe that the arithmetic should be done on mp_integer
.
@@ -120,6 +122,25 @@ static std::string architecture_string(T value, const char *s) | |||
std::string(s) + "=" + std::to_string(value) + ";\n"; | |||
} | |||
|
|||
using max_alloc_sizet = uint64_t; |
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.
Could we please use mp_integer
here instead of any architecturally limited type? We do not want to run into limitations of the host or compiler with this computation.
unit/ansi-c/max_malloc_size.cpp
Outdated
\*******************************************************************/ | ||
|
||
#include <ansi-c/ansi_c_internal_additions.cpp> | ||
#include <iostream> |
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 this include actually needed?
Ensure that invalid numbers of bits for object id throws, as well as any overflow if the max allocation size is too big.
This ensures the other preconditions don't pass spuriously with a zero width pointer
This is valid - the offset can be 0 for a pointer pointing at the start of a single byte allocation.
This removes the possibility for the calculation itself to overflow
9438b49
to
448ff24
Compare
@tautschnig done (which of course removes the possibility of any overflow). Will merge when passes CI / tomorrow |
Previously, to work out the max allocation size, we we working out how many bits there were for the offset (
pointer_width - object_bits - 1
) and then raising 2 to that power. However, we were doing this with int literals, and so the calculation was done in 32bits.Therefore, for the default object bits (8) gives you:
1 << (64 - 8 - 1)
Which depends on your compiler / OS might give you zero, or
8388608
(since overflow on signed integral types is undefined behavior, both are valid). The correct number is36028797018963968
This PR changes to use a 64 bit type on all platforms, and invariant if the result is not going to fit.
Also adds unit tests showing this, and documentation explaining the calculations.