coroutines: Update handling and failure for g-r-o-o-a-f [PR95505]
authorIain Sandoe <iain@sandoe.co.uk>
Sat, 20 Jun 2020 13:01:21 +0000 (14:01 +0100)
committerIain Sandoe <iain@sandoe.co.uk>
Sat, 20 Jun 2020 15:12:32 +0000 (16:12 +0100)
The actual issue is that (in the testcase) std::nothrow is not
available.  So update the handling of the get-return-on-alloc-fail
to include the possibility that std::nothrow might not be
available.

gcc/cp/ChangeLog:

PR c++/95505
* coroutines.cc (morph_fn_to_coro): Update handling of
get-return-object-on-allocation-fail and diagnose missing
std::nothrow.

gcc/testsuite/ChangeLog:

PR c++/95505
* g++.dg/coroutines/pr95505.C: New test.

gcc/cp/coroutines.cc
gcc/testsuite/g++.dg/coroutines/pr95505.C [new file with mode: 0644]

index 11fca9954ac28f4b36bb0fb0b2289aab57d2bfb6..3aaa3d4cd5775b381e05e34dbed85a4273e8cc0f 100644 (file)
@@ -3913,30 +3913,25 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer)
   tree grooaf = NULL_TREE;
   tree dummy_promise = build_dummy_object (get_coroutine_promise_type (orig));
 
-  /* We don't require this, so lookup_promise_method can return NULL...  */
+  /* We don't require this, so lookup_promise_method can return NULL,
+     but, if the lookup succeeds, then the function must be usable.  */
   if (grooaf_meth && BASELINK_P (grooaf_meth))
-    {
-      /* ... but, if the lookup succeeds, then the function must be
-        usable.
-        build_new_method_call () wants a valid pointer to (an empty)  args
-        list in this case.  */
-      vec<tree, va_gc> *args = make_tree_vector ();
-      grooaf = build_new_method_call (dummy_promise, grooaf_meth, &args,
-                                     NULL_TREE, LOOKUP_NORMAL, NULL,
-                                     tf_warning_or_error);
-      release_tree_vector (args);
-    }
+    grooaf = build_new_method_call (dummy_promise, grooaf_meth, NULL,
+                                   NULL_TREE, LOOKUP_NORMAL, NULL,
+                                   tf_warning_or_error);
 
   /* Allocate the frame, this has several possibilities:
      [dcl.fct.def.coroutine] / 9 (part 1)
      The allocation function’s name is looked up in the scope of the promise
      type.  It's not a failure for it to be absent see part 4, below.  */
+
   tree nwname = ovl_op_identifier (false, NEW_EXPR);
-  tree fns = lookup_promise_method (orig, nwname, fn_start,
-                                   /*musthave=*/false);
   tree new_fn = NULL_TREE;
-  if (fns && BASELINK_P (fns))
+
+  if (TYPE_HAS_NEW_OPERATOR (promise_type))
     {
+      tree fns = lookup_promise_method (orig, nwname, fn_start,
+                                   /*musthave=*/true);
       /* [dcl.fct.def.coroutine] / 9 (part 2)
        If the lookup finds an allocation function in the scope of the promise
        type, overload resolution is performed on a function call created by
@@ -3966,30 +3961,29 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer)
            vec_safe_push (args, arg);
        }
 
-      /* We might need to check that the provided function is nothrow.  */
+      /* Note the function selected; we test to see if it's NOTHROW.  */
       tree func;
-      /* Failure is OK for the first attempt.  */
+      /* Failure is not an error for this attempt.  */
       new_fn = build_new_method_call (dummy_promise, fns, &args, NULL,
                                      LOOKUP_NORMAL, &func, tf_none);
       release_tree_vector (args);
 
-      if (!new_fn || new_fn == error_mark_node)
+      if (new_fn == error_mark_node)
        {
          /* [dcl.fct.def.coroutine] / 9 (part 3)
            If no viable function is found, overload resolution is performed
            again on a function call created by passing just the amount of
            space required as an argument of type std::size_t.  */
-         args = make_tree_vector ();
-         vec_safe_push (args, resizeable); /* Space needed.  */
+         args = make_tree_vector_single (resizeable); /* Space needed.  */
          new_fn = build_new_method_call (dummy_promise, fns, &args,
                                          NULL_TREE, LOOKUP_NORMAL, &func,
                                          tf_none);
          release_tree_vector (args);
        }
 
-     /* However, if the initial lookup succeeded, then one of these two
-       options must be available.  */
-    if (!new_fn || new_fn == error_mark_node)
+     /* However, if the promise provides an operator new, then one of these
+       two options must be available.  */
+    if (new_fn == error_mark_node)
       {
        error_at (fn_start, "%qE is provided by %qT but is not usable with"
                  " the function signature %qD", nwname, promise_type, orig);
@@ -3999,7 +3993,7 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer)
       error_at (fn_start, "%qE is provided by %qT but %qE is not marked"
                " %<throw()%> or %<noexcept%>", grooaf, promise_type, nwname);
     }
-  else
+  else /* No operator new in the promise.  */
     {
       /* [dcl.fct.def.coroutine] / 9 (part 4)
         If this lookup fails, the allocation function’s name is looked up in
@@ -4009,7 +4003,6 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer)
       /* build_operator_new_call () will insert size needed as element 0 of
         this, and we might need to append the std::nothrow constant.  */
       vec_alloc (args, 2);
-
       if (grooaf)
        {
          /* [dcl.fct.def.coroutine] / 10 (part 2)
@@ -4023,6 +4016,9 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer)
          tree std_nt = lookup_qualified_name (std_node,
                                               get_identifier ("nothrow"),
                                               0, /*complain=*/true, false);
+         if (!std_nt || std_nt == error_mark_node)
+           error_at (fn_start, "%qE is provided by %qT but %<std::nothrow%> "
+                     "cannot be found", grooaf, promise_type);
          vec_safe_push (args, std_nt);
        }
 
@@ -4037,7 +4033,10 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer)
                                        tf_warning_or_error);
       resizeable = build_call_expr_internal_loc
        (fn_start, IFN_CO_FRAME, size_type_node, 2, frame_size, coro_fp);
-      CALL_EXPR_ARG (new_fn, 0) = resizeable;
+      /* If the operator call fails for some reason, then don't try to
+        amend it.  */
+      if (new_fn != error_mark_node)
+       CALL_EXPR_ARG (new_fn, 0) = resizeable;
 
       release_tree_vector (args);
     }
diff --git a/gcc/testsuite/g++.dg/coroutines/pr95505.C b/gcc/testsuite/g++.dg/coroutines/pr95505.C
new file mode 100644 (file)
index 0000000..a76b827
--- /dev/null
@@ -0,0 +1,26 @@
+#if __has_include (<coroutine>)
+#include <coroutine>
+using namespace std;
+#elif defined (__clang__) && __has_include (<experimental/coroutine>)
+#include <experimental/coroutine>
+namespace std { using namespace experimental; }
+#endif
+
+struct dummy
+{
+    struct promise_type
+    {
+        dummy get_return_object() const noexcept { return {}; }
+        static dummy get_return_object_on_allocation_failure() noexcept { return {}; }
+        std::suspend_always initial_suspend() const noexcept { return {}; }
+        std::suspend_never final_suspend() const noexcept { return {}; }
+        void return_void() const noexcept {}
+        void unhandled_exception() const noexcept {}
+    };
+};
+
+dummy foo() // { dg-error {dummy::promise_type::get_return_object_on_allocation_failure.*but 'std::nothrow' cannot be found} }
+{
+    co_return;
+}
+