public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* C++ PATCH for c++/69509 and c++/69516 (infinite recursion with invalid VLAs in constexpr functions)
@ 2016-01-28 20:33 Marek Polacek
  2016-01-28 22:51 ` Jason Merrill
  2016-01-29  3:53 ` Martin Sebor
  0 siblings, 2 replies; 4+ messages in thread
From: Marek Polacek @ 2016-01-28 20:33 UTC (permalink / raw)
  To: GCC Patches, Jason Merrill

This patch fixes some problems with VLAs and constexprs.  (The runtime aspect
of this matter is being tracked in PR69517.)  It does two things:

1) Changes build_vec_init slightly to prevent the compiler from getting into
   infinite recursion.  Currently, we emit the following sequence when we're
   initializing e.g. n = 1; int a[n] = { 1, 2 }; (cleanup_point junk omitted):

  int a[0:(sizetype) (SAVE_EXPR <(ssizetype) n + -1>)];
  (void) (int[0:(sizetype) (SAVE_EXPR <(ssizetype) n + -1>)] *)   int * D.2256;
  (void) (D.2256 = (int *) &a)
    int * D.2257;
  (void) (D.2257 = D.2256)
    long int D.2258;
  (void) (D.2258 = (long int) (SAVE_EXPR <(ssizetype) n + -1>)) // == 0
  (void) (*D.2257 = 1)
  (void)  ++D.2257 
  (void)  --D.2258	// == -1
  (void) (*D.2257 = 2)
  (void)  ++D.2257
  (void)  --D.2258	// == -2

  while (1) 
    {   
      if (D.2258 == -1)	// never triggers
	goto <D.2261>;
      (void) (*D.2257 = 0) 
      (void)  ++D.2257
      (void)  --D.2258;
    }   
  <D.2261>:;
  ...

  So we first write the elements from the initializer and then we default-initialize
  any remaining elements.  But the iterator == -1 check is never true for invalid
  code, which means the compiler will get stuck in the while loop forever, leading
  to crash -- it tries to cxx_eval_* the body of the loop over and over again.

  Fixed by changing == -1 into <= -1; this should only ever happen for invalid code,
  but we don't want to ICE in any case.

  This also "fixes" the problem in PR69517 -- the program could get into infinite
  recursion as well, because it was evaluating the sequence above at runtime. But
  since it's invoking UB, it doesn't matter much.

2) Moves the check for "array subscript out of bound" a tad above, because for
   invalid VLAs we can't rely on the bool "found" -- e.g. for the example above
   it will find all indexes in the initializer, so "found" is true, which means
   we wouldn't get to the out-of-bounds check below.

Hopefully I'm making sense.  

Bootstrapped/regtested on x86_64-linux, ok for trunk?

2016-01-28  Marek Polacek  <polacek@redhat.com>

	PR c++/69509
	PR c++/69516
	* constexpr.c (cxx_eval_array_reference): Give the "array subscript
	out of bound" error earlier.
	* init.c (build_vec_init): Change NE_EXPR into GT_EXPR.  Update the
	commentary.

	* g++.dg/ext/constexpr-vla2.C: New test.
	* g++.dg/ext/constexpr-vla3.C: New test.
	* g++.dg/ubsan/vla-1.C: Remove dg-shouldfail.

diff --git gcc/cp/constexpr.c gcc/cp/constexpr.c
index 57595a4..b076991 100644
--- gcc/cp/constexpr.c
+++ gcc/cp/constexpr.c
@@ -1833,6 +1833,19 @@ cxx_eval_array_reference (const constexpr_ctx *ctx, tree t,
       return t;
     }
 
+  tree nelts = array_type_nelts_top (TREE_TYPE (ary));
+  /* For VLAs, the number of elements won't be an integer constant.  */
+  nelts = cxx_eval_constant_expression (ctx, nelts, false, non_constant_p,
+					overflow_p);
+  VERIFY_CONSTANT (nelts);
+  if (!tree_int_cst_lt (index, nelts))
+    {
+      if (!ctx->quiet)
+	error ("array subscript out of bound");
+      *non_constant_p = true;
+      return t;
+    }
+
   bool found;
   if (TREE_CODE (ary) == CONSTRUCTOR)
     {
@@ -1846,37 +1859,23 @@ cxx_eval_array_reference (const constexpr_ctx *ctx, tree t,
 
   if (!found)
     {
-      tree nelts = array_type_nelts_top (TREE_TYPE (ary));
-      /* For VLAs, the number of elements won't be an integer constant.  */
-      nelts = cxx_eval_constant_expression (ctx, nelts, false, non_constant_p,
-					    overflow_p);
-      VERIFY_CONSTANT (nelts);
-      if (tree_int_cst_lt (index, nelts))
+      if (TREE_CODE (ary) == CONSTRUCTOR
+	  && CONSTRUCTOR_NO_IMPLICIT_ZERO (ary))
 	{
-	  if (TREE_CODE (ary) == CONSTRUCTOR
-	      && CONSTRUCTOR_NO_IMPLICIT_ZERO (ary))
-	    {
-	      /* 'ary' is part of the aggregate initializer we're currently
-		 building; if there's no initializer for this element yet,
-		 that's an error. */
-	      if (!ctx->quiet)
-		error ("accessing uninitialized array element");
-	      *non_constant_p = true;
-	      return t;
-	    }
-
-	  /* If it's within the array bounds but doesn't have an explicit
-	     initializer, it's value-initialized.  */
-	  tree val = build_value_init (elem_type, tf_warning_or_error);
-	  return cxx_eval_constant_expression (ctx, val,
-					       lval,
-					       non_constant_p, overflow_p);
+	  /* 'ary' is part of the aggregate initializer we're currently
+	     building; if there's no initializer for this element yet,
+	     that's an error.  */
+	  if (!ctx->quiet)
+	    error ("accessing uninitialized array element");
+	  *non_constant_p = true;
+	  return t;
 	}
 
-      if (!ctx->quiet)
-	error ("array subscript out of bound");
-      *non_constant_p = true;
-      return t;
+      /* If it's within the array bounds but doesn't have an explicit
+	 initializer, it's value-initialized.  */
+      tree val = build_value_init (elem_type, tf_warning_or_error);
+      return cxx_eval_constant_expression (ctx, val, lval, non_constant_p,
+					   overflow_p);
     }
 
   if (TREE_CODE (ary) == CONSTRUCTOR)
diff --git gcc/cp/init.c gcc/cp/init.c
index 9f7b614..976ada8 100644
--- gcc/cp/init.c
+++ gcc/cp/init.c
@@ -4008,7 +4008,7 @@ build_vec_init (tree base, tree maxindex, tree init,
 		&& (num_initialized_elts
 		    == tree_to_shwi (maxindex) + 1))))
     {
-      /* If the ITERATOR is equal to -1, then we don't have to loop;
+      /* If the ITERATOR is lesser or equal to -1, then we don't have to loop;
 	 we've already initialized all the elements.  */
       tree for_stmt;
       tree elt_init;
@@ -4016,7 +4016,7 @@ build_vec_init (tree base, tree maxindex, tree init,
 
       for_stmt = begin_for_stmt (NULL_TREE, NULL_TREE);
       finish_for_init_stmt (for_stmt);
-      finish_for_cond (build2 (NE_EXPR, boolean_type_node, iterator,
+      finish_for_cond (build2 (GT_EXPR, boolean_type_node, iterator,
 			       build_int_cst (TREE_TYPE (iterator), -1)),
 		       for_stmt, false);
       elt_init = cp_build_unary_op (PREDECREMENT_EXPR, iterator, 0,
diff --git gcc/testsuite/g++.dg/ext/constexpr-vla2.C gcc/testsuite/g++.dg/ext/constexpr-vla2.C
index e69de29..6cb1f70 100644
--- gcc/testsuite/g++.dg/ext/constexpr-vla2.C
+++ gcc/testsuite/g++.dg/ext/constexpr-vla2.C
@@ -0,0 +1,21 @@
+// PR c++/69509
+// { dg-do compile { target c++14 } }
+
+constexpr int
+fn_bad (int n)
+{
+  __extension__ int a [n] = { 0 };
+  int z = a [0] + (n ? fn_bad (n - 1) : 0);
+  return z;
+}
+
+constexpr int
+fn_ok (int n)
+{
+  __extension__ int a [n] = { 0 };
+  int z = a [0] + (n > 1 ? fn_ok (n - 1) : 0);
+  return z;
+}
+
+constexpr int i1 = fn_ok (3);
+constexpr int i2 = fn_bad (3); // { dg-error "array subscript out of bound" }
diff --git gcc/testsuite/g++.dg/ext/constexpr-vla3.C gcc/testsuite/g++.dg/ext/constexpr-vla3.C
index e69de29..ba4eb50 100644
--- gcc/testsuite/g++.dg/ext/constexpr-vla3.C
+++ gcc/testsuite/g++.dg/ext/constexpr-vla3.C
@@ -0,0 +1,14 @@
+// PR c++/69516
+// { dg-do compile { target c++14 } }
+
+constexpr int
+foo (int n)
+{
+  __extension__ int a[n] = { 1, 2, 3, 4, 5, 6 };
+  int z = 0;
+  for (int i = 0; i <= n; ++i)
+    z += a[i];
+  return z;
+}
+
+constexpr int n = foo (3); // { dg-error "array subscript out of bound" }
diff --git gcc/testsuite/g++.dg/ubsan/vla-1.C gcc/testsuite/g++.dg/ubsan/vla-1.C
index e7f2494..311cdb1 100644
--- gcc/testsuite/g++.dg/ubsan/vla-1.C
+++ gcc/testsuite/g++.dg/ubsan/vla-1.C
@@ -1,6 +1,5 @@
 // { dg-do run }
 // { dg-options "-Wno-vla -fsanitize=undefined" }
-// { dg-shouldfail "ubsan" }
 // { dg-output "index 1 out of bounds" }
 
 void f(int i) {

	Marek

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: C++ PATCH for c++/69509 and c++/69516 (infinite recursion with invalid VLAs in constexpr functions)
  2016-01-28 20:33 C++ PATCH for c++/69509 and c++/69516 (infinite recursion with invalid VLAs in constexpr functions) Marek Polacek
@ 2016-01-28 22:51 ` Jason Merrill
  2016-01-28 23:09   ` Marek Polacek
  2016-01-29  3:53 ` Martin Sebor
  1 sibling, 1 reply; 4+ messages in thread
From: Jason Merrill @ 2016-01-28 22:51 UTC (permalink / raw)
  To: Marek Polacek, GCC Patches

OK.  I suppose we should also catch the out-of-bounds store in 
cxx_eval_store_expression...

Jason

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: C++ PATCH for c++/69509 and c++/69516 (infinite recursion with invalid VLAs in constexpr functions)
  2016-01-28 22:51 ` Jason Merrill
@ 2016-01-28 23:09   ` Marek Polacek
  0 siblings, 0 replies; 4+ messages in thread
From: Marek Polacek @ 2016-01-28 23:09 UTC (permalink / raw)
  To: Jason Merrill; +Cc: GCC Patches

On Thu, Jan 28, 2016 at 05:51:30PM -0500, Jason Merrill wrote:
> OK.  I suppose we should also catch the out-of-bounds store in
> cxx_eval_store_expression...

Thanks, I'll look into that as a follow-up.

	Marek

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: C++ PATCH for c++/69509 and c++/69516 (infinite recursion with invalid VLAs in constexpr functions)
  2016-01-28 20:33 C++ PATCH for c++/69509 and c++/69516 (infinite recursion with invalid VLAs in constexpr functions) Marek Polacek
  2016-01-28 22:51 ` Jason Merrill
@ 2016-01-29  3:53 ` Martin Sebor
  1 sibling, 0 replies; 4+ messages in thread
From: Martin Sebor @ 2016-01-29  3:53 UTC (permalink / raw)
  To: Marek Polacek, GCC Patches, Jason Merrill

On 01/28/2016 01:33 PM, Marek Polacek wrote:
> This patch fixes some problems with VLAs and constexprs.  (The runtime aspect
> of this matter is being tracked in PR69517.)  It does two things:
>
> 1) Changes build_vec_init slightly to prevent the compiler from getting into
>     infinite recursion.  Currently, we emit the following sequence when we're
>     initializing e.g. n = 1; int a[n] = { 1, 2 }; (cleanup_point junk omitted):
>
>    int a[0:(sizetype) (SAVE_EXPR <(ssizetype) n + -1>)];
>    (void) (int[0:(sizetype) (SAVE_EXPR <(ssizetype) n + -1>)] *)   int * D.2256;
>    (void) (D.2256 = (int *) &a)
>      int * D.2257;
>    (void) (D.2257 = D.2256)
>      long int D.2258;
>    (void) (D.2258 = (long int) (SAVE_EXPR <(ssizetype) n + -1>)) // == 0
>    (void) (*D.2257 = 1)
>    (void)  ++D.2257
>    (void)  --D.2258	// == -1
>    (void) (*D.2257 = 2)
>    (void)  ++D.2257
>    (void)  --D.2258	// == -2
>
>    while (1)
>      {
>        if (D.2258 == -1)	// never triggers
> 	goto <D.2261>;
>        (void) (*D.2257 = 0)
>        (void)  ++D.2257
>        (void)  --D.2258;
>      }
>    <D.2261>:;
>    ...
>
>    So we first write the elements from the initializer and then we default-initialize
>    any remaining elements.  But the iterator == -1 check is never true for invalid
>    code, which means the compiler will get stuck in the while loop forever, leading
>    to crash -- it tries to cxx_eval_* the body of the loop over and over again.
>
>    Fixed by changing == -1 into <= -1; this should only ever happen for invalid code,
>    but we don't want to ICE in any case.
>
>    This also "fixes" the problem in PR69517 -- the program could get into infinite
>    recursion as well, because it was evaluating the sequence above at runtime. But
>    since it's invoking UB, it doesn't matter much.
>
> 2) Moves the check for "array subscript out of bound" a tad above, because for
>     invalid VLAs we can't rely on the bool "found" -- e.g. for the example above
>     it will find all indexes in the initializer, so "found" is true, which means
>     we wouldn't get to the out-of-bounds check below.

I haven't had time to study the code but I did apply the patch
and play with it a bit.  It sure does fix the infinite loop/
recursion problem!

That said, while the test case below is rejected with the array
being of type int (as it should be), it's accepted as is, with
the struct.  I don't know enough to tell if it's because of
the change you mention or if it's a latent bug.

The test case is also accepted with an invalid argument to foo
(negative or excessive), both which should be rejected as well.

You're clearly good and efficient at fixing things.  I seem to
have a knack for breaking them.  Please let me know if I should
open a new bug for this.

   struct A {
     constexpr A (int = 0) { }
     constexpr operator int () const { return 0; }
   };

   constexpr int foo (int n) {
     A a [n] = { 1, 2, 3, 4, 5, 6 };
     return a [99];
   }

   struct B { unsigned b: foo (1) + 1; };

Martin

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2016-01-29  3:53 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-01-28 20:33 C++ PATCH for c++/69509 and c++/69516 (infinite recursion with invalid VLAs in constexpr functions) Marek Polacek
2016-01-28 22:51 ` Jason Merrill
2016-01-28 23:09   ` Marek Polacek
2016-01-29  3:53 ` Martin Sebor

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).