re PR c++/77467 (Segmentation fault with switch statement in constexpr function)
authorJakub Jelinek <jakub@redhat.com>
Wed, 28 Sep 2016 19:21:47 +0000 (21:21 +0200)
committerJakub Jelinek <jakub@gcc.gnu.org>
Wed, 28 Sep 2016 19:21:47 +0000 (21:21 +0200)
PR c++/77467
* constexpr.c (enum constexpr_switch_state): New.
(struct constexpr_ctx): Add css_state field.
(label_matches): Add CTX and STMT arguments, remove I and
DEFAULT_LABEL.  For CASE_LABEL_EXPR assert ctx->css_state != NULL,
handle default labels according to css_state.
(cxx_eval_statement_list): Remove statement skipping, label_matches
and default_label handling code.
(cxx_eval_loop_expr): Exit after first iteration even if
switches (jump_target).
(cxx_eval_switch_expr): Set up css_state field in ctx, if default
label has been seen in the body, but no cases matched, evaluate
the body second time.
(cxx_eval_constant_expression): Handle stmt skipping and label_matches
here.  Handle PREDICT_EXPR.  For MODIFY_EXPR or INIT_EXPR, assert
statement is not skipped.  For COND_EXPR during skipping, don't
evaluate condition, just the then block and if still skipping at the
end also the else block.
(cxx_eval_outermost_constant_expr): Adjust constexpr_ctx initializer.
(is_sub_constant_expr): Likewise.

* g++.dg/cpp1y/constexpr-77467.C: New test.

From-SVN: r240591

gcc/cp/ChangeLog
gcc/cp/constexpr.c
gcc/testsuite/ChangeLog
gcc/testsuite/g++.dg/cpp1y/constexpr-77467.C [new file with mode: 0644]

index 1a1f186e72665bd9d467df00e3e5c9bcffc2a1e1..0e18eb7fd171888ded0000fa93ca7784da8357b9 100644 (file)
@@ -1,3 +1,26 @@
+2016-09-28  Jakub Jelinek  <jakub@redhat.com>
+
+       PR c++/77467
+       * constexpr.c (enum constexpr_switch_state): New.
+       (struct constexpr_ctx): Add css_state field.
+       (label_matches): Add CTX and STMT arguments, remove I and
+       DEFAULT_LABEL.  For CASE_LABEL_EXPR assert ctx->css_state != NULL,
+       handle default labels according to css_state.
+       (cxx_eval_statement_list): Remove statement skipping, label_matches
+       and default_label handling code.
+       (cxx_eval_loop_expr): Exit after first iteration even if
+       switches (jump_target).
+       (cxx_eval_switch_expr): Set up css_state field in ctx, if default
+       label has been seen in the body, but no cases matched, evaluate
+       the body second time.
+       (cxx_eval_constant_expression): Handle stmt skipping and label_matches
+       here.  Handle PREDICT_EXPR.  For MODIFY_EXPR or INIT_EXPR, assert
+       statement is not skipped.  For COND_EXPR during skipping, don't
+       evaluate condition, just the then block and if still skipping at the
+       end also the else block.
+       (cxx_eval_outermost_constant_expr): Adjust constexpr_ctx initializer.
+       (is_sub_constant_expr): Likewise.
+
 2016-09-27  Jakub Jelinek  <jakub@redhat.com>
 
        Implement P0018R3, C++17 lambda capture of *this by value as [=,*this]
index bd4068e84c588b018b41f0b31ef577365a1125cd..2db13d2e1877a18535a2d838ed1a93624619b68a 100644 (file)
@@ -900,6 +900,18 @@ struct constexpr_call_hasher : ggc_ptr_hash<constexpr_call>
   static bool equal (constexpr_call *, constexpr_call *);
 };
 
+enum constexpr_switch_state {
+  /* Used when processing a switch for the first time by cxx_eval_switch_expr
+     and default: label for that switch has not been seen yet.  */
+  css_default_not_seen,
+  /* Used when processing a switch for the first time by cxx_eval_switch_expr
+     and default: label for that switch has been seen already.  */
+  css_default_seen,
+  /* Used when processing a switch for the second time by
+     cxx_eval_switch_expr, where default: label should match.  */
+  css_default_processing
+};
+
 /* The constexpr expansion context.  CALL is the current function
    expansion, CTOR is the current aggregate initializer, OBJECT is the
    object being initialized by CTOR, either a VAR_DECL or a _REF.  VALUES
@@ -919,6 +931,8 @@ struct constexpr_ctx {
   tree ctor;
   /* The object we're building the CONSTRUCTOR for.  */
   tree object;
+  /* If inside SWITCH_EXPR.  */
+  constexpr_switch_state *css_state;
   /* Whether we should error on a non-constant expression or fail quietly.  */
   bool quiet;
   /* Whether we are strictly conforming to constant expression rules or
@@ -3484,14 +3498,12 @@ switches (tree *jump_target)
 }
 
 /* Subroutine of cxx_eval_statement_list.  Determine whether the statement
-   at I matches *jump_target.  If we're looking for a case label and we see
-   the default label, copy I into DEFAULT_LABEL.  */
+   STMT matches *jump_target.  If we're looking for a case label and we see
+   the default label, note it in ctx->css_state.  */
 
 static bool
-label_matches (tree *jump_target, tree_stmt_iterator i,
-              tree_stmt_iterator& default_label)
+label_matches (const constexpr_ctx *ctx, tree *jump_target, tree stmt)
 {
-  tree stmt = tsi_stmt (i);
   switch (TREE_CODE (*jump_target))
     {
     case LABEL_DECL:
@@ -3503,8 +3515,18 @@ label_matches (tree *jump_target, tree_stmt_iterator i,
     case INTEGER_CST:
       if (TREE_CODE (stmt) == CASE_LABEL_EXPR)
        {
+         gcc_assert (ctx->css_state != NULL);
          if (!CASE_LOW (stmt))
-           default_label = i;
+           {
+             /* default: should appear just once in a SWITCH_EXPR
+                body (excluding nested SWITCH_EXPR).  */
+             gcc_assert (*ctx->css_state != css_default_seen);
+             /* When evaluating SWITCH_EXPR body for the second time,
+                return true for the default: label.  */
+             if (*ctx->css_state == css_default_processing)
+               return true;
+             *ctx->css_state = css_default_seen;
+           }
          else if (CASE_HIGH (stmt))
            {
              if (tree_int_cst_le (CASE_LOW (stmt), *jump_target)
@@ -3531,7 +3553,6 @@ cxx_eval_statement_list (const constexpr_ctx *ctx, tree t,
                         tree *jump_target)
 {
   tree_stmt_iterator i;
-  tree_stmt_iterator default_label = tree_stmt_iterator();
   tree local_target;
   /* In a statement-expression we want to return the last value.  */
   tree r = NULL_TREE;
@@ -3542,18 +3563,7 @@ cxx_eval_statement_list (const constexpr_ctx *ctx, tree t,
     }
   for (i = tsi_start (t); !tsi_end_p (i); tsi_next (&i))
     {
-    reenter:
       tree stmt = tsi_stmt (i);
-      if (*jump_target)
-       {
-         if (TREE_CODE (stmt) == STATEMENT_LIST)
-           /* The label we want might be inside.  */;
-         else if (label_matches (jump_target, i, default_label))
-           /* Found it.  */
-           *jump_target = NULL_TREE;
-         else
-           continue;
-       }
       r = cxx_eval_constant_expression (ctx, stmt, false,
                                        non_constant_p, overflow_p,
                                        jump_target);
@@ -3562,12 +3572,6 @@ cxx_eval_statement_list (const constexpr_ctx *ctx, tree t,
       if (returns (jump_target) || breaks (jump_target))
        break;
     }
-  if (switches (jump_target) && !tsi_end_p (default_label))
-    {
-      i = default_label;
-      *jump_target = NULL_TREE;
-      goto reenter;
-    }
   return r;
 }
 
@@ -3606,7 +3610,10 @@ cxx_eval_loop_expr (const constexpr_ctx *ctx, tree t,
          break;
        }
     }
-  while (!returns (jump_target) && !breaks (jump_target) && !*non_constant_p);
+  while (!returns (jump_target)
+        && !breaks (jump_target)
+        && !switches (jump_target)
+        && !*non_constant_p);
 
   if (breaks (jump_target))
     *jump_target = NULL_TREE;
@@ -3629,8 +3636,20 @@ cxx_eval_switch_expr (const constexpr_ctx *ctx, tree t,
   *jump_target = cond;
 
   tree body = TREE_OPERAND (t, 1);
-  cxx_eval_statement_list (ctx, body,
-                          non_constant_p, overflow_p, jump_target);
+  constexpr_ctx new_ctx = *ctx;
+  constexpr_switch_state css = css_default_not_seen;
+  new_ctx.css_state = &css;
+  cxx_eval_constant_expression (&new_ctx, body, false,
+                               non_constant_p, overflow_p, jump_target);
+  if (switches (jump_target) && css == css_default_seen)
+    {
+      /* If the SWITCH_EXPR body has default: label, process it once again,
+        this time instructing label_matches to return true for default:
+        label on switches (jump_target).  */
+      css = css_default_processing;
+      cxx_eval_constant_expression (&new_ctx, body, false,
+                                   non_constant_p, overflow_p, jump_target);
+    }
   if (breaks (jump_target) || switches (jump_target))
     *jump_target = NULL_TREE;
   return NULL_TREE;
@@ -3650,6 +3669,27 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
   constexpr_ctx new_ctx;
   tree r = t;
 
+  if (jump_target && *jump_target)
+    {
+      /* If we are jumping, ignore all statements/expressions except those
+        that could have LABEL_EXPR or CASE_LABEL_EXPR in their bodies.  */
+      switch (TREE_CODE (t))
+       {
+       case BIND_EXPR:
+       case STATEMENT_LIST:
+       case LOOP_EXPR:
+       case COND_EXPR:
+         break;
+       case LABEL_EXPR:
+       case CASE_LABEL_EXPR:
+         if (label_matches (ctx, jump_target, t))
+           /* Found it.  */
+           *jump_target = NULL_TREE;
+         return NULL_TREE;
+       default:
+         return NULL_TREE;
+       }
+    }
   if (t == error_mark_node)
     {
       *non_constant_p = true;
@@ -3730,6 +3770,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
     case LABEL_DECL:
     case LABEL_EXPR:
     case CASE_LABEL_EXPR:
+    case PREDICT_EXPR:
       return t;
 
     case PARM_DECL:
@@ -3835,6 +3876,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
 
     case INIT_EXPR:
     case MODIFY_EXPR:
+      gcc_assert (jump_target == NULL || *jump_target == NULL_TREE);
       r = cxx_eval_store_expression (ctx, t, lval,
                                     non_constant_p, overflow_p);
       break;
@@ -4065,6 +4107,22 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
       break;
 
     case COND_EXPR:
+      if (jump_target && *jump_target)
+       {
+         /* When jumping to a label, the label might be either in the
+            then or else blocks, so process then block first in skipping
+            mode first, and if we are still in the skipping mode at its end,
+            process the else block too.  */
+         r = cxx_eval_constant_expression (ctx, TREE_OPERAND (t, 1),
+                                           lval, non_constant_p, overflow_p,
+                                           jump_target);
+         if (*jump_target)
+           r = cxx_eval_constant_expression (ctx, TREE_OPERAND (t, 2),
+                                             lval, non_constant_p, overflow_p,
+                                             jump_target);
+         break;
+       }
+      /* FALLTHRU */
     case VEC_COND_EXPR:
       r = cxx_eval_conditional_expression (ctx, t, lval,
                                           non_constant_p, overflow_p,
@@ -4340,7 +4398,7 @@ cxx_eval_outermost_constant_expr (tree t, bool allow_non_constant,
   bool overflow_p = false;
   hash_map<tree,tree> map;
 
-  constexpr_ctx ctx = { NULL, &map, NULL, NULL, NULL,
+  constexpr_ctx ctx = { NULL, &map, NULL, NULL, NULL, NULL,
                        allow_non_constant, strict };
 
   tree type = initialized_type (t);
@@ -4460,7 +4518,7 @@ is_sub_constant_expr (tree t)
   bool overflow_p = false;
   hash_map <tree, tree> map;
 
-  constexpr_ctx ctx = { NULL, &map, NULL, NULL, NULL, true, true };
+  constexpr_ctx ctx = { NULL, &map, NULL, NULL, NULL, NULL, true, true };
 
   cxx_eval_constant_expression (&ctx, t, false, &non_constant_p,
                                &overflow_p);
index ee788ee208268b0b0d3c558f91685acd7b8baa2e..22a001b14459568ff3843fd9b3997b2eb03ca6e6 100644 (file)
@@ -1,3 +1,8 @@
+2016-09-28  Jakub Jelinek  <jakub@redhat.com>
+
+       PR c++/77467
+       * g++.dg/cpp1y/constexpr-77467.C: New test.
+
 2016-09-28  Martin Sebor  <msebor@redhat.com>
 
        PR c/77762
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-77467.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-77467.C
new file mode 100644 (file)
index 0000000..fd94e78
--- /dev/null
@@ -0,0 +1,128 @@
+// PR c++/77467
+// { dg-do compile { target c++14 } }
+
+constexpr int
+foo (const int x, const unsigned n) noexcept
+{
+  switch (n)
+    {
+    case 0:
+      return 1;
+    case 1:
+      return x;
+    default:
+      const auto m = (n >> 1);
+      const auto y = foo (x, m);
+      return ((m << 1) == n) ? y * y : x * y * y;
+    }
+}
+
+static_assert (foo (3, 2) == 9, "");
+static_assert (foo (2, 3) == 8, "");
+
+constexpr int
+bar (int x)
+{
+  int a = x;
+  switch (x)
+    a = x + 1;
+  return a;
+}
+
+static_assert (bar (0) == 0, "");
+static_assert (bar (1) == 1, "");
+
+constexpr int
+baz (const int x, int y) noexcept
+{
+  switch (x)
+    {
+    case 0:
+      return 1;
+    case 1:
+      return x;
+    case 2:
+      if ((y += 2) == 0)
+       {
+       case 3:
+         y += 4;
+         break;
+       }
+      else
+       {
+       case 4:
+         y += 8;
+         break;
+       }
+      break;
+    case 5:
+      for (y = 0; y < 3; y++)
+       {
+       case 7:
+         if (y == -4)
+           y += 3;
+         if (y == -3)
+           continue;
+         if (y == -2)
+           {
+             y += 18;
+             break;
+           }
+         if (y == 2)
+           {
+           case 6:
+             y += 12;
+           default:
+             y++;
+             break;
+           }
+       }
+      break;
+    case -1:
+    case -2:
+      switch (y)
+       {
+       case 19:
+         y += 2;
+         break;
+       case 20:
+         y += 3;
+         if (x == 2)
+           case 21:;
+         y += 2;
+         if (x == 3)
+           default:;
+         y += 4;
+         break;
+       }
+      return x + y + 1;
+    }
+  return x + y;
+}
+
+static_assert (baz (0, 7) == 1, "");
+static_assert (baz (1, 7) == 1, "");
+static_assert (baz (2, -2) == 6, "");
+static_assert (baz (2, 0) == 12, "");
+static_assert (baz (3, 1) == 8, "");
+static_assert (baz (4, 2) == 14, "");
+static_assert (baz (5, -20) == 20, "");
+static_assert (baz (6, 5) == 24, "");
+static_assert (baz (7, -5) == 22, "");
+static_assert (baz (7, -4) == 22, "");
+static_assert (baz (7, -3) == 23, "");
+static_assert (baz (7, -2) == 23, "");
+static_assert (baz (7, -1) == 22, "");
+static_assert (baz (7, 0) == 22, "");
+static_assert (baz (7, 2) == 22, "");
+static_assert (baz (7, 6) == 14, "");
+static_assert (baz (8, 9) == 18, "");
+static_assert (baz (8, -2) == 7, "");
+static_assert (baz (-1, 19) == 21, "");
+static_assert (baz (-1, 20) == 29, "");
+static_assert (baz (-1, 21) == 27, "");
+static_assert (baz (-1, 5) == 9, "");
+static_assert (baz (-2, 19) == 20, "");
+static_assert (baz (-2, 20) == 28, "");
+static_assert (baz (-2, 21) == 26, "");
+static_assert (baz (-2, 5) == 8, "");