]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++: extended temp cleanups [PR118856]
authorJason Merrill <jason@redhat.com>
Fri, 14 Feb 2025 09:53:01 +0000 (10:53 +0100)
committerJason Merrill <jason@redhat.com>
Fri, 14 Feb 2025 16:12:48 +0000 (17:12 +0100)
A later testcase in PR118856 highlights a preexisting problem with multiple
reference-extended temporaries in a single declaration; if initializing a
later one throws, the cleanup for the earlier one is not in scope yet.
Let's deal with this by keeping a dummy TARGET_EXPR to hold the EH cleanup
until all other initialization is complete.  See the comment for various
other considered approaches.

We now avoid extending TARGET_EXPRs with CLEANUP_EH_ONLY set; all such
TARGET_EXPRs were already only internal iterator/flag variables that don't
want to be extended, as they are dead after initialization is complete even
if other temporaries are extended.  But some other internal temporaries did
not have the flag set because they don't have TARGET_EXPR_CLEANUP; I
introduce a get_internal_target_expr function to set the flag rather than
directly set the flag (and add a comment) in such places.  The places
changed to call get_internal_target_expr either already set the flag, or
have no cleanup at all.

PR c++/118856

gcc/cp/ChangeLog:

* call.cc (set_up_extended_ref_temp): Retain a TARGET_EXPR for
cleanups if something later in initialization throws.
(extend_temps_r): Don't extend eliding or EH-only TARGET_EXPRs.
* cp-tree.h (get_internal_target_expr): Declare.
* tree.cc (get_internal_target_expr): New.
* decl.cc (cp_finish_decomp, expand_static_init): Use it.
* except.cc (build_throw): Likewise.
* init.cc (build_new_1, build_vec_init, build_delete): Likewise.
(build_vec_delete): Likewise.
* typeck2.cc (maybe_push_temp_cleanup): Likewise.

gcc/testsuite/ChangeLog:

* g++.dg/eh/ref-temp3.C: New test.
* g++.dg/eh/ref-temp4.C: New test.

gcc/cp/call.cc
gcc/cp/cp-tree.h
gcc/cp/decl.cc
gcc/cp/except.cc
gcc/cp/init.cc
gcc/cp/tree.cc
gcc/cp/typeck2.cc
gcc/testsuite/g++.dg/eh/ref-temp3.C [new file with mode: 0644]
gcc/testsuite/g++.dg/eh/ref-temp4.C [new file with mode: 0644]

index 38a8f7fdcda9fcd6efc4caf1311a3a26d0a731a7..03130f80f861da25b5259fc1cf60dd4dbb2dd73a 100644 (file)
@@ -14251,6 +14251,8 @@ set_up_extended_ref_temp (tree decl, tree expr, vec<tree, va_gc> **cleanups,
        init = add_stmt_to_compound (init, register_dtor_fn (var));
       else
        {
+         /* ??? Instead of rebuilding the cleanup, we could replace the slot
+            with var in TARGET_EXPR_CLEANUP (expr).  */
          tree cleanup = cxx_maybe_build_cleanup (var, tf_warning_or_error);
          if (cleanup)
            {
@@ -14269,6 +14271,37 @@ set_up_extended_ref_temp (tree decl, tree expr, vec<tree, va_gc> **cleanups,
                  cleanup = build3 (COND_EXPR, void_type_node,
                                    *cond_guard, cleanup, NULL_TREE);
                }
+             if (flag_exceptions && TREE_CODE (TREE_TYPE (var)) != ARRAY_TYPE)
+               {
+                 /* The normal cleanup for this extended variable isn't pushed
+                    until cp_finish_decl, so we need to retain a TARGET_EXPR
+                    to clean it up in case a later initializer throws
+                    (g++.dg/eh/ref-temp3.C).
+
+                    We don't do this for array temporaries because they have
+                    the array cleanup region from build_vec_init.
+
+                    Unlike maybe_push_temp_cleanup, we don't actually need a
+                    flag, but a TARGET_EXPR needs a TARGET_EXPR_SLOT.
+                    Perhaps this could use WITH_CLEANUP_EXPR instead, but
+                    gimplify.cc doesn't handle that, and front-end handling
+                    was removed in r8-1725 and r8-1818.
+
+                    Alternately it might be preferable to flatten an
+                    initialization with extended temps into a sequence of
+                    (non-full-expression) statements, so we could immediately
+                    push_cleanup here for only a single cleanup region, but we
+                    don't have a mechanism for that in the front-end, only the
+                    gimplifier.  */
+                 tree targ = get_internal_target_expr (boolean_true_node);
+                 TARGET_EXPR_CLEANUP (targ) = cleanup;
+                 CLEANUP_EH_ONLY (targ) = true;
+                 /* Don't actually initialize the bool.  */
+                 init = (!init ? void_node
+                         : convert_to_void (init, ICV_STATEMENT, tf_none));
+                 TARGET_EXPR_INITIAL (targ) = init;
+                 init = targ;
+               }
              vec_safe_push (*cleanups, cleanup);
            }
        }
@@ -14886,7 +14919,12 @@ extend_temps_r (tree *tp, int *walk_subtrees, void *data)
         TREE_CODE (*p) == COMPONENT_REF || TREE_CODE (*p) == ARRAY_REF; )
       p = &TREE_OPERAND (*p, 0);
 
-  if (TREE_CODE (*p) == TARGET_EXPR)
+  if (TREE_CODE (*p) == TARGET_EXPR
+      /* An eliding TARGET_EXPR isn't a temporary at all.  */
+      && !TARGET_EXPR_ELIDING_P (*p)
+      /* A TARGET_EXPR with CLEANUP_EH_ONLY is an artificial variable used
+        during initialization, and need not be extended.  */
+      && !CLEANUP_EH_ONLY (*p))
     {
       tree subinit = NULL_TREE;
       tree slot = TARGET_EXPR_SLOT (*p);
index b7749eb2b327cd4707ed587803fc7f5ece3b5100..d87cc1555b7a71d579bcb9ca1b640293b791ff4c 100644 (file)
@@ -8207,6 +8207,7 @@ extern bool is_local_temp                 (tree);
 extern tree build_aggr_init_expr               (tree, tree);
 extern tree get_target_expr                    (tree,
                                                 tsubst_flags_t = tf_warning_or_error);
+extern tree get_internal_target_expr           (tree);
 extern tree build_cplus_array_type             (tree, tree, int is_dep = -1);
 extern tree build_array_of_n_type              (tree, unsigned HOST_WIDE_INT);
 extern bool array_of_runtime_bound_p           (tree);
index df4e66798b15fd8b4bdac94e601fdf25ce5b05a2..05ad9bb24d59c598dde4a3b14ed91c92e7736144 100644 (file)
@@ -10024,7 +10024,7 @@ cp_finish_decomp (tree decl, cp_decomp *decomp, bool test_p)
              /* Wrap that value into a TARGET_EXPR, emit it right
                 away and save for later uses in the cp_parse_condition
                 or its instantiation.  */
-             cond = get_target_expr (cond);
+             cond = get_internal_target_expr (cond);
              add_stmt (cond);
              DECL_DECOMP_BASE (decl) = cond;
            }
@@ -10687,7 +10687,7 @@ expand_static_init (tree decl, tree init)
                               inner_if_stmt);
 
          inner_then_clause = begin_compound_stmt (BCS_NO_SCOPE);
-         begin = get_target_expr (boolean_false_node);
+         begin = get_internal_target_expr (boolean_false_node);
          flag = TARGET_EXPR_SLOT (begin);
 
          TARGET_EXPR_CLEANUP (begin)
index 1c1894b092d640067ebe802af8dfb13d4efbe6e8..a9d8e2ffb57ade7b5fd7e7bb28815dab2421fc2a 100644 (file)
@@ -712,7 +712,7 @@ build_throw (location_t loc, tree exp, tsubst_flags_t complain)
       allocate_expr = do_allocate_exception (temp_type);
       if (allocate_expr == error_mark_node)
        return error_mark_node;
-      allocate_expr = get_target_expr (allocate_expr);
+      allocate_expr = get_internal_target_expr (allocate_expr);
       ptr = TARGET_EXPR_SLOT (allocate_expr);
       TARGET_EXPR_CLEANUP (allocate_expr) = do_free_exception (ptr);
       CLEANUP_EH_ONLY (allocate_expr) = 1;
index bc0754735549d3f69a6127e7eee062d33bb86ee8..f77da4267cbd3198da294af396c88f7c839514f2 100644 (file)
@@ -3529,7 +3529,7 @@ build_new_1 (vec<tree, va_gc> **placement, tree type, tree nelts,
          && (INTEGRAL_OR_ENUMERATION_TYPE_P (TREE_TYPE (TREE_TYPE (placement)))
              || VOID_TYPE_P (TREE_TYPE (TREE_TYPE (placement)))))
        {
-         placement_expr = get_target_expr (placement_first);
+         placement_expr = get_internal_target_expr (placement_first);
          CALL_EXPR_ARG (alloc_call, 1)
            = fold_convert (TREE_TYPE (placement), placement_expr);
        }
@@ -3557,7 +3557,7 @@ build_new_1 (vec<tree, va_gc> **placement, tree type, tree nelts,
 
   /* Store the result of the allocation call in a variable so that we can
      use it more than once.  */
-  alloc_expr = get_target_expr (alloc_expr);
+  alloc_expr = get_internal_target_expr (alloc_expr);
   alloc_node = TARGET_EXPR_SLOT (alloc_expr);
 
   /* Strip any COMPOUND_EXPRs from ALLOC_CALL.  */
@@ -3841,7 +3841,7 @@ build_new_1 (vec<tree, va_gc> **placement, tree type, tree nelts,
        {
          tree end, sentry, begin;
 
-         begin = get_target_expr (boolean_true_node);
+         begin = get_internal_target_expr (boolean_true_node);
 
          sentry = TARGET_EXPR_SLOT (begin);
 
@@ -4679,7 +4679,7 @@ build_vec_init (tree base, tree maxindex, tree init,
   current_stmt_tree ()->stmts_are_full_exprs_p = 0;
   rval = get_temp_regvar (ptype, base);
   base = get_temp_regvar (ptype, rval);
-  tree iterator_targ = get_target_expr (maxindex);
+  tree iterator_targ = get_internal_target_expr (maxindex);
   add_stmt (iterator_targ);
   iterator = TARGET_EXPR_SLOT (iterator_targ);
 
@@ -5323,7 +5323,7 @@ build_delete (location_t loc, tree otype, tree addr,
   if (deleting)
     /* We will use ADDR multiple times so we must save it.  */
     {
-      addr_expr = get_target_expr (addr);
+      addr_expr = get_internal_target_expr (addr);
       addr = TARGET_EXPR_SLOT (addr_expr);
     }
 
@@ -5348,7 +5348,7 @@ build_delete (location_t loc, tree otype, tree addr,
      delete'.  */
   else if (use_global_delete)
     {
-      head = get_target_expr (build_headof (addr));
+      head = get_internal_target_expr (build_headof (addr));
       /* Delete the object.  */
       do_delete = build_op_delete_call (DELETE_EXPR,
                                        head,
@@ -5582,7 +5582,7 @@ build_vec_delete (location_t loc, tree base, tree maxindex,
       base = mark_rvalue_use (base);
       if (TREE_SIDE_EFFECTS (base))
        {
-         base_init = get_target_expr (base);
+         base_init = get_internal_target_expr (base);
          base = TARGET_EXPR_SLOT (base_init);
        }
       type = strip_array_types (TREE_TYPE (type));
@@ -5603,7 +5603,7 @@ build_vec_delete (location_t loc, tree base, tree maxindex,
        return error_mark_node;
       if (TREE_SIDE_EFFECTS (base))
        {
-         base_init = get_target_expr (base);
+         base_init = get_internal_target_expr (base);
          base = TARGET_EXPR_SLOT (base_init);
        }
     }
index bf84fb6bcec4d89ede5ce5635a2be7d0f1e2c55d..611930b3c286d8f7f93a9a0f6be203d7ca2402f1 100644 (file)
@@ -969,6 +969,27 @@ get_target_expr (tree init, tsubst_flags_t complain /* = tf_warning_or_error */)
     }
 }
 
+/* Like get_target_expr, but for an internal detail like a cleanup flag or loop
+   iterator.  These variables should not be extended by extend_all_temps.
+
+   This function can also be used for an ephemeral copy of a scalar value such
+   as the pointer to the allocated memory in build_new_1.
+
+   This function should not be used for objects that are part of the abstract
+   C++ semantics such as in stabilize_expr.  */
+
+tree
+get_internal_target_expr (tree init)
+{
+  init = convert_bitfield_to_declared_type (init);
+  tree t = build_target_expr_with_type (init, TREE_TYPE (init),
+                                       tf_warning_or_error);
+  /* No internal variable should have a cleanup on the normal path, and
+     extend_temps_r checks this flag to decide whether to extend.  */
+  CLEANUP_EH_ONLY (t) = true;
+  return t;
+}
+
 /* If EXPR is a bitfield reference, convert it to the declared type of
    the bitfield, and return the resulting expression.  Otherwise,
    return EXPR itself.  */
index e88f6f0ad51d58e71f319e7893543298c6b2f935..2555e9c1b6459659e00149b09ad458b2b4254d0f 100644 (file)
@@ -457,7 +457,7 @@ maybe_push_temp_cleanup (tree sub, vec<tree,va_gc> **flags)
   if (tree cleanup
       = cxx_maybe_build_cleanup (sub, tf_warning_or_error))
     {
-      tree tx = get_target_expr (boolean_true_node);
+      tree tx = get_internal_target_expr (boolean_true_node);
       tree flag = TARGET_EXPR_SLOT (tx);
       CLEANUP_EH_ONLY (tx) = true;
       TARGET_EXPR_CLEANUP (tx) = build3 (COND_EXPR, void_type_node,
diff --git a/gcc/testsuite/g++.dg/eh/ref-temp3.C b/gcc/testsuite/g++.dg/eh/ref-temp3.C
new file mode 100644 (file)
index 0000000..44a729d
--- /dev/null
@@ -0,0 +1,36 @@
+// { dg-do run }
+
+extern "C" void abort();
+
+int a;
+
+struct A {
+  A()
+  {
+    if (a == 1)
+      throw 42;
+    ++a;
+  }
+  ~A()
+  {
+    --a;
+  }
+};
+
+struct B {
+  const A &a1;
+  const A &a2;
+};
+
+void f();
+
+int main()
+{
+  try
+    {
+      B b = { A(), A() };
+    }
+  catch (...) { }
+  if (a != 0)
+    abort ();
+}
diff --git a/gcc/testsuite/g++.dg/eh/ref-temp4.C b/gcc/testsuite/g++.dg/eh/ref-temp4.C
new file mode 100644 (file)
index 0000000..bef31cd
--- /dev/null
@@ -0,0 +1,35 @@
+// PR c++/118856
+// { dg-do run { target c++11 } }
+// { dg-additional-options -O2 }
+
+int a, b, c, d;
+struct A { A () { ++a; ++d; }; ~A () { --a; ++d; }; };
+struct B { B (const A & = A {}) { ++b; ++d; }; ~B () { --b; ++d; }; };
+struct C {
+  C (const B &, const A & = A {}) { ++c; ++d; throw 42; };
+  ~C () { --c; ++d; };
+  int *begin () { return nullptr; };
+  int *end () { return nullptr; };
+};
+
+void
+foo ()
+{
+  for (auto &i : C { B {} })
+    ;
+}
+
+int
+main ()
+{
+  try
+    {
+      foo ();
+      __builtin_abort ();
+    }
+  catch (int x)
+    {
+      if (x != 42 || a || b || c != 1 || d != 7)
+        __builtin_abort ();
+    }
+}