-
Notifications
You must be signed in to change notification settings - Fork 274
Add support for conversion of pointer arithmetic expressions to new SMT backend. #6866
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
Add support for conversion of pointer arithmetic expressions to new SMT backend. #6866
Conversation
Codecov Report
@@ Coverage Diff @@
## develop #6866 +/- ##
===========================================
+ Coverage 77.80% 77.81% +0.01%
===========================================
Files 1567 1568 +1
Lines 179916 180023 +107
===========================================
+ Hits 139988 140093 +105
- Misses 39928 39930 +2
Continue to review full report at Codecov.
|
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.
Some initial comments.
auto pointer_base_type = pointer_type.base_type(); | ||
exprt pointer_size_expr; | ||
// There's a special case for a pointer subtype here: the case where the pointer is `void *`. This means | ||
// that we don't know the underlying base type, so we're just assigning a size expression value of 1 (given |
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.
⛏️ void *
does not mean that "we don't know the underlying base type". It means the type is not defined or "is empty". For example the return type of malloc
is void *
and the memory to which the returned pointer refers to will have no type associated. Your explanation of why we are using 1 explains how this affects the maths which we are using but not why (or why not) it is the correct thing to do. Critically, pointer arithmetic on void pointers is disallowed by the C standard because void does not have a size. Using a size of 1 is actually a GCC extension. Source references - http://www.c-faq.com/ansi/voidparith.html and https://gcc.gnu.org/onlinedocs/gcc-4.8.0/gcc/Pointer-Arith.html
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 is still unaddressed.
|
||
#include <unordered_map> | ||
|
||
using pointer_size_mapt = std::unordered_map<typet, smt_termt, irep_full_hash>; |
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 thought we agreed to call this a type_size_map
rather than a pointer size map? The keys of the map are expected to be the types the pointers point to, not the pointers themselves or the whole pointer type. I don't want to tie the naming of this map to pointers only, because we may want the type size information for other purposes as we add more functionality to the new decision procedure.
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.
Addressed in the refactor commits.
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 you rename the file to match as well please?
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.
Done.
/// expression argument. | ||
/// \param expression: the expression we're building the map for. | ||
/// \param ns: a namespace - passed to size_of_expr to find expression sizes. | ||
/// \param pointer_size_map: the map containing the pointer.base_type() -> size (in bytes) mappings. |
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.
⛏️ Its worth stating that this function adds the mappings to this map. Your current document doesn't really explain that this is an "output" parameter.
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.
Addressed in 658672c
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 am not sure what you mean by "Initially empty, by the function.". This function does not empty the map and the map may or may not contain existing entries when it is called. I suggest -
/// \param type_size_map:
/// A map of types to terms expressing the size of the type (in bytes). This
/// function adds new entries to the map for instances of pointer.base_type()
/// from \p expression which are not already keys in the map.
/// Establish pointer-sizes map for all pointers present in the | ||
/// expression argument. | ||
/// \param expression: the expression we're building the map for. | ||
/// \param ns: a namespace - passed to size_of_expr to find expression sizes. |
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.
⛏️ Its worth noting that the name space is needed for looking up (following) type symbols in the case where pointers have tag_typet
, rather than a more completely defined type. Ideally the size_of_expr
would have doxygen as well, but I am happy to consider that to be "off topic" for this PR.
38bbc27
to
41f27a9
Compare
This PR is now actively being worked on, and includes a small number of commits that are going to be squashed away or spun out into their own PRs. |
dc7a300
to
ec40a7b
Compare
|
||
using type_size_mapt = std::unordered_map<typet, smt_termt, irep_full_hash>; | ||
|
||
/// This function creates a map of types to their related sizes (in bytes). |
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 documentation is inaccurate as the function adds to the map rather than creating/constructing it. The map is (empty) constructed before this function is called.
/// expression argument. | ||
/// \param expression: the expression we're building the map for. | ||
/// \param ns: a namespace - passed to size_of_expr to find expression sizes. | ||
/// \param pointer_size_map: the map containing the pointer.base_type() -> size (in bytes) mappings. |
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 am not sure what you mean by "Initially empty, by the function.". This function does not empty the map and the map may or may not contain existing entries when it is called. I suggest -
/// \param type_size_map:
/// A map of types to terms expressing the size of the type (in bytes). This
/// function adds new entries to the map for instances of pointer.base_type()
/// from \p expression which are not already keys in the map.
|
||
pointer_typet pointer_type = | ||
*type_try_dynamic_cast<pointer_typet>(pointer.type()); | ||
auto base_type = pointer_type.base_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.
⛏️ I would probably inline pointer_type.base_type()
into the call to at
. But this should at least be const
if you are going to introduce a new variable which is not mutated.
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.
All of the related definitions have been const
ed.
@@ -0,0 +1,13 @@ | |||
#include <stdint.h> | |||
|
|||
#define NULL (void *)0 |
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 would prefer that one of the standard library definitions of NULL
was used instead of defining it in the test, in order to avoid re-definition errors.
{ | ||
int *x; | ||
int *y; | ||
int *z = x - y; |
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.
z
should be of int
type rather than int *
type. The result of x - y
is an integer not a pointer, so because you declared z
as a pointer then the integer result of the subtraction is being implicitly cast back to a pointer. Because you have assumed that x != y
the property that z != 0
should hold in a subsequent assertion.
SECTION("Addition of a pointer and a constant") | ||
{ | ||
// (int32_t *)a + 2 | ||
const auto pointer_arith_expr = plus_exprt{pointer_a, two_bvint_32bit}; |
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 suggest using catchs INFO
macro to add information of the pretty printing of pointer_arith_expr
, so that more information is provided if this test fails in future.
const auto constructed_term = | ||
test.convert(minus_exprt{pointer_b, pointer_a}); | ||
const auto expected_term = | ||
smt_bit_vector_theoryt::subtract(smt_term_b, smt_term_a); |
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 looks to be missing the division of the subtraction result by the 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 think this is fixed in a following commit?
{ | ||
int *x = malloc(sizeof(int)); | ||
float *y = x + 3; | ||
int z = y - x; |
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 subtraction is actually non-compiling code if I run it through gcc
. If I run cbmc with the sat backend then this code is currently accepted and analysed. As this is not valid code it should really be detected in the C type checking performed as part of the C front end. Adding such front-end checks would be outside of the scope of this PR however.
6c7c885
to
26f5a62
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.
Looks like the serious issues have all been resolved.
⛏️ It could do with a regression test to check on the result of subtracting two pointers is as we expect in the case where the resulting integer is negative. That is because this is the case where the kind of division used is important. The wrong kind of division would result in a value of the wrong sign and far larger than expected.
26f5a62
to
c27ba48
Compare
c27ba48
to
a441756
Compare
…MT backend. This will be needed later to support pointer arithmetic.
This works without any changes to our conversion of `minus_exprt`s because there's a transformation between the frontend and the backend that converts a (*a - 3) to (*a + (-3)).
a441756
to
34b2985
Compare
This PR adds the capability to handle expressions like the following:
to the new SMT backend, along with regression tests and unit tests.