https://gcc.gnu.org/g:31c151da46e71a35e3ee6e0ad6abd21a45f06022

commit r16-9510-g31c151da46e71a35e3ee6e0ad6abd21a45f06022
Author: Jakub Jelinek <[email protected]>
Date:   Thu Aug 6 11:26:42 2026 +0200

    c++: Fix up constexpr handling of break in expansion statements [PR125601]
    
    As the following testcase shows, we mishandle break statements in expansion
    statements during constant evaluation.
    finish_expansion_stmt changes the BREAK_STMT/CONTINUE_STMTs to GOTO_EXPRs
    to corresponding labels and marks those labels with
    LABEL_DECL_BREAK/LABEL_DECL_CONTINUE so that constexpr.cc is happy about
    those.  The continue label (if any is needed) is right after each
    iteration's instantiated body, the break label (if any is needed) is after
    the last body.
    Now, continue seems to work properly, when we encounter it, we set
    *jump_target to it and continues predicate is true on it, but
    cxx_eval_statement_list has
          /* We've found a continue, so skip everything until we reach
             the label its jumping to.  */
          if (continues (jump_target))
            {
              if (label_matches (ctx, jump_target, stmt))
                /* Found it.  */
                *jump_target = NULL_TREE;
              else
                continue;
            }
    ...
          if (returns (jump_target)
              || breaks (jump_target)
              || throws (jump_target))
            break;
    and so it properly iterates through statement lists until it finds
    the label decl.
    But unfortunately it doesn't work for break, we set *jump_target on
    the GOTO_EXPR, breaks predicate is true, but then break out of any
    STATEMENT_LISTs and the only way to resume processing of statements
    in that case is when cxx_eval_loop_expr does
              if (breaks (jump_target))
                {
                  *jump_target = NULL_TREE;
                  break;
                }
    or similarly switch handling.  But for expansion stmt there is
    nothing like that in the IL, so either we break some outer loop
    (foo in the testcase) instead, or fail because we think there was no return
    in the function.
    
    The following patch fixes this by wrapping the series of instantiated
    expansion stmt bodies (for all iterations) in an artificial
    do ... while (0); statement, but does that only if break; was actually
    needed (i.e. when we are emitting a break_label).
    
    2026-08-06  Jakub Jelinek  <[email protected]>
    
            PR c++/125601
            * pt.cc (finish_expansion_stmt): If break; was seen in any of the
            expansion stmt bodies, wrap all the bodies in an artificial
            do ... while (0); stmt.
    
            * g++.dg/cpp26/expansion-stmt45.C: New test.
    
    Reviewed-by: Jason Merrill <[email protected]>
    (cherry picked from commit 94843e8b8a47e9a4972b73637578dac12fe1ade7)

Diff:
---
 gcc/cp/pt.cc                                  | 15 ++++++-
 gcc/testsuite/g++.dg/cpp26/expansion-stmt45.C | 64 +++++++++++++++++++++++++++
 2 files changed, 78 insertions(+), 1 deletion(-)

diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 729c7b191654..ab3ae922ac51 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -33659,6 +33659,7 @@ finish_expansion_stmt (tree expansion_stmt, tree args,
       DECL_NAME (decl) = NULL_TREE;
     }
 
+  tree stmt_list = push_stmt_list ();
   expansion_stmt_bc bc_data = { NULL_TREE, NULL_TREE, NULL, loc, false };
 
   for (unsigned HOST_WIDE_INT i = 0; i < n; ++i)
@@ -33858,7 +33859,19 @@ finish_expansion_stmt (tree expansion_stmt, tree args,
        }
     }
   if (bc_data.break_label)
-    add_stmt (build1 (LABEL_EXPR, void_type_node, bc_data.break_label));
+    {
+      /* If break; is seen, wrap all the expansion stmt bodies in
+        a single artificial do ... while (0); statement, so that
+        constant evaluation handles break; correctly.  */
+      tree do_stmt
+       = build_stmt (loc, DO_STMT, NULL_TREE, NULL_TREE, NULL_TREE);
+      DO_COND (do_stmt) = boolean_false_node;
+      DO_BODY (do_stmt) = pop_stmt_list (stmt_list);
+      add_stmt (do_stmt);
+      add_stmt (build1 (LABEL_EXPR, void_type_node, bc_data.break_label));
+    }
+  else
+    add_stmt (pop_stmt_list (stmt_list));
   if (args == NULL_TREE)
     {
       TREE_TYPE (range_decl) = error_mark_node;
diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt45.C 
b/gcc/testsuite/g++.dg/cpp26/expansion-stmt45.C
new file mode 100644
index 000000000000..eeaf9981455f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt45.C
@@ -0,0 +1,64 @@
+// PR c++/125601
+// { dg-do run { target c++14 } }
+// { dg-options "-O2" }
+
+constexpr int
+foo (int x)
+{
+  int a = 0, b = 3;
+  while (b > 0)
+    {
+      ++a;
+      --b;
+      template for (constexpr int value : { 10, 20, 30 })      // { dg-warning 
"'template for' only available with" "" { target c++23_down } }
+        {
+          a += value;
+         if (x == 0)
+           break;
+         else if (x == 1)
+           continue;
+         a += 42;
+        }
+    }
+  return a;
+}
+
+constexpr int
+bar (int x)
+{
+  int a = 0;
+  template for (constexpr int value : { 10, 20, 30 })          // { dg-warning 
"'template for' only available with" "" { target c++23_down } }
+    {
+      a += value;
+      if (x == 0)
+       break;
+      else if (x == 1)
+       continue;
+      a += 42;
+    }
+  return a;
+}
+
+static_assert (foo (0) == 3 * (1 + 10), "");
+static_assert (foo (1) == 3 * (1 + 10 + 20 + 30), "");
+static_assert (foo (2) == 3 * (1 + 10 + 20 + 30 + 3 * 42), "");
+static_assert (bar (0) == 10, "");
+static_assert (bar (1) == 10 + 20 + 30, "");
+static_assert (bar (2) == 10 + 20 + 30 + 3 * 42, "");
+
+int
+main ()
+{
+  if (foo (0) != 3 * (1 + 10))
+    __builtin_abort ();
+  if (foo (1) != 3 * (1 + 10 + 20 + 30))
+    __builtin_abort ();
+  if (foo (2) != 3 * (1 + 10 + 20 + 30 + 3 * 42))
+    __builtin_abort ();
+  if (bar (0) != 10)
+    __builtin_abort ();
+  if (bar (1) != 10 + 20 + 30)
+    __builtin_abort ();
+  if (bar (2) != 10 + 20 + 30 + 3 * 42)
+    __builtin_abort ();
+}

Reply via email to