Skip to content

[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

Merged

Conversation

NlightNFotis
Copy link
Contributor

No description provided.

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))
Copy link
Contributor

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

Copy link
Collaborator

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.

@chrisr-diffblue chrisr-diffblue changed the title Fix for the constant arrays marked as nondet issue. [fix] Constant arrays should not be marked as nondet. Nov 28, 2017
^VERIFICATION SUCCESSFUL$
g_B = \{ 10, 20, 30 \}
--
!(g_B = NONDET\(const signed int \[3l\]\);)
Copy link
Contributor

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.

Copy link
Collaborator

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.

Copy link
Contributor

@chrisr-diffblue chrisr-diffblue left a 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.
Copy link
Collaborator

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\]\);)
Copy link
Collaborator

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))
Copy link
Collaborator

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.

@@ -0,0 +1,8 @@
const int g_A = 100;
Copy link
Contributor

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
Copy link
Contributor

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.

Copy link
Collaborator

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;
Copy link
Collaborator

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];
Copy link
Collaborator

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];
Copy link
Collaborator

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.
Copy link
Collaborator

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;


Copy link
Collaborator

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.

@tautschnig
Copy link
Collaborator

Why is this to be merged into diffblue:goto-analyzer-develop? (I guess that's the cause of it failing to build on AppVeyor.)

@chrisr-diffblue
Copy link
Contributor

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.

@tautschnig
Copy link
Collaborator

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.

@chrisr-diffblue
Copy link
Contributor

chrisr-diffblue commented Nov 28, 2017

We'll address your comments in this PR, don't worry :-)

@NlightNFotis
Copy link
Contributor Author

@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.

int main(void)
{
const int constant_array[]={10, 20, 30};
int var = constant_array[2];
Copy link
Collaborator

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?!


bool is_constant_variable_or_array(const typet &type)
{
return type.get_bool(ID_C_constant) ||
Copy link
Collaborator

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 [])
Copy link
Collaborator

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.)

@NlightNFotis NlightNFotis force-pushed the fotis_pb10 branch 2 times, most recently from ed18460 to f31809f Compare November 29, 2017 12:22
Copy link
Collaborator

@martin-cs martin-cs left a 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];
}
Copy link
Collaborator

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.

Copy link
Contributor

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);
Copy link
Collaborator

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;
Copy link
Collaborator

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

]

@martin-cs
Copy link
Collaborator

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
Copy link
Contributor

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?

Copy link
Contributor Author

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.

return component.type().get_bool(ID_C_constant);
if (component.type().get_bool(ID_C_constant))
return true;
}
Copy link
Contributor

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...

Copy link
Contributor

@chrisr-diffblue chrisr-diffblue left a 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.

@@ -34,3 +36,54 @@ bool is_number(const typet &type)
id==ID_floatbv ||
id==ID_fixedbv;
}

bool is_constant_or_has_constant_components(
Copy link
Contributor

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?

return true;

// struct t { const int a; };
// struct t t1; <--
Copy link
Contributor

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?

Copy link
Contributor

@chrisr-diffblue chrisr-diffblue left a 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.

for(const auto &component : struct_union_type.components())
{
if(component.type().id() == ID_pointer)
return component.type().get_bool(ID_C_constant);
Copy link
Collaborator

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?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good catch!

if(subtype.id() == ID_symbol)
{
const auto &new_subtype = ns.follow(to_symbol_type(subtype));
return has_constant_components(new_subtype);
Copy link
Collaborator

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.

Copy link
Contributor

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?

Copy link
Collaborator

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.

Copy link
Contributor Author

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"
Copy link
Collaborator

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.

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.
Copy link
Contributor

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.

Copy link
Contributor

@chrisr-diffblue chrisr-diffblue left a 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.

@martin-cs
Copy link
Collaborator

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.

@tautschnig
Copy link
Collaborator

Can this maybe just be re-targetted to develop? All I'd ask for is squashing commits.

@martin-cs
Copy link
Collaborator

@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!

Copy link
Contributor

@chrisr-diffblue chrisr-diffblue left a 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.

@martin-cs
Copy link
Collaborator

Happy with the code but am still not sure why we are getting CI failures. Any ideas @NlightNFotis ?

@chrisr-diffblue
Copy link
Contributor

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.

@chrisr-diffblue
Copy link
Contributor

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?

@thk123
Copy link
Contributor

thk123 commented Dec 8, 2017

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.

The windows build I don't think is related to recent changes (in part as this not based on a recent version of develop). But it is also failing on goto-analyzer-develop HEAD so isn't a result of this PR. It may be resolved by rebasing on develop or there may be a problem here. Read @chrisr-diffblue comment more carefully - I think this is just a case of bit rotting.

@chrisr-diffblue chrisr-diffblue merged commit 8879771 into diffblue:goto-analyzer-develop Dec 8, 2017
@NlightNFotis NlightNFotis deleted the fotis_pb10 branch October 20, 2020 15:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants