diff mbox

Fix PR80533

Message ID alpine.LSU.2.20.1704280921260.17885@zhemvz.fhfr.qr
State New
Headers show

Commit Message

Richard Biener April 28, 2017, 8:22 a.m. UTC
On Thu, 27 Apr 2017, Alexander Monakov wrote:

> On Thu, 27 Apr 2017, Richard Biener wrote:
> > struct q { int n; long o[100]; };
> > struct r { int n; long o[0]; };
> > 
> > union {
> >     struct r r;
> >     struct q q;
> > } u;
> > 
> > int foo (int i, int j)
> > {
> >   long *q = u.r.o;
> >   u.r.o[i/j] = 1;
> >   return q[2];
> > }
> > 
> > but nothing convinced scheduling to move the load before the store ;)
> > The two memory references are seen as not aliasing though.  Stupid
> > scheduler.
> 
> On x86 there's an anti-dependence on %eax that prevents the second scheduler
> from performing the breaking motion; with -fschedule-insns, pre-RA scheduler
> is actually able to move the load too early, but then IRA immediately undoes
> that.  Also, -fselective-scheduling2 is able to move the load early via renaming
> (and uncovers the miscompile, as nothing undoes it later).
> 
> Applying division to the result of the load, rather than the address of the
> store, also produces code demonstrating the miscompile:
> 
> struct q { int n; long o[100]; };
> struct r { int n; long o[0]; };
> 
> union {
>     struct r r;
>     struct q q;
> } u;
> 
> int foo (int i, int j)
> {
>   long *q = u.r.o;
>   u.r.o[i] = 1;
>   return q[2]/j;
> }

Thanks.  It also is still miscompiled because array_at_struct_end_p
is somewhat confused as to what its semantics are supposed to be.
Multiple callers (including the new one) assume when it returns false
they can trust the array domain while in reality when it returns false
it says we can constrain the access even if only because we know an
underlying decls size ...

It also raises the question whether for

struct X
{
  double x;
  int a[1];
} x;

x.a is an array at struct end -- there's enough padding to provide
space for x.a[1] even though its size doesn't include that (and
whether it can depends on the alignment of 'double').  This is
sort-of what happens for the union case above -- u.r.o can be
extended into the padding (which is quite large due to u.q.o).
The question is whether we allow such access to padding in general
(not only when the array is at struct end).  Likewise do we allow, in

struct Y
{
  struct X[4][4] a;
} y;

for y.a[i][j].a[k] k == 3?  that is, allow the "last" element of
an array to be flexibly extended?  Or do we instead allow
y.a[i][j] with j == 4, thus the last dimension of a multidimensional
array to be "over-allocated"?  Or do we just allow i > 3?

There's another PR where array-bound warnings are confused by
the weird answer from array_at_struct_end_p and two other users
I skimmed would generate wrong code because they trust the array
domain after it.

Looks like I have to refactor that a bit, like with the following
which makes things a bit more explicit, and _not_ allowing to
use padding appart when using flexarrays (though that can't
happen, you can't instantiate those with decls) or the zero-size
array GNU extension.

It also corrects IMHO wrong behavior with VIEW_CONVERT_EXPRs
and the overly pessimistic handling of arrays of structs with
arrays at struct end or multi-dimensional arrays when not
dealing with the outermost dimension.

So I'm testing the following.

Richard.

2017-04-28  Richard Biener  <rguenther@suse.de>

	PR middle-end/80533
	* tree.c (array_at_struct_end_p): Handle arrays at struct
	end with size zero more conservatively.  Refactor and treat
	arrays of arrays or aggregates more strict.  Fix
	VIEW_CONVERT_EXPR handling.

	* gcc.dg/torture/pr80533.c: New testcase.

Comments

Richard Biener April 28, 2017, 1:17 p.m. UTC | #1
On Fri, 28 Apr 2017, Richard Biener wrote:

> On Thu, 27 Apr 2017, Alexander Monakov wrote:
> 
> > On Thu, 27 Apr 2017, Richard Biener wrote:
> > > struct q { int n; long o[100]; };
> > > struct r { int n; long o[0]; };
> > > 
> > > union {
> > >     struct r r;
> > >     struct q q;
> > > } u;
> > > 
> > > int foo (int i, int j)
> > > {
> > >   long *q = u.r.o;
> > >   u.r.o[i/j] = 1;
> > >   return q[2];
> > > }
> > > 
> > > but nothing convinced scheduling to move the load before the store ;)
> > > The two memory references are seen as not aliasing though.  Stupid
> > > scheduler.
> > 
> > On x86 there's an anti-dependence on %eax that prevents the second scheduler
> > from performing the breaking motion; with -fschedule-insns, pre-RA scheduler
> > is actually able to move the load too early, but then IRA immediately undoes
> > that.  Also, -fselective-scheduling2 is able to move the load early via renaming
> > (and uncovers the miscompile, as nothing undoes it later).
> > 
> > Applying division to the result of the load, rather than the address of the
> > store, also produces code demonstrating the miscompile:
> > 
> > struct q { int n; long o[100]; };
> > struct r { int n; long o[0]; };
> > 
> > union {
> >     struct r r;
> >     struct q q;
> > } u;
> > 
> > int foo (int i, int j)
> > {
> >   long *q = u.r.o;
> >   u.r.o[i] = 1;
> >   return q[2]/j;
> > }
> 
> Thanks.  It also is still miscompiled because array_at_struct_end_p
> is somewhat confused as to what its semantics are supposed to be.
> Multiple callers (including the new one) assume when it returns false
> they can trust the array domain while in reality when it returns false
> it says we can constrain the access even if only because we know an
> underlying decls size ...
> 
> It also raises the question whether for
> 
> struct X
> {
>   double x;
>   int a[1];
> } x;
> 
> x.a is an array at struct end -- there's enough padding to provide
> space for x.a[1] even though its size doesn't include that (and
> whether it can depends on the alignment of 'double').  This is
> sort-of what happens for the union case above -- u.r.o can be
> extended into the padding (which is quite large due to u.q.o).
> The question is whether we allow such access to padding in general
> (not only when the array is at struct end).  Likewise do we allow, in
> 
> struct Y
> {
>   struct X[4][4] a;
> } y;
> 
> for y.a[i][j].a[k] k == 3?  that is, allow the "last" element of
> an array to be flexibly extended?  Or do we instead allow
> y.a[i][j] with j == 4, thus the last dimension of a multidimensional
> array to be "over-allocated"?  Or do we just allow i > 3?
> 
> There's another PR where array-bound warnings are confused by
> the weird answer from array_at_struct_end_p and two other users
> I skimmed would generate wrong code because they trust the array
> domain after it.
> 
> Looks like I have to refactor that a bit, like with the following
> which makes things a bit more explicit, and _not_ allowing to
> use padding appart when using flexarrays (though that can't
> happen, you can't instantiate those with decls) or the zero-size
> array GNU extension.
> 
> It also corrects IMHO wrong behavior with VIEW_CONVERT_EXPRs
> and the overly pessimistic handling of arrays of structs with
> arrays at struct end or multi-dimensional arrays when not
> dealing with the outermost dimension.
> 
> So I'm testing the following.

Bootstrapped / tested on x86_64-unknown-linux-gnu.

FAIL: g++.dg/warn/Warray-bounds-4.C  -std=gnu++11  (test for warnings, 
line 25)

looks like we explicitely want to warn about char[0] (in C++!) ...

Richard.

> Richard.
> 
> 2017-04-28  Richard Biener  <rguenther@suse.de>
> 
> 	PR middle-end/80533
> 	* tree.c (array_at_struct_end_p): Handle arrays at struct
> 	end with size zero more conservatively.  Refactor and treat
> 	arrays of arrays or aggregates more strict.  Fix
> 	VIEW_CONVERT_EXPR handling.
> 
> 	* gcc.dg/torture/pr80533.c: New testcase.
> 
> Index: gcc/tree.c
> ===================================================================
> --- gcc/tree.c	(revision 247362)
> +++ gcc/tree.c	(working copy)
> @@ -13222,9 +13230,19 @@ array_ref_up_bound (tree exp)
>  bool
>  array_at_struct_end_p (tree ref, bool allow_compref)
>  {
> -  if (TREE_CODE (ref) != ARRAY_REF
> -      && TREE_CODE (ref) != ARRAY_RANGE_REF
> -      && (!allow_compref || TREE_CODE (ref) != COMPONENT_REF))
> +  tree atype;
> +
> +  if (TREE_CODE (ref) == ARRAY_REF
> +      || TREE_CODE (ref) == ARRAY_RANGE_REF)
> +    {
> +      atype = TREE_TYPE (TREE_OPERAND (ref, 0));
> +      ref = TREE_OPERAND (ref, 0);
> +    }
> +  else if (allow_compref
> +	   && TREE_CODE (ref) == COMPONENT_REF
> +	   && TREE_CODE (TREE_TYPE (TREE_OPERAND (ref, 1))) == ARRAY_TYPE)
> +    atype = TREE_TYPE (TREE_OPERAND (ref, 1));
> +  else
>      return false;
>  
>    while (handled_component_p (ref))
> @@ -13232,19 +13250,43 @@ array_at_struct_end_p (tree ref, bool al
>        /* If the reference chain contains a component reference to a
>           non-union type and there follows another field the reference
>  	 is not at the end of a structure.  */
> -      if (TREE_CODE (ref) == COMPONENT_REF
> -	  && TREE_CODE (TREE_TYPE (TREE_OPERAND (ref, 0))) == RECORD_TYPE)
> +      if (TREE_CODE (ref) == COMPONENT_REF)
>  	{
> -	  tree nextf = DECL_CHAIN (TREE_OPERAND (ref, 1));
> -	  while (nextf && TREE_CODE (nextf) != FIELD_DECL)
> -	    nextf = DECL_CHAIN (nextf);
> -	  if (nextf)
> -	    return false;
> +	  if (TREE_CODE (TREE_TYPE (TREE_OPERAND (ref, 0))) == RECORD_TYPE)
> +	    {
> +	      tree nextf = DECL_CHAIN (TREE_OPERAND (ref, 1));
> +	      while (nextf && TREE_CODE (nextf) != FIELD_DECL)
> +		nextf = DECL_CHAIN (nextf);
> +	      if (nextf)
> +		return false;
> +	    }
>  	}
> +      /* If we have a multi-dimensional array we do not consider
> +         a non-innermost dimension as flex array if the whole
> +	 multi-dimensional array is at struct end.
> +	 Same for an array of aggregates with a trailing array
> +	 member.  */
> +      else if (TREE_CODE (ref) == ARRAY_REF)
> +	return false;
> +      else if (TREE_CODE (ref) == ARRAY_RANGE_REF)
> +	;
> +      /* If we view an underlying object as sth else then what we
> +         gathered up to now is what we have to rely on.  */
> +      else if (TREE_CODE (ref) == VIEW_CONVERT_EXPR)
> +	break;
> +      else
> +	gcc_unreachable ();
>  
>        ref = TREE_OPERAND (ref, 0);
>      }
>  
> +  /* The array now is at struct end.  Treat flexible arrays and
> +     zero-sized arrays as always subject to extend, even into
> +     just padding constrained by an underlying decl.  */
> +  if (! TYPE_SIZE (atype)
> +      || integer_zerop (TYPE_SIZE (atype)))
> +    return true;
> +
>    tree size = NULL;
>  
>    if (TREE_CODE (ref) == MEM_REF
> Index: gcc/testsuite/gcc.dg/torture/pr80533.c
> ===================================================================
> --- gcc/testsuite/gcc.dg/torture/pr80533.c	(nonexistent)
> +++ gcc/testsuite/gcc.dg/torture/pr80533.c	(working copy)
> @@ -0,0 +1,27 @@
> +/* { dg-do run } */
> +
> +extern void abort (void);
> +
> +struct q { int n; long o[100]; };
> +struct r { int n; long o[0]; };
> +
> +union {
> +    struct r r;
> +    struct q q;
> +} u;
> +
> +int __attribute__((noclone,noinline))
> +foo (int i, int j)
> +{
> +  long *q = u.r.o;
> +  u.r.o[i] = 1;
> +  return q[2]/j;
> +}
> +
> +int
> +main()
> +{
> +  if (foo (2, 1) != 1)
> +    abort ();
> +  return 0;
> +}
>
diff mbox

Patch

Index: gcc/tree.c
===================================================================
--- gcc/tree.c	(revision 247362)
+++ gcc/tree.c	(working copy)
@@ -13222,9 +13230,19 @@  array_ref_up_bound (tree exp)
 bool
 array_at_struct_end_p (tree ref, bool allow_compref)
 {
-  if (TREE_CODE (ref) != ARRAY_REF
-      && TREE_CODE (ref) != ARRAY_RANGE_REF
-      && (!allow_compref || TREE_CODE (ref) != COMPONENT_REF))
+  tree atype;
+
+  if (TREE_CODE (ref) == ARRAY_REF
+      || TREE_CODE (ref) == ARRAY_RANGE_REF)
+    {
+      atype = TREE_TYPE (TREE_OPERAND (ref, 0));
+      ref = TREE_OPERAND (ref, 0);
+    }
+  else if (allow_compref
+	   && TREE_CODE (ref) == COMPONENT_REF
+	   && TREE_CODE (TREE_TYPE (TREE_OPERAND (ref, 1))) == ARRAY_TYPE)
+    atype = TREE_TYPE (TREE_OPERAND (ref, 1));
+  else
     return false;
 
   while (handled_component_p (ref))
@@ -13232,19 +13250,43 @@  array_at_struct_end_p (tree ref, bool al
       /* If the reference chain contains a component reference to a
          non-union type and there follows another field the reference
 	 is not at the end of a structure.  */
-      if (TREE_CODE (ref) == COMPONENT_REF
-	  && TREE_CODE (TREE_TYPE (TREE_OPERAND (ref, 0))) == RECORD_TYPE)
+      if (TREE_CODE (ref) == COMPONENT_REF)
 	{
-	  tree nextf = DECL_CHAIN (TREE_OPERAND (ref, 1));
-	  while (nextf && TREE_CODE (nextf) != FIELD_DECL)
-	    nextf = DECL_CHAIN (nextf);
-	  if (nextf)
-	    return false;
+	  if (TREE_CODE (TREE_TYPE (TREE_OPERAND (ref, 0))) == RECORD_TYPE)
+	    {
+	      tree nextf = DECL_CHAIN (TREE_OPERAND (ref, 1));
+	      while (nextf && TREE_CODE (nextf) != FIELD_DECL)
+		nextf = DECL_CHAIN (nextf);
+	      if (nextf)
+		return false;
+	    }
 	}
+      /* If we have a multi-dimensional array we do not consider
+         a non-innermost dimension as flex array if the whole
+	 multi-dimensional array is at struct end.
+	 Same for an array of aggregates with a trailing array
+	 member.  */
+      else if (TREE_CODE (ref) == ARRAY_REF)
+	return false;
+      else if (TREE_CODE (ref) == ARRAY_RANGE_REF)
+	;
+      /* If we view an underlying object as sth else then what we
+         gathered up to now is what we have to rely on.  */
+      else if (TREE_CODE (ref) == VIEW_CONVERT_EXPR)
+	break;
+      else
+	gcc_unreachable ();
 
       ref = TREE_OPERAND (ref, 0);
     }
 
+  /* The array now is at struct end.  Treat flexible arrays and
+     zero-sized arrays as always subject to extend, even into
+     just padding constrained by an underlying decl.  */
+  if (! TYPE_SIZE (atype)
+      || integer_zerop (TYPE_SIZE (atype)))
+    return true;
+
   tree size = NULL;
 
   if (TREE_CODE (ref) == MEM_REF
Index: gcc/testsuite/gcc.dg/torture/pr80533.c
===================================================================
--- gcc/testsuite/gcc.dg/torture/pr80533.c	(nonexistent)
+++ gcc/testsuite/gcc.dg/torture/pr80533.c	(working copy)
@@ -0,0 +1,27 @@ 
+/* { dg-do run } */
+
+extern void abort (void);
+
+struct q { int n; long o[100]; };
+struct r { int n; long o[0]; };
+
+union {
+    struct r r;
+    struct q q;
+} u;
+
+int __attribute__((noclone,noinline))
+foo (int i, int j)
+{
+  long *q = u.r.o;
+  u.r.o[i] = 1;
+  return q[2]/j;
+}
+
+int
+main()
+{
+  if (foo (2, 1) != 1)
+    abort ();
+  return 0;
+}