-
Notifications
You must be signed in to change notification settings - Fork 273
[fix] Constant arrays should not be marked as nondet. #1632
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] Constant arrays should not be marked as nondet. #1632
Conversation
if(sym.type().get_bool(ID_C_constant)) | ||
if(sym.type().get_bool(ID_C_constant) | ||
// if it's an array, we need to check the subtype | ||
|| sym.type().subtype().get_bool(ID_C_constant)) |
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 you include a test for ID_array as part of the new condition? I'd feel happier if you explicitly tested for (pseudo code) sym is array && sym subtype is constant
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.
Also this will require recursing to avoid the same problem for multi-dimensional arrays.
^VERIFICATION SUCCESSFUL$ | ||
g_B = \{ 10, 20, 30 \} | ||
-- | ||
!(g_B = NONDET\(const signed int \[3l\]\);) |
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 pattern correct - in particular the leading !
? I've not written a negative test like this yet so I'm not totally sure of the syntax here. Also, I'd almost be tempted to wildcard match the contents of the NONDET in this pattern e.g. NONDET\(.*\);
or whatever, since the key thing this patch fixes and we want to preserve is that we don't expect to NONDET the constant array. By making the pattern too specific, it could be possible that a bug might introduce a new way of NONDET'ing this that has slightly different values, which then wouldn't trigger this test to fail - by making the NONDET use a broader regex, this test will then catch any bug that causes a NONDET to appear.
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.
Agreed that there's no place for the !
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.
Looks good in principal, but a couple of comments to look at if you could, please :-)
-- | ||
!(g_B = NONDET\(const signed int \[3l\]\);) | ||
-- | ||
This corresponds to [PB-10] - constant arrays being marked as 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.
What is PB-10?
^VERIFICATION SUCCESSFUL$ | ||
g_B = \{ 10, 20, 30 \} | ||
-- | ||
!(g_B = NONDET\(const signed int \[3l\]\);) |
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.
Agreed that there's no place for the !
here.
if(sym.type().get_bool(ID_C_constant)) | ||
if(sym.type().get_bool(ID_C_constant) | ||
// if it's an array, we need to check the subtype | ||
|| sym.type().subtype().get_bool(ID_C_constant)) |
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.
Also this will require recursing to avoid the same problem for multi-dimensional arrays.
4d3ef5d
to
81c2c22
Compare
@@ -0,0 +1,8 @@ | |||
const int g_A = 100; |
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.
Minor nit, feel free to ignore, but now you have extended the tests, is it worth given them slightly better names? e.g. perhaps no_nondet_static_array
and no_nondet_static_multi_array
or something like that?
@@ -21,6 +21,14 @@ Date: November 2011 | |||
|
|||
#include "nondet_static.h" | |||
|
|||
static bool |
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.
Nice 👍 but perhaps rather than making this static and hiding it away in here, would it be better to treat this as a proper utility function and move into one of the util/*
files? Perhaps util/types.cpp/.h
might be a good location as there is already an is_number
utility function in there. Happy if you think there is a better candidate file to move it to though.
g_B = NONDET\(.*\); | ||
-- | ||
This regression covers an issue with constant arrays being marked as 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.
Nit: no need for blank line
@@ -0,0 +1,8 @@ | |||
const int g_A = 100; | |||
const int g_B[]={10, 20, 30}; | |||
int g_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.
To what extent are g_A
and g_var
relevant to this test (or, rather, the fact that they are global)?
|
||
void main(void) | ||
{ | ||
g_var = g_B[g_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 always is undefined behaviour, I don't think that's good practice here.
|
||
void main(void) | ||
{ | ||
g_var = g_B[g_A][g_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.
See above for g_var
, g_A
, and undefined behaviour.
-- | ||
g_B = NONDET\(.*\); | ||
-- | ||
This corresponds to [PB-10] - constant arrays being marked as 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.
As before: what is PB-10
?
continue; | ||
|
||
|
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: newly added blank line seems unnecessary.
Why is this to be merged into diffblue:goto-analyzer-develop? (I guess that's the cause of it failing to build on AppVeyor.) |
I think @NlightNFotis is still addressing comments, but thanks for the extra reviews. This is being merged into diffblue:goto-analyzer-develop because the bug that has spawned this work was reported against that branch, but currently goto-analyzer-develop is quite some number of commits behind develop or master. We have a plan and a rough schedule (pending other work, time for PR reviews, etc) for incrementally bring goto-analyzer-develop closer to develop and then merging across - so this work is intended to land back in develop eventually. |
I'll stick with my changes-requested for the moment as I don't want to be making the same comments when a merge into develop eventually happens. But then obviously you might choose to just hit "Merge" anyhow. |
We'll address your comments in this PR, don't worry :-) |
81c2c22
to
6289a76
Compare
@chrisr-diffblue @tautschnig All your comments have been addressed. Please check to see if you would like to make further comments, as this is very close to what my final solution looks like. |
6289a76
to
3151b7c
Compare
int main(void) | ||
{ | ||
const int constant_array[]={10, 20, 30}; | ||
int var = constant_array[2]; |
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.
Those tests will always have succeeded as the array isn't static?!
src/util/type.cpp
Outdated
|
||
bool is_constant_variable_or_array(const typet &type) | ||
{ | ||
return type.get_bool(ID_C_constant) || |
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 pick: looks like 4-character instead of 2-character indent.
src/util/type.h
Outdated
@@ -203,4 +203,8 @@ pre-defined types: | |||
bool is_number(const typet &type); | |||
// rational, real, integer, complex, unsignedbv, signedbv, floatbv | |||
|
|||
// If a given type is a constant variable (e.g const int) or a constant | |||
// array (e.g. const int []) |
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 word carefully: what does it mean for a type to be a "constant variable"? And: In C, const int []
does not actually declare a constant array, but instead declares an array of constant int
elements. (This, however, implies that the entire object is constant as the size is constant.)
ed18460
to
f31809f
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.
I would suggest the logic should be "if any part of the type has a const qualifier then don't nondet it". Will send some examples.
{ | ||
static const int constant_array[]={10, 20, 30}; | ||
int var = constant_array[2]; | ||
} |
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.
If it would make your life easier (and your test cases more robust), you could make this a CBMC test and add assert(var == 30);
. If the verification succeeds, all is well. If it fails then something has been non-det'd that shouldn't have been.
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.
Now you've added the cbmc regression test, do you still need this goto-instrument regression test?
src/util/type.h
Outdated
@@ -203,4 +203,7 @@ pre-defined types: | |||
bool is_number(const typet &type); | |||
// rational, real, integer, complex, unsignedbv, signedbv, floatbv | |||
|
|||
// Is the passed in type const qualified? | |||
bool is_constant_variable_or_array(const typet &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.
Comment doesn't match the function behaviour or name :-(
@@ -50,7 +51,7 @@ void nondet_static( | |||
continue; | |||
|
|||
// constant? | |||
if(sym.type().get_bool(ID_C_constant)) | |||
if(is_constant_variable_or_array(sym.type())) | |||
continue; |
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 don't think this is quite the right logic because there are a number of cases that it doesn't cover. What about structs? What about unions? What about the difference between const int * and int * const?
[ I always mix the last ones up so I use cdelc
:
cdecl> explain const int *s
declare s as pointer to const int
cdecl> explain const int * const s
declare s as const pointer to const int
cdecl> explain int * const s
declare s as const pointer to int
]
int x;
int y = 23;
const int z = 23;
struct s { int x; };
struct s s1;
struct s s2 = { 23 };
const struct s s3 = { 23 };
struct t { int x; const int y; };
struct t t1;
struct t t2 = { 23, 23 };
const struct t t3 = { 23, 23 };
struct u { const int x; };
struct u u1;
struct u u2 = { 23 };
const struct u u3 = { 23 };
struct contains_pointer { int x; int *p; };
struct contains_constant_pointer { int x; const int *p; };
struct contains_pointer_to_const { int x; int const *p; };
struct contains_pointer a[3] = { {23, &x}, {23, &x}, {23, &x} };
struct contains_constant_pointer b[3] = { {23, &y}, {23, &y}, {23, &y} };
struct contains_pointer_to_const c[3] = { {23, &z}, {23, &z}, {23, &z} };
typedef int Int;
typedef const int Const_Int;
const Int p = 23;
Const_Int q = 23;
#include <assert.h>
int main (int argc, char **argv) {
/* Pass normally but fail with nondet-static */
assert(x == 0);
assert(y == 23);
assert(s1.x == 0);
assert(s2.x == 23);
assert(a[0].x == 23);
assert(a[0].p == &x);
assert(c[2].x == 23);
assert(c[2].p == &x);
/* Should still pass */
assert(z == 23);
assert(s3.x == 23);
assert(t1.y == 0);
assert(t2.y == 23);
assert(t3.x == 23);
assert(t3.y == 23);
assert(u1.x == 0);
assert(u2.x == 23);
assert(u3.x == 23);
assert(b[1].p == &x);
assert(p == 23);
assert(q == 23);
/* Debateable (part of struct is const, part isn't) */
assert(t1.x == 0);
assert(t2.x == 23);
assert(b[1].x == 23);
return 0;
} |
/* Should still pass */ | ||
assert(z == 23); | ||
assert(s3.x == 23); | ||
assert(t1.y == 0); // wrong |
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 what way are these wrong
??? Are you sure these comments are not left over from your own debugging or something?
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.
They are not. They are notes that I had taken while debugging. They are deleted now.
src/util/type.cpp
Outdated
return component.type().get_bool(ID_C_constant); | ||
if (component.type().get_bool(ID_C_constant)) | ||
return true; | ||
} |
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 feels like a large amount of code duplication... and because of the code duplication it makes me wonder a little if the logic is fully right, but I'm not really sure. It looks to me like the first if block is identical to the if (subtype.id() == ID_symbol) {}
block in the second block - so I'd start by factoring that into a helper function. You may then find that what you are left with in this function can be further restructured and simplified...
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 goto-analyser regression tests could probably be dropped as the cbmc regression test is much more comprehensive (thanks for that @martin-cs and @NlightNFotis - its good!) - but I think theres some code duplication that needs cleaning up.
6d7fe0d
to
3311e93
Compare
src/util/type.cpp
Outdated
@@ -34,3 +36,54 @@ bool is_number(const typet &type) | |||
id==ID_floatbv || | |||
id==ID_fixedbv; | |||
} | |||
|
|||
bool is_constant_or_has_constant_components( |
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 add some doxygen comment to this please?
src/util/type.cpp
Outdated
return true; | ||
|
||
// struct t { const int a; }; | ||
// struct t t1; <-- |
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 expand this comment and explain the case a little better?
6b1dadc
to
c2e5a7e
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.
Thanks for all the cleanups and restructuring - looks good to me now.
src/util/type.cpp
Outdated
for(const auto &component : struct_union_type.components()) | ||
{ | ||
if(component.type().id() == ID_pointer) | ||
return component.type().get_bool(ID_C_constant); |
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 return false
early in this case? There could be a constant member?
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.
Ah, good catch!
src/util/type.cpp
Outdated
if(subtype.id() == ID_symbol) | ||
{ | ||
const auto &new_subtype = ns.follow(to_symbol_type(subtype)); | ||
return has_constant_components(new_subtype); |
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 may cause indefinite recursion.
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 assume that this would only be indefinite recursion if the source program had a recursive data structure?
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’ll need to maintain a set of symbols that have been seen already.
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 investigated this a little bit further with recursive data types, and you were right that it caused infinite recursion with the solution as it was. I have modified it to contain a termination clause by checking if its a pointer and if so, checking if it's a const without hunting it in that specific case.
src/util/type.h
Outdated
@@ -11,6 +11,7 @@ Author: Daniel Kroening, [email protected] | |||
#define CPROVER_UTIL_TYPE_H | |||
|
|||
#include <util/source_location.h> | |||
#include "namespace.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.
Please use a forward declaration here.
eb6a1be
to
ccb8700
Compare
struct list { const int datum; struct list * next; }; | ||
|
||
// The point of this is that cbmc needs to correctly handle | ||
// the embedded datum int the nested struct which is const. |
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 comment isn't quite true. The issue is not with the datum field, its with the recursive reference to the containing structure - i.e. next
.
ccb8700
to
e7e2d1c
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.
Thanks for expanding the test case further and fixing up the comments. I'm slightly wondering about multi-dimensional arrays, but I think this is probably ok, and I'm happy for this to be merged into goto-analyzer-develop as it is.
I'm happy enough to add this to goto-analyzer-develop. I'm going to leave this here so we can get @tautschnig 's concluding thoughts; then it would be nice to get it merged to master as well. |
Can this maybe just be re-targetted to develop? All I'd ask for is squashing commits. |
@tautschnig Thanks for the super-prompt response. @NlightNFotis is just squashing, I will merge and then he will submit a PR to develop as well and let's merge that one too! |
e7e2d1c
to
6bc0d15
Compare
27cbf2f
to
a2198cc
Compare
a2198cc
to
9b1a75e
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 good, and I'm happy for this to go in now.
Happy with the code but am still not sure why we are getting CI failures. Any ideas @NlightNFotis ? |
My understanding is that the failures are only on a job that runs Alpine linux. This job doesn't run on newer branches, and I'm told that Alpine linux uses a different libc and is basically always going to fail. If you look at travis builds for other PR's on this branch you'll see a few other jobs failing, and I believe that @thk123 had suggested just disabling the Alpine linux job. Likewise, the lint failure looks more like just infrastructure cruft rather than a code issue. |
As far as the AppVeyor failure goes, it's not one I've seen before but I wonder if its somehow related to any of the recent windows build work, or whether AppVeyor builds have just bit-rotted on this branch, given its relative age? |
The Apline build fails because it has a different definition of assert which somehow trips over or changes the result of regression tests. There is a more full discussion on #726. If this is rebased on the current develop, the alpine build is no longer run.
|
No description provided.