[v2] c++/coroutines: check for members we use in handle_types [PR105475]

Message ID 861q37gesj.fsf@aarsen.me
State Committed
Commit 5b4476a165565cb20729c0a97a3f43b060595209
Headers
Series [v2] c++/coroutines: check for members we use in handle_types [PR105475] |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gcc_check--master-arm warning Patch is already merged
linaro-tcwg-bot/tcwg_gcc_check--master-aarch64 warning Patch is already merged

Commit Message

Arsen Arsenović Aug. 2, 2024, 2:50 p.m. UTC
  Jason Merrill <jason@redhat.com> writes:

> I don't think these names need to mention "baselink", but I'm not strongly
> against it.

It doesn't fit with the rest of the codebase either, so I'll rename
them.

> A few other tweaks below:
>
>> @@ -90,6 +90,7 @@ struct GTY((for_user)) coroutine_info
>>     tree self_h_proxy;  /* A handle instance that is used as the proxy for the
>>   			 one that will eventually be allocated in the coroutine
>>   			 frame.  */
>> +  tree from_address_bl;  /* handle_type from_address function (BASELINK).  */
>
> Inserting this here breaks the "Likewise" reference in the next line.

Ah, true.  Swapped it with the next line.

>>     tree promise_proxy; /* Likewise, a proxy promise instance.  */
>>     tree return_void;   /* The expression for p.return_void() if it exists.  */
>>     location_t first_coro_keyword; /* The location of the keyword that made this
>> @@ -389,7 +389,105 @@ find_coro_handle_template_decl (location_t kw)
>>       return handle_decl;
>>   }
>>   -/* Instantiate the handle template for a given promise type.  */
>> +/* Get and validate HANDLE_TYPE#address.  The resulting function, if any, will
>
> :: instead of #

Fixed.

>> +   be a non-overloaded member function that takes no arguments and returns
>> +   void*.  If that is not the case, signals an error and returns NULL_TREE.  */
>> +
>> +static tree
>> +get_handle_type_address_bl (location_t kw, tree handle_type)
>> +{
>> +  tree addr_getter = lookup_member (handle_type, coro_address_identifier, 1,
>> +				    0, tf_warning_or_error);
>> +  if (!addr_getter || addr_getter == error_mark_node)
>> +    {
>> +      error_at (kw, "could not find %qE in %qT",
>> +		coro_address_identifier, handle_type);
>
> How about using qualified_name_lookup_error?

That seems to work, if the following usage is correct:

  tree addr_getter = lookup_member (handle_type, coro_address_identifier, 1,
				    0, tf_warning_or_error);
  if (!addr_getter || addr_getter == error_mark_node)
    {
      qualified_name_lookup_error (handle_type, coro_address_identifier,
				   error_mark_node, kw);
      // notably the error_mark_node above, since it seems that
      // qualified_name_lookup_error accesses DECL.
      return NULL_TREE;
    }

>> +  gcc_assert (TREE_CODE (addr_getter) == BASELINK);
>
> This looks like it will ICE on an invalid handle type (e.g. where "address" is
> a type or data member) instead of giving an error.

Ah, good point.  I'll fix that and add the testcases for it.

>> +  tree fn_t = TREE_TYPE (addr_getter);
>> +  if (TREE_CODE (fn_t) != METHOD_TYPE)
>> +    {
>> +      error_at (kw, "%qE must be a non-overloaded method", addr_getter);
>> +      return NULL_TREE;
>> +    }
>> +
>> +  tree arg = TYPE_ARG_TYPES (fn_t);
>> +  /* Given that this is a method, we have an argument to skip (the this
>> +     pointer).  */
>> +  gcc_checking_assert (TREE_CHAIN (arg));
>
> This assert seems redundant with the following checks.
>
>> +  arg = TREE_CHAIN (arg);
>> +
>> +  /* Check that from_addr has the argument list ().  */
>> +  if (!arg
>> +      || !same_type_p (TREE_VALUE (arg), void_type_node)
>> +      || TREE_CHAIN (arg))
>> +    {
>> +      error_at (kw, "%qE must take no arguments", addr_getter);
>> +      return NULL_TREE;
>> +    }
>
> I think this could just be
>
> if (arg != void_list_node)
>
> as we share the terminal void between all non-varargs function types.

Yes, seems so.  I've addressed both comments above.

>> +  tree ret_t = TREE_TYPE (fn_t);
>> +  if (!same_type_p (ret_t, ptr_type_node))
>> +    {
>> +      error_at (kw, "%qE must return %qT, not %qT",
>> +		addr_getter, ptr_type_node, ret_t);
>> +      return NULL_TREE;
>> +    }
>
> Do you also want to check that it has public access?

lookup_member checks access already, and we can recover from that error
AFAICT, so it might be best to leave it.

(I think leaving the check out permits the user to craft particular
invalid code, e.g. when they place a coroutine inside a class inheriting
from coroutine handle but make the methods protected, but as long as the
compiler does not crash, I think this is good enough - I don't think we
ought to verify here that the declaration matches the spec)

>> @@ -454,10 +552,15 @@ ensure_coro_initialized (location_t loc)
>>           /*  We can also instantiate the void coroutine_handle<>  */
>>         void_coro_handle_type =
>> -	instantiate_coro_handle_for_promise_type (loc, NULL_TREE);
>> +	instantiate_coro_handle_for_promise_type (loc, void_type_node);
>>         if (void_coro_handle_type == NULL_TREE)
>>   	return false;
>>   +      void_coro_handle_address =
>> +	get_handle_type_address_bl (loc, void_coro_handle_type);
>
> The = should be at the beginning of the second line for both variables.

Fixed.

>> @@ -2365,8 +2480,9 @@ build_actor_fn (location_t loc, tree coro_frame_type, tree actor, tree fnbody,
>>     tree ash = build_class_member_access_expr (actor_frame, ash_m, NULL_TREE,
>>   					     false, tf_warning_or_error);
>>     /* So construct the self-handle from the frame address.  */
>> -  tree hfa_m = lookup_member (handle_type, coro_from_address_identifier, 1,
>> -			      0, tf_warning_or_error);
>> +  tree hfa_m = get_coroutine_from_address_baselink (orig);
>> +  /* Should have been set earlier by coro_promise_type_found_p.  */
>> +  gcc_assert (hfa_m);
>
> Maybe move this assert into the new get function?

I was thinking that this isn't really a post-condition of the getter,
but more a precondition of build_actor_fn, hence it being in
build_actor_fn.

v2 attached below.  Currently regression testing it.

Thanks, have a lovely day.
---------- >8 ----------
Currently, it is possible to ICE GCC by giving it sufficiently broken
code, where sufficiently broken means a std::coroutine_handle missing a
default on the promise_type template argument, and missing members.
As the code generator relies on lookups in the coroutine_handle never
failing (and has no way to signal that error), lets do it ahead of time,
save the result, and use that.  This saves us some lookups and allows us
to propagate an error.

PR c++/105475 - coroutines: ICE in coerce_template_parms, at cp/pt.cc:9183

gcc/cp/ChangeLog:

	PR c++/105475
	* coroutines.cc (struct coroutine_info): Add from_address.
	Carries the from_address member we looked up earlier.
	(coro_resume_identifier): Remove.  Unused.
	(coro_init_identifiers): Do not initialize the above.
	(void_coro_handle_address): New variable.  Contains the baselink
	for the std::coroutine_handle<void>::address() instance method.
	(get_handle_type_address): New function.  Looks up and validates
	handle_type::address in a given handle_type.
	(get_handle_type_from_address): New function.  Looks up and
	validates handle_type::from_address in a given handle_type.
	(coro_promise_type_found_p): Remove reliance on
	coroutine_handle<> defaulting the promise type to void.  Store
	get_handle_type_* results where appropriate.
	(get_coroutine_from_address): New helper.  Gets the
	handle_type::from_address BASELINK from a coroutine_info.
	(build_actor_fn): Use the get_coroutine_from_address helper and
	void_coro_handle_address.

gcc/testsuite/ChangeLog:

	PR c++/105475
	* g++.dg/coroutines/pr103868.C: Add std::coroutine_handle
	members we check for now.
	* g++.dg/coroutines/pr105287.C: Ditto.
	* g++.dg/coroutines/pr105301.C: Ditto.
	* g++.dg/coroutines/pr94528.C: Ditto.
	* g++.dg/coroutines/pr94879-folly-1.C: Ditto.
	* g++.dg/coroutines/pr94883-folly-2.C: Ditto.
	* g++.dg/coroutines/pr98118.C: Ditto.
	* g++.dg/coroutines/pr105475.C: New test.
	* g++.dg/coroutines/pr105475-1.C: New test.
	* g++.dg/coroutines/pr105475-2.C: New test.
	* g++.dg/coroutines/pr105475-3.C: New test.
	* g++.dg/coroutines/pr105475-4.C: New test.
	* g++.dg/coroutines/pr105475-5.C: New test.
	* g++.dg/coroutines/pr105475-6.C: New test.
	* g++.dg/coroutines/pr105475-broken-spec.C: New test.
	* g++.dg/coroutines/pr105475-broken-spec-2.C: New test.
---
 gcc/cp/coroutines.cc                          | 138 ++++++++++++++++--
 gcc/testsuite/g++.dg/coroutines/pr103868.C    |   2 +
 gcc/testsuite/g++.dg/coroutines/pr105287.C    |   2 +
 gcc/testsuite/g++.dg/coroutines/pr105301.C    |   5 +-
 gcc/testsuite/g++.dg/coroutines/pr105475-1.C  |  27 ++++
 gcc/testsuite/g++.dg/coroutines/pr105475-2.C  |  29 ++++
 gcc/testsuite/g++.dg/coroutines/pr105475-3.C  |  29 ++++
 gcc/testsuite/g++.dg/coroutines/pr105475-4.C  |  41 ++++++
 gcc/testsuite/g++.dg/coroutines/pr105475-5.C  |  29 ++++
 gcc/testsuite/g++.dg/coroutines/pr105475-6.C  |  29 ++++
 .../coroutines/pr105475-broken-spec-2.C       |  33 +++++
 .../g++.dg/coroutines/pr105475-broken-spec.C  |  29 ++++
 gcc/testsuite/g++.dg/coroutines/pr105475.C    |  28 ++++
 gcc/testsuite/g++.dg/coroutines/pr94528.C     |  11 +-
 .../g++.dg/coroutines/pr94879-folly-1.C       |  10 +-
 .../g++.dg/coroutines/pr94883-folly-2.C       |  10 +-
 gcc/testsuite/g++.dg/coroutines/pr98118.C     |  10 +-
 17 files changed, 439 insertions(+), 23 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr105475-1.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr105475-2.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr105475-3.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr105475-4.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr105475-5.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr105475-6.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr105475-broken-spec-2.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr105475-broken-spec.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr105475.C
  

Comments

Jason Merrill Aug. 2, 2024, 4:58 p.m. UTC | #1
On 8/2/24 10:50 AM, Arsen Arsenović wrote:
> Jason Merrill <jason@redhat.com> writes:
> 
>> I don't think these names need to mention "baselink", but I'm not strongly
>> against it.
> 
> It doesn't fit with the rest of the codebase either, so I'll rename
> them.
> 
>> A few other tweaks below:
>>
>>> @@ -90,6 +90,7 @@ struct GTY((for_user)) coroutine_info
>>>      tree self_h_proxy;  /* A handle instance that is used as the proxy for the
>>>    			 one that will eventually be allocated in the coroutine
>>>    			 frame.  */
>>> +  tree from_address_bl;  /* handle_type from_address function (BASELINK).  */
>>
>> Inserting this here breaks the "Likewise" reference in the next line.
> 
> Ah, true.  Swapped it with the next line.
> 
>>>      tree promise_proxy; /* Likewise, a proxy promise instance.  */
>>>      tree return_void;   /* The expression for p.return_void() if it exists.  */
>>>      location_t first_coro_keyword; /* The location of the keyword that made this
>>> @@ -389,7 +389,105 @@ find_coro_handle_template_decl (location_t kw)
>>>        return handle_decl;
>>>    }
>>>    -/* Instantiate the handle template for a given promise type.  */
>>> +/* Get and validate HANDLE_TYPE#address.  The resulting function, if any, will
>>
>> :: instead of #
> 
> Fixed.
> 
>>> +   be a non-overloaded member function that takes no arguments and returns
>>> +   void*.  If that is not the case, signals an error and returns NULL_TREE.  */
>>> +
>>> +static tree
>>> +get_handle_type_address_bl (location_t kw, tree handle_type)
>>> +{
>>> +  tree addr_getter = lookup_member (handle_type, coro_address_identifier, 1,
>>> +				    0, tf_warning_or_error);
>>> +  if (!addr_getter || addr_getter == error_mark_node)
>>> +    {
>>> +      error_at (kw, "could not find %qE in %qT",
>>> +		coro_address_identifier, handle_type);
>>
>> How about using qualified_name_lookup_error?
> 
> That seems to work, if the following usage is correct:
> 
>    tree addr_getter = lookup_member (handle_type, coro_address_identifier, 1,
> 				    0, tf_warning_or_error);
>    if (!addr_getter || addr_getter == error_mark_node)
>      {
>        qualified_name_lookup_error (handle_type, coro_address_identifier,
> 				   error_mark_node, kw);
>        // notably the error_mark_node above, since it seems that
>        // qualified_name_lookup_error accesses DECL.
>        return NULL_TREE;
>      }
> 
>>> +  gcc_assert (TREE_CODE (addr_getter) == BASELINK);
>>
>> This looks like it will ICE on an invalid handle type (e.g. where "address" is
>> a type or data member) instead of giving an error.
> 
> Ah, good point.  I'll fix that and add the testcases for it.
> 
>>> +  tree fn_t = TREE_TYPE (addr_getter);
>>> +  if (TREE_CODE (fn_t) != METHOD_TYPE)
>>> +    {
>>> +      error_at (kw, "%qE must be a non-overloaded method", addr_getter);
>>> +      return NULL_TREE;
>>> +    }
>>> +
>>> +  tree arg = TYPE_ARG_TYPES (fn_t);
>>> +  /* Given that this is a method, we have an argument to skip (the this
>>> +     pointer).  */
>>> +  gcc_checking_assert (TREE_CHAIN (arg));
>>
>> This assert seems redundant with the following checks.
>>
>>> +  arg = TREE_CHAIN (arg);
>>> +
>>> +  /* Check that from_addr has the argument list ().  */
>>> +  if (!arg
>>> +      || !same_type_p (TREE_VALUE (arg), void_type_node)
>>> +      || TREE_CHAIN (arg))
>>> +    {
>>> +      error_at (kw, "%qE must take no arguments", addr_getter);
>>> +      return NULL_TREE;
>>> +    }
>>
>> I think this could just be
>>
>> if (arg != void_list_node)
>>
>> as we share the terminal void between all non-varargs function types.
> 
> Yes, seems so.  I've addressed both comments above.
> 
>>> +  tree ret_t = TREE_TYPE (fn_t);
>>> +  if (!same_type_p (ret_t, ptr_type_node))
>>> +    {
>>> +      error_at (kw, "%qE must return %qT, not %qT",
>>> +		addr_getter, ptr_type_node, ret_t);
>>> +      return NULL_TREE;
>>> +    }
>>
>> Do you also want to check that it has public access?
> 
> lookup_member checks access already, and we can recover from that error
> AFAICT, so it might be best to leave it.
> 
> (I think leaving the check out permits the user to craft particular
> invalid code, e.g. when they place a coroutine inside a class inheriting
> from coroutine handle but make the methods protected, but as long as the
> compiler does not crash, I think this is good enough - I don't think we
> ought to verify here that the declaration matches the spec)
> 
>>> @@ -454,10 +552,15 @@ ensure_coro_initialized (location_t loc)
>>>            /*  We can also instantiate the void coroutine_handle<>  */
>>>          void_coro_handle_type =
>>> -	instantiate_coro_handle_for_promise_type (loc, NULL_TREE);
>>> +	instantiate_coro_handle_for_promise_type (loc, void_type_node);
>>>          if (void_coro_handle_type == NULL_TREE)
>>>    	return false;
>>>    +      void_coro_handle_address =
>>> +	get_handle_type_address_bl (loc, void_coro_handle_type);
>>
>> The = should be at the beginning of the second line for both variables.
> 
> Fixed.
> 
>>> @@ -2365,8 +2480,9 @@ build_actor_fn (location_t loc, tree coro_frame_type, tree actor, tree fnbody,
>>>      tree ash = build_class_member_access_expr (actor_frame, ash_m, NULL_TREE,
>>>    					     false, tf_warning_or_error);
>>>      /* So construct the self-handle from the frame address.  */
>>> -  tree hfa_m = lookup_member (handle_type, coro_from_address_identifier, 1,
>>> -			      0, tf_warning_or_error);
>>> +  tree hfa_m = get_coroutine_from_address_baselink (orig);
>>> +  /* Should have been set earlier by coro_promise_type_found_p.  */
>>> +  gcc_assert (hfa_m);
>>
>> Maybe move this assert into the new get function?
> 
> I was thinking that this isn't really a post-condition of the getter,
> but more a precondition of build_actor_fn, hence it being in
> build_actor_fn.
> 
> v2 attached below.  Currently regression testing it.

OK.

> Thanks, have a lovely day.
> ---------- >8 ----------
> Currently, it is possible to ICE GCC by giving it sufficiently broken
> code, where sufficiently broken means a std::coroutine_handle missing a
> default on the promise_type template argument, and missing members.
> As the code generator relies on lookups in the coroutine_handle never
> failing (and has no way to signal that error), lets do it ahead of time,
> save the result, and use that.  This saves us some lookups and allows us
> to propagate an error.
> 
> PR c++/105475 - coroutines: ICE in coerce_template_parms, at cp/pt.cc:9183
> 
> gcc/cp/ChangeLog:
> 
> 	PR c++/105475
> 	* coroutines.cc (struct coroutine_info): Add from_address.
> 	Carries the from_address member we looked up earlier.
> 	(coro_resume_identifier): Remove.  Unused.
> 	(coro_init_identifiers): Do not initialize the above.
> 	(void_coro_handle_address): New variable.  Contains the baselink
> 	for the std::coroutine_handle<void>::address() instance method.
> 	(get_handle_type_address): New function.  Looks up and validates
> 	handle_type::address in a given handle_type.
> 	(get_handle_type_from_address): New function.  Looks up and
> 	validates handle_type::from_address in a given handle_type.
> 	(coro_promise_type_found_p): Remove reliance on
> 	coroutine_handle<> defaulting the promise type to void.  Store
> 	get_handle_type_* results where appropriate.
> 	(get_coroutine_from_address): New helper.  Gets the
> 	handle_type::from_address BASELINK from a coroutine_info.
> 	(build_actor_fn): Use the get_coroutine_from_address helper and
> 	void_coro_handle_address.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR c++/105475
> 	* g++.dg/coroutines/pr103868.C: Add std::coroutine_handle
> 	members we check for now.
> 	* g++.dg/coroutines/pr105287.C: Ditto.
> 	* g++.dg/coroutines/pr105301.C: Ditto.
> 	* g++.dg/coroutines/pr94528.C: Ditto.
> 	* g++.dg/coroutines/pr94879-folly-1.C: Ditto.
> 	* g++.dg/coroutines/pr94883-folly-2.C: Ditto.
> 	* g++.dg/coroutines/pr98118.C: Ditto.
> 	* g++.dg/coroutines/pr105475.C: New test.
> 	* g++.dg/coroutines/pr105475-1.C: New test.
> 	* g++.dg/coroutines/pr105475-2.C: New test.
> 	* g++.dg/coroutines/pr105475-3.C: New test.
> 	* g++.dg/coroutines/pr105475-4.C: New test.
> 	* g++.dg/coroutines/pr105475-5.C: New test.
> 	* g++.dg/coroutines/pr105475-6.C: New test.
> 	* g++.dg/coroutines/pr105475-broken-spec.C: New test.
> 	* g++.dg/coroutines/pr105475-broken-spec-2.C: New test.
> ---
>   gcc/cp/coroutines.cc                          | 138 ++++++++++++++++--
>   gcc/testsuite/g++.dg/coroutines/pr103868.C    |   2 +
>   gcc/testsuite/g++.dg/coroutines/pr105287.C    |   2 +
>   gcc/testsuite/g++.dg/coroutines/pr105301.C    |   5 +-
>   gcc/testsuite/g++.dg/coroutines/pr105475-1.C  |  27 ++++
>   gcc/testsuite/g++.dg/coroutines/pr105475-2.C  |  29 ++++
>   gcc/testsuite/g++.dg/coroutines/pr105475-3.C  |  29 ++++
>   gcc/testsuite/g++.dg/coroutines/pr105475-4.C  |  41 ++++++
>   gcc/testsuite/g++.dg/coroutines/pr105475-5.C  |  29 ++++
>   gcc/testsuite/g++.dg/coroutines/pr105475-6.C  |  29 ++++
>   .../coroutines/pr105475-broken-spec-2.C       |  33 +++++
>   .../g++.dg/coroutines/pr105475-broken-spec.C  |  29 ++++
>   gcc/testsuite/g++.dg/coroutines/pr105475.C    |  28 ++++
>   gcc/testsuite/g++.dg/coroutines/pr94528.C     |  11 +-
>   .../g++.dg/coroutines/pr94879-folly-1.C       |  10 +-
>   .../g++.dg/coroutines/pr94883-folly-2.C       |  10 +-
>   gcc/testsuite/g++.dg/coroutines/pr98118.C     |  10 +-
>   17 files changed, 439 insertions(+), 23 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/coroutines/pr105475-1.C
>   create mode 100644 gcc/testsuite/g++.dg/coroutines/pr105475-2.C
>   create mode 100644 gcc/testsuite/g++.dg/coroutines/pr105475-3.C
>   create mode 100644 gcc/testsuite/g++.dg/coroutines/pr105475-4.C
>   create mode 100644 gcc/testsuite/g++.dg/coroutines/pr105475-5.C
>   create mode 100644 gcc/testsuite/g++.dg/coroutines/pr105475-6.C
>   create mode 100644 gcc/testsuite/g++.dg/coroutines/pr105475-broken-spec-2.C
>   create mode 100644 gcc/testsuite/g++.dg/coroutines/pr105475-broken-spec.C
>   create mode 100644 gcc/testsuite/g++.dg/coroutines/pr105475.C
> 
> diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
> index 91bbe6b0a0eb..0dcdeace5ff8 100644
> --- a/gcc/cp/coroutines.cc
> +++ b/gcc/cp/coroutines.cc
> @@ -91,6 +91,7 @@ struct GTY((for_user)) coroutine_info
>   			 one that will eventually be allocated in the coroutine
>   			 frame.  */
>     tree promise_proxy; /* Likewise, a proxy promise instance.  */
> +  tree from_address;  /* handle_type from_address function.  */
>     tree return_void;   /* The expression for p.return_void() if it exists.  */
>     location_t first_coro_keyword; /* The location of the keyword that made this
>   				    function into a coroutine.  */
> @@ -203,7 +204,6 @@ static GTY(()) tree coro_final_suspend_identifier;
>   static GTY(()) tree coro_return_void_identifier;
>   static GTY(()) tree coro_return_value_identifier;
>   static GTY(()) tree coro_yield_value_identifier;
> -static GTY(()) tree coro_resume_identifier;
>   static GTY(()) tree coro_address_identifier;
>   static GTY(()) tree coro_from_address_identifier;
>   static GTY(()) tree coro_get_return_object_identifier;
> @@ -243,7 +243,6 @@ coro_init_identifiers ()
>     coro_return_void_identifier = get_identifier ("return_void");
>     coro_return_value_identifier = get_identifier ("return_value");
>     coro_yield_value_identifier = get_identifier ("yield_value");
> -  coro_resume_identifier = get_identifier ("resume");
>     coro_address_identifier = get_identifier ("address");
>     coro_from_address_identifier = get_identifier ("from_address");
>     coro_get_return_object_identifier = get_identifier ("get_return_object");
> @@ -271,6 +270,7 @@ coro_init_identifiers ()
>   static GTY(()) tree coro_traits_templ;
>   static GTY(()) tree coro_handle_templ;
>   static GTY(()) tree void_coro_handle_type;
> +static GTY(()) tree void_coro_handle_address;
>   
>   /* ================= Parse, Semantics and Type checking ================= */
>   
> @@ -389,7 +389,97 @@ find_coro_handle_template_decl (location_t kw)
>       return handle_decl;
>   }
>   
> -/* Instantiate the handle template for a given promise type.  */
> +/* Get and validate HANDLE_TYPE::address.  The resulting function, if any, will
> +   be a non-overloaded member function that takes no arguments and returns
> +   void*.  If that is not the case, signals an error and returns NULL_TREE.  */
> +
> +static tree
> +get_handle_type_address (location_t kw, tree handle_type)
> +{
> +  tree addr_getter = lookup_member (handle_type, coro_address_identifier, 1,
> +				    0, tf_warning_or_error);
> +  if (!addr_getter || addr_getter == error_mark_node)
> +    {
> +      qualified_name_lookup_error (handle_type, coro_address_identifier,
> +				   error_mark_node, kw);
> +      return NULL_TREE;
> +    }
> +
> +  if (!BASELINK_P (addr_getter)
> +      || TREE_CODE (TREE_TYPE (addr_getter)) != METHOD_TYPE)
> +    {
> +      error_at (kw, "%qE must be a non-overloaded method", addr_getter);
> +      return NULL_TREE;
> +    }
> +
> +  tree fn_t = TREE_TYPE (addr_getter);
> +  tree arg = TYPE_ARG_TYPES (fn_t);
> +
> +  /* Skip the 'this' pointer.  */
> +  arg = TREE_CHAIN (arg);
> +
> +  /* Check that from_addr has the argument list ().  */
> +  if (arg != void_list_node)
> +    {
> +      error_at (kw, "%qE must take no arguments", addr_getter);
> +      return NULL_TREE;
> +    }
> +
> +  tree ret_t = TREE_TYPE (fn_t);
> +  if (!same_type_p (ret_t, ptr_type_node))
> +    {
> +      error_at (kw, "%qE must return %qT, not %qT",
> +		addr_getter, ptr_type_node, ret_t);
> +      return NULL_TREE;
> +    }
> +
> +  return addr_getter;
> +}
> +
> +/* Get and validate HANDLE_TYPE::from_address.  The resulting function, if
> +   any, will be a non-overloaded static function that takes a single void* and
> +   returns HANDLE_TYPE.  If that is not the case, signals an error and returns
> +   NULL_TREE.  */
> +
> +static tree
> +get_handle_type_from_address (location_t kw, tree handle_type)
> +{
> +  tree from_addr = lookup_member (handle_type, coro_from_address_identifier, 1,
> +				  0, tf_warning_or_error);
> +  if (!from_addr || from_addr == error_mark_node)
> +    {
> +      qualified_name_lookup_error (handle_type, coro_from_address_identifier,
> +				   error_mark_node, kw);
> +      return NULL_TREE;
> +    }
> +  if (!BASELINK_P (from_addr)
> +      || TREE_CODE (TREE_TYPE (from_addr)) != FUNCTION_TYPE)
> +    {
> +      error_at (kw, "%qE must be a non-overloaded static function", from_addr);
> +      return NULL_TREE;
> +    }
> +
> +  tree fn_t = TREE_TYPE (from_addr);
> +  tree arg = TYPE_ARG_TYPES (fn_t);
> +  /* Check that from_addr has the argument list (void*).  */
> +  if (!arg
> +      || !same_type_p (TREE_VALUE (arg), ptr_type_node)
> +      || TREE_CHAIN (arg) != void_list_node)
> +    {
> +      error_at (kw, "%qE must take a single %qT", from_addr, ptr_type_node);
> +      return NULL_TREE;
> +    }
> +
> +  tree ret_t = TREE_TYPE (fn_t);
> +  if (!same_type_p (ret_t, handle_type))
> +    {
> +      error_at (kw, "%qE must return %qT, not %qT",
> +		from_addr, handle_type, ret_t);
> +      return NULL_TREE;
> +    }
> +
> +  return from_addr;
> +}
>   
>   static tree
>   instantiate_coro_handle_for_promise_type (location_t kw, tree promise_type)
> @@ -453,11 +543,16 @@ ensure_coro_initialized (location_t loc)
>   	return false;
>   
>         /*  We can also instantiate the void coroutine_handle<>  */
> -      void_coro_handle_type =
> -	instantiate_coro_handle_for_promise_type (loc, NULL_TREE);
> +      void_coro_handle_type
> +	= instantiate_coro_handle_for_promise_type (loc, void_type_node);
>         if (void_coro_handle_type == NULL_TREE)
>   	return false;
>   
> +      void_coro_handle_address
> +	= get_handle_type_address (loc, void_coro_handle_type);
> +      if (!void_coro_handle_address)
> +	return false;
> +
>         /* A table to hold the state, per coroutine decl.  */
>         gcc_checking_assert (coroutine_info_table == NULL);
>         coroutine_info_table =
> @@ -552,13 +647,17 @@ coro_promise_type_found_p (tree fndecl, location_t loc)
>   	}
>   
>         /* Try to find the handle type for the promise.  */
> -      tree handle_type =
> -	instantiate_coro_handle_for_promise_type (loc, coro_info->promise_type);
> +      tree handle_type
> +	= instantiate_coro_handle_for_promise_type (loc, coro_info->promise_type);
>         if (handle_type == NULL_TREE)
>   	return false;
> +      tree from_address = get_handle_type_from_address (loc, handle_type);
> +      if (from_address == NULL_TREE)
> +	return false;
>   
>         /* Complete this, we're going to use it.  */
>         coro_info->handle_type = complete_type_or_else (handle_type, fndecl);
> +      coro_info->from_address = from_address;
>   
>         /* Diagnostic would be emitted by complete_type_or_else.  */
>         if (!coro_info->handle_type)
> @@ -674,6 +773,15 @@ get_coroutine_promise_proxy (tree decl)
>     return NULL_TREE;
>   }
>   
> +static tree
> +get_coroutine_from_address (tree decl)
> +{
> +  if (coroutine_info *info = get_coroutine_info (decl))
> +    return info->from_address;
> +
> +  return NULL_TREE;
> +}
> +
>   static tree
>   lookup_promise_method (tree fndecl, tree member_id, location_t loc,
>   		       bool musthave)
> @@ -2232,7 +2340,6 @@ build_actor_fn (location_t loc, tree coro_frame_type, tree actor, tree fnbody,
>   {
>     verify_stmt_tree (fnbody);
>     /* Some things we inherit from the original function.  */
> -  tree handle_type = get_coroutine_handle_type (orig);
>     tree promise_type = get_coroutine_promise_type (orig);
>     tree promise_proxy = get_coroutine_promise_proxy (orig);
>   
> @@ -2392,8 +2499,9 @@ build_actor_fn (location_t loc, tree coro_frame_type, tree actor, tree fnbody,
>     tree ash = build_class_member_access_expr (actor_frame, ash_m, NULL_TREE,
>   					     false, tf_warning_or_error);
>     /* So construct the self-handle from the frame address.  */
> -  tree hfa_m = lookup_member (handle_type, coro_from_address_identifier, 1,
> -			      0, tf_warning_or_error);
> +  tree hfa_m = get_coroutine_from_address (orig);
> +  /* Should have been set earlier by coro_promise_type_found_p.  */
> +  gcc_assert (hfa_m);
>   
>     r = build1 (CONVERT_EXPR, build_pointer_type (void_type_node), actor_fp);
>     vec<tree, va_gc> *args = make_tree_vector_single (r);
> @@ -2488,12 +2596,14 @@ build_actor_fn (location_t loc, tree coro_frame_type, tree actor, tree fnbody,
>     r = build_stmt (loc, LABEL_EXPR, continue_label);
>     add_stmt (r);
>   
> +  /* Should have been set earlier by the coro_initialized code.  */
> +  gcc_assert (void_coro_handle_address);
> +
>     /* We want to force a tail-call even for O0/1, so this expands the resume
>        call into its underlying implementation.  */
> -  tree addr = lookup_member (void_coro_handle_type, coro_address_identifier,
> -			       1, 0, tf_warning_or_error);
> -  addr = build_new_method_call (continuation, addr, NULL, NULL_TREE,
> -				  LOOKUP_NORMAL, NULL, tf_warning_or_error);
> +  tree addr = build_new_method_call (continuation, void_coro_handle_address,
> +				     NULL, NULL_TREE, LOOKUP_NORMAL, NULL,
> +				     tf_warning_or_error);
>     tree resume = build_call_expr_loc
>       (loc, builtin_decl_explicit (BUILT_IN_CORO_RESUME), 1, addr);
>   
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr103868.C b/gcc/testsuite/g++.dg/coroutines/pr103868.C
> index 8687effb5637..fd05769db3db 100644
> --- a/gcc/testsuite/g++.dg/coroutines/pr103868.C
> +++ b/gcc/testsuite/g++.dg/coroutines/pr103868.C
> @@ -79,6 +79,8 @@ namespace std {
>   template <typename...> struct coroutine_traits;
>   template <typename = void> struct coroutine_handle {
>     operator coroutine_handle<>();
> +  static coroutine_handle from_address(void*);
> +  void* address();
>   };
>   struct suspend_always {
>     bool await_ready();
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr105287.C b/gcc/testsuite/g++.dg/coroutines/pr105287.C
> index 9790945287da..c54d1fd5b845 100644
> --- a/gcc/testsuite/g++.dg/coroutines/pr105287.C
> +++ b/gcc/testsuite/g++.dg/coroutines/pr105287.C
> @@ -4,6 +4,8 @@ namespace std {
>   template <typename _Result> struct coroutine_traits : _Result {};
>   template <typename = void> struct coroutine_handle {
>     operator coroutine_handle<>();
> +  static coroutine_handle from_address(void*);
> +  void* address();
>   };
>   }
>   struct coro1 {
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr105301.C b/gcc/testsuite/g++.dg/coroutines/pr105301.C
> index 33a0b03cf5d9..b3a3fe1365e3 100644
> --- a/gcc/testsuite/g++.dg/coroutines/pr105301.C
> +++ b/gcc/testsuite/g++.dg/coroutines/pr105301.C
> @@ -29,7 +29,10 @@ struct suspend_always {
>   
>   namespace std {
>   template <class PromiseType = void>
> -struct coroutine_handle {};
> +struct coroutine_handle {
> +  static coroutine_handle from_address(void*);
> +  void* address();
> +};
>   }
>   
>   struct bad_promise_6 {
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr105475-1.C b/gcc/testsuite/g++.dg/coroutines/pr105475-1.C
> new file mode 100644
> index 000000000000..8fc0a70e5d2a
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/coroutines/pr105475-1.C
> @@ -0,0 +1,27 @@
> +// https://gcc.gnu.org/PR105475
> +// Test the case where we have everything we need for coroutine_handle<void>,
> +// but not for coroutine_handle<promise>.
> +namespace std {
> +struct awaitable {
> +  bool await_ready() noexcept { return false; }
> +  void await_suspend(auto) noexcept {}
> +  bool await_resume() noexcept { return true; }
> +};
> +
> +template <typename T> struct coroutine_handle {
> +  static coroutine_handle from_address(void *address);
> +};
> +
> +template <typename T = void> struct coroutine_traits {
> +  struct promise_type {
> +    awaitable initial_suspend() { return {}; }
> +    awaitable final_suspend() noexcept { return {}; }
> +    void return_void() {}
> +    T get_return_object() { return T(); }
> +    void unhandled_exception() {}
> +  };
> +};
> +} // namespace std
> +
> +void foo() { co_return; }
> +// { dg-error "'address' is not a member of 'std::coroutine_handle<void>'" "" { target *-*-* } {.-1} }
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr105475-2.C b/gcc/testsuite/g++.dg/coroutines/pr105475-2.C
> new file mode 100644
> index 000000000000..7c9d76e4f415
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/coroutines/pr105475-2.C
> @@ -0,0 +1,29 @@
> +// { dg-do compile }
> +// https://gcc.gnu.org/PR105475
> +// Test the case where we lack 'from_address' (i.e. the member required from
> +// coroutine_handle<void>), but not address()
> +namespace std {
> +
> +struct awaitable {
> +  bool await_ready() noexcept { return false; }
> +  void await_suspend(auto) noexcept {}
> +  bool await_resume() noexcept { return true; }
> +};
> +
> +template <typename T> struct coroutine_handle {
> +  void* address();
> +};
> +
> +template <typename T = void> struct coroutine_traits {
> +  struct promise_type {
> +    awaitable initial_suspend() { return {}; }
> +    awaitable final_suspend() noexcept { return {}; }
> +    void return_void() {}
> +    T get_return_object() { return T(); }
> +    void unhandled_exception() {}
> +  };
> +};
> +} // namespace std
> +
> +void foo() { co_return; }
> +// { dg-error "'from_address' is not a member of 'std::coroutine_handle<std::coroutine_traits<void>::promise_type>'" "" { target *-*-* } {.-1} }
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr105475-3.C b/gcc/testsuite/g++.dg/coroutines/pr105475-3.C
> new file mode 100644
> index 000000000000..75e0624ad719
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/coroutines/pr105475-3.C
> @@ -0,0 +1,29 @@
> +// https://gcc.gnu.org/PR105475
> +// Test the case where we create a non-static from_address.
> +#include <coroutine>
> +
> +struct promise;
> +
> +struct task
> +{ using promise_type = promise; };
> +
> +struct promise
> +{
> +  void return_void () {}
> +  std::suspend_never final_suspend() noexcept { return {}; }
> +  std::suspend_never initial_suspend() noexcept { return {}; }
> +  void unhandled_exception () {}
> +  task get_return_object() { return {}; }
> +};
> +
> +/* Invalid.  */
> +namespace std
> +{
> +  template<>
> +  struct coroutine_handle<promise>
> +  { coroutine_handle from_address(void*); };
> +};
> +
> +task foo()
> +{ co_return; }
> +// { dg-error "std::__n4861::coroutine_handle<promise>::from_address' must be a non-overloaded static function" "" { target *-*-* } {.-1} }
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr105475-4.C b/gcc/testsuite/g++.dg/coroutines/pr105475-4.C
> new file mode 100644
> index 000000000000..398d5c894e26
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/coroutines/pr105475-4.C
> @@ -0,0 +1,41 @@
> +// https://gcc.gnu.org/PR105475
> +// Test the case where we create a static address.
> +struct promise;
> +
> +namespace std
> +{
> +  template<typename>
> +  struct coroutine_handle
> +  {
> +    static coroutine_handle from_address(void*);
> +
> +    static void* address();
> +  };
> +
> +  struct suspend_never
> +  {
> +    bool await_ready();
> +    void await_suspend(coroutine_handle<void>);
> +    void await_resume();
> +  };
> +
> +  template<typename T>
> +  struct coroutine_traits
> +  { using promise_type = typename T::promise_type; };
> +};
> +
> +
> +struct task
> +{ using promise_type = promise; };
> +
> +struct promise
> +{
> +  void return_void () {}
> +  std::suspend_never final_suspend() noexcept { return {}; }
> +  std::suspend_never initial_suspend() noexcept { return {}; }
> +  void unhandled_exception () {}
> +  task get_return_object() { return {}; }
> +};
> +
> +task foo() { co_return; }
> +// { dg-error "std::coroutine_handle<void>::address' must be a non-overloaded method" "" { target *-*-* } {.-1} }
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr105475-5.C b/gcc/testsuite/g++.dg/coroutines/pr105475-5.C
> new file mode 100644
> index 000000000000..aeb96b0fb338
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/coroutines/pr105475-5.C
> @@ -0,0 +1,29 @@
> +// https://gcc.gnu.org/PR105475
> +// Test the case where we have type members instead of functions for
> +// from_address and address.
> +namespace std {
> +struct awaitable {
> +  bool await_ready() noexcept { return false; }
> +  void await_suspend(auto) noexcept {}
> +  bool await_resume() noexcept { return true; }
> +};
> +
> +template <typename T>
> +struct coroutine_handle {
> +  struct from_address {};
> +  struct address {};
> +};
> +
> +template <typename T = void> struct coroutine_traits {
> +  struct promise_type {
> +    awaitable initial_suspend() { return {}; }
> +    awaitable final_suspend() noexcept { return {}; }
> +    void return_void() {}
> +    T get_return_object() { return T(); }
> +    void unhandled_exception() {}
> +  };
> +};
> +} // namespace std
> +
> +void foo() { co_return; }
> +// { dg-error "'std::coroutine_handle<void>::address' must be a non-overloaded method" "" { target *-*-* } {.-1} }
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr105475-6.C b/gcc/testsuite/g++.dg/coroutines/pr105475-6.C
> new file mode 100644
> index 000000000000..c7059f292e1c
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/coroutines/pr105475-6.C
> @@ -0,0 +1,29 @@
> +// https://gcc.gnu.org/PR105475
> +// Test the case where we have data members instead of functions for
> +// from_address and address.
> +namespace std {
> +struct awaitable {
> +  bool await_ready() noexcept { return false; }
> +  void await_suspend(auto) noexcept {}
> +  bool await_resume() noexcept { return true; }
> +};
> +
> +template <typename T>
> +struct coroutine_handle {
> +  static int from_address;
> +  int address;
> +};
> +
> +template <typename T = void> struct coroutine_traits {
> +  struct promise_type {
> +    awaitable initial_suspend() { return {}; }
> +    awaitable final_suspend() noexcept { return {}; }
> +    void return_void() {}
> +    T get_return_object() { return T(); }
> +    void unhandled_exception() {}
> +  };
> +};
> +} // namespace std
> +
> +void foo() { co_return; }
> +// { dg-error "'std::coroutine_handle<void>::address' must be a non-overloaded method" "" { target *-*-* } {.-1} }
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr105475-broken-spec-2.C b/gcc/testsuite/g++.dg/coroutines/pr105475-broken-spec-2.C
> new file mode 100644
> index 000000000000..8f469ab00952
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/coroutines/pr105475-broken-spec-2.C
> @@ -0,0 +1,33 @@
> +// https://gcc.gnu.org/PR105475
> +// Test the case where we specialize coroutine_handle<void> and remove
> +// 'address'.
> +namespace std {
> +
> +struct awaitable {
> +  bool await_ready() noexcept { return false; }
> +  void await_suspend(auto) noexcept {}
> +  bool await_resume() noexcept { return true; }
> +};
> +
> +template <typename T> struct coroutine_handle {
> +  static coroutine_handle from_address(void *address);
> +  void* address();
> +};
> +
> +template <> struct coroutine_handle<void> {
> +  static coroutine_handle from_address(void *address);
> +};
> +
> +template <typename T = void> struct coroutine_traits {
> +  struct promise_type {
> +    awaitable initial_suspend() { return {}; }
> +    awaitable final_suspend() noexcept { return {}; }
> +    void return_void() {}
> +    T get_return_object() { return T(); }
> +    void unhandled_exception() {}
> +  };
> +};
> +} // namespace std
> +
> +void foo() { co_return; }
> +// { dg-error "'address' is not a member of 'std::coroutine_handle<void>'" "" { target *-*-* } {.-1} }
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr105475-broken-spec.C b/gcc/testsuite/g++.dg/coroutines/pr105475-broken-spec.C
> new file mode 100644
> index 000000000000..07bf9b06aed9
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/coroutines/pr105475-broken-spec.C
> @@ -0,0 +1,29 @@
> +// https://gcc.gnu.org/PR105475
> +// Test the case where we specialize coroutine_handle and break it.
> +#include <coroutine>
> +
> +struct promise;
> +
> +struct task
> +{ using promise_type = promise; };
> +
> +struct promise
> +{
> +  void return_void () {}
> +  std::suspend_never final_suspend() noexcept { return {}; }
> +  std::suspend_never initial_suspend() noexcept { return {}; }
> +  void unhandled_exception () {}
> +  task get_return_object() { return {}; }
> +};
> +
> +/* Invalid.  */
> +namespace std
> +{
> +  template<>
> +  struct coroutine_handle<promise>
> +  {};
> +};
> +
> +task foo()
> +{ co_return; }
> +// { dg-error "'from_address' is not a member of 'std::__n4861::coroutine_handle<promise>'" "" { target *-*-* } {.-1} }
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr105475.C b/gcc/testsuite/g++.dg/coroutines/pr105475.C
> new file mode 100644
> index 000000000000..cf031a0c5283
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/coroutines/pr105475.C
> @@ -0,0 +1,28 @@
> +// https://gcc.gnu.org/PR105475
> +namespace std {
> +
> +struct handle {};
> +
> +struct awaitable {
> +  bool await_ready() noexcept { return false; }
> +  void await_suspend(handle) noexcept {}
> +  bool await_resume() noexcept { return true; }
> +};
> +
> +template <typename T> struct coroutine_handle {
> +  static handle from_address(void *address) noexcept { return {}; }
> +};
> +
> +template <typename T = void> struct coroutine_traits {
> +  struct promise_type {
> +    awaitable initial_suspend() { return {}; }
> +    awaitable final_suspend() noexcept { return {}; }
> +    void return_void() {}
> +    T get_return_object() { return T(); }
> +    void unhandled_exception() {}
> +  };
> +};
> +} // namespace std
> +
> +void foo() { co_return; }
> +// { dg-error "'address' is not a member of 'std::coroutine_handle<void>'" "" { target *-*-* } {.-1} }
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr94528.C b/gcc/testsuite/g++.dg/coroutines/pr94528.C
> index 80e7273f1789..f5adba5749a4 100644
> --- a/gcc/testsuite/g++.dg/coroutines/pr94528.C
> +++ b/gcc/testsuite/g++.dg/coroutines/pr94528.C
> @@ -5,8 +5,15 @@ template <typename _Result, typename> struct coroutine_traits {
>     using promise_type = _Result::promise_type;
>   };
>   template <typename = void> struct coroutine_handle;
> -template <> struct coroutine_handle<> { public: };
> -template <typename> struct coroutine_handle : coroutine_handle<> {};
> +template <> struct coroutine_handle<> {
> +public:
> +  static coroutine_handle from_address(void*);
> +  void* address();
> +};
> +template <typename> struct coroutine_handle : coroutine_handle<> {
> +  static coroutine_handle from_address(void*);
> +  void* address();
> +};
>   struct suspend_always {
>     bool await_ready();
>     void await_suspend(coroutine_handle<>);
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr94879-folly-1.C b/gcc/testsuite/g++.dg/coroutines/pr94879-folly-1.C
> index 11bcce04b7dc..6e091526fe77 100644
> --- a/gcc/testsuite/g++.dg/coroutines/pr94879-folly-1.C
> +++ b/gcc/testsuite/g++.dg/coroutines/pr94879-folly-1.C
> @@ -5,8 +5,14 @@ template <typename a> a b(a &&);
>   template <typename c> struct d { c e; };
>   template <typename f, typename> struct coroutine_traits : f {};
>   template <typename = void> struct coroutine_handle;
> -template <> struct coroutine_handle<> {};
> -template <typename> struct coroutine_handle : coroutine_handle<> {};
> +template <> struct coroutine_handle<> {
> +  static coroutine_handle from_address(void*);
> +  void* address();
> +};
> +template <typename> struct coroutine_handle : coroutine_handle<> {
> +  static coroutine_handle from_address(void*);
> +  void* address();
> +};
>   struct g {};
>   } // namespace std
>   
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr94883-folly-2.C b/gcc/testsuite/g++.dg/coroutines/pr94883-folly-2.C
> index ce06cfddb0ad..98c5a7e3eeef 100644
> --- a/gcc/testsuite/g++.dg/coroutines/pr94883-folly-2.C
> +++ b/gcc/testsuite/g++.dg/coroutines/pr94883-folly-2.C
> @@ -2,8 +2,14 @@
>   namespace std {
>   template <typename a, typename...> struct coroutine_traits : a {};
>   template <typename = void> struct coroutine_handle;
> -template <> struct coroutine_handle<> {};
> -template <typename> struct coroutine_handle : coroutine_handle<> {};
> +template <> struct coroutine_handle<> {
> +  static coroutine_handle from_address(void*);
> +  void* address();
> +};
> +template <typename> struct coroutine_handle : coroutine_handle<> {
> +  static coroutine_handle from_address(void*);
> +  void* address();
> +};
>   struct b {
>     bool await_ready();
>     void await_suspend(coroutine_handle<>);
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr98118.C b/gcc/testsuite/g++.dg/coroutines/pr98118.C
> index d09ffff21426..9a37dc3596a9 100644
> --- a/gcc/testsuite/g++.dg/coroutines/pr98118.C
> +++ b/gcc/testsuite/g++.dg/coroutines/pr98118.C
> @@ -2,8 +2,14 @@ namespace std {
>   inline namespace __n4861 {
>   template <typename _Result, typename> struct coroutine_traits : _Result {};
>   template <typename = void> struct coroutine_handle;
> -template <> struct coroutine_handle<> {};
> -template <typename> struct coroutine_handle : coroutine_handle<> {};
> +template <> struct coroutine_handle<> {
> +  static coroutine_handle from_address(void*);
> +  void* address();
> +};
> +template <typename> struct coroutine_handle : coroutine_handle<> {
> +  static coroutine_handle from_address(void*);
> +  void* address();
> +};
>   struct suspend_never {
>     bool await_ready() noexcept;
>     void await_suspend(coroutine_handle<>) noexcept;
  

Patch

diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
index 91bbe6b0a0eb..0dcdeace5ff8 100644
--- a/gcc/cp/coroutines.cc
+++ b/gcc/cp/coroutines.cc
@@ -91,6 +91,7 @@  struct GTY((for_user)) coroutine_info
 			 one that will eventually be allocated in the coroutine
 			 frame.  */
   tree promise_proxy; /* Likewise, a proxy promise instance.  */
+  tree from_address;  /* handle_type from_address function.  */
   tree return_void;   /* The expression for p.return_void() if it exists.  */
   location_t first_coro_keyword; /* The location of the keyword that made this
 				    function into a coroutine.  */
@@ -203,7 +204,6 @@  static GTY(()) tree coro_final_suspend_identifier;
 static GTY(()) tree coro_return_void_identifier;
 static GTY(()) tree coro_return_value_identifier;
 static GTY(()) tree coro_yield_value_identifier;
-static GTY(()) tree coro_resume_identifier;
 static GTY(()) tree coro_address_identifier;
 static GTY(()) tree coro_from_address_identifier;
 static GTY(()) tree coro_get_return_object_identifier;
@@ -243,7 +243,6 @@  coro_init_identifiers ()
   coro_return_void_identifier = get_identifier ("return_void");
   coro_return_value_identifier = get_identifier ("return_value");
   coro_yield_value_identifier = get_identifier ("yield_value");
-  coro_resume_identifier = get_identifier ("resume");
   coro_address_identifier = get_identifier ("address");
   coro_from_address_identifier = get_identifier ("from_address");
   coro_get_return_object_identifier = get_identifier ("get_return_object");
@@ -271,6 +270,7 @@  coro_init_identifiers ()
 static GTY(()) tree coro_traits_templ;
 static GTY(()) tree coro_handle_templ;
 static GTY(()) tree void_coro_handle_type;
+static GTY(()) tree void_coro_handle_address;
 
 /* ================= Parse, Semantics and Type checking ================= */
 
@@ -389,7 +389,97 @@  find_coro_handle_template_decl (location_t kw)
     return handle_decl;
 }
 
-/* Instantiate the handle template for a given promise type.  */
+/* Get and validate HANDLE_TYPE::address.  The resulting function, if any, will
+   be a non-overloaded member function that takes no arguments and returns
+   void*.  If that is not the case, signals an error and returns NULL_TREE.  */
+
+static tree
+get_handle_type_address (location_t kw, tree handle_type)
+{
+  tree addr_getter = lookup_member (handle_type, coro_address_identifier, 1,
+				    0, tf_warning_or_error);
+  if (!addr_getter || addr_getter == error_mark_node)
+    {
+      qualified_name_lookup_error (handle_type, coro_address_identifier,
+				   error_mark_node, kw);
+      return NULL_TREE;
+    }
+
+  if (!BASELINK_P (addr_getter)
+      || TREE_CODE (TREE_TYPE (addr_getter)) != METHOD_TYPE)
+    {
+      error_at (kw, "%qE must be a non-overloaded method", addr_getter);
+      return NULL_TREE;
+    }
+
+  tree fn_t = TREE_TYPE (addr_getter);
+  tree arg = TYPE_ARG_TYPES (fn_t);
+
+  /* Skip the 'this' pointer.  */
+  arg = TREE_CHAIN (arg);
+
+  /* Check that from_addr has the argument list ().  */
+  if (arg != void_list_node)
+    {
+      error_at (kw, "%qE must take no arguments", addr_getter);
+      return NULL_TREE;
+    }
+
+  tree ret_t = TREE_TYPE (fn_t);
+  if (!same_type_p (ret_t, ptr_type_node))
+    {
+      error_at (kw, "%qE must return %qT, not %qT",
+		addr_getter, ptr_type_node, ret_t);
+      return NULL_TREE;
+    }
+
+  return addr_getter;
+}
+
+/* Get and validate HANDLE_TYPE::from_address.  The resulting function, if
+   any, will be a non-overloaded static function that takes a single void* and
+   returns HANDLE_TYPE.  If that is not the case, signals an error and returns
+   NULL_TREE.  */
+
+static tree
+get_handle_type_from_address (location_t kw, tree handle_type)
+{
+  tree from_addr = lookup_member (handle_type, coro_from_address_identifier, 1,
+				  0, tf_warning_or_error);
+  if (!from_addr || from_addr == error_mark_node)
+    {
+      qualified_name_lookup_error (handle_type, coro_from_address_identifier,
+				   error_mark_node, kw);
+      return NULL_TREE;
+    }
+  if (!BASELINK_P (from_addr)
+      || TREE_CODE (TREE_TYPE (from_addr)) != FUNCTION_TYPE)
+    {
+      error_at (kw, "%qE must be a non-overloaded static function", from_addr);
+      return NULL_TREE;
+    }
+
+  tree fn_t = TREE_TYPE (from_addr);
+  tree arg = TYPE_ARG_TYPES (fn_t);
+  /* Check that from_addr has the argument list (void*).  */
+  if (!arg
+      || !same_type_p (TREE_VALUE (arg), ptr_type_node)
+      || TREE_CHAIN (arg) != void_list_node)
+    {
+      error_at (kw, "%qE must take a single %qT", from_addr, ptr_type_node);
+      return NULL_TREE;
+    }
+
+  tree ret_t = TREE_TYPE (fn_t);
+  if (!same_type_p (ret_t, handle_type))
+    {
+      error_at (kw, "%qE must return %qT, not %qT",
+		from_addr, handle_type, ret_t);
+      return NULL_TREE;
+    }
+
+  return from_addr;
+}
 
 static tree
 instantiate_coro_handle_for_promise_type (location_t kw, tree promise_type)
@@ -453,11 +543,16 @@  ensure_coro_initialized (location_t loc)
 	return false;
 
       /*  We can also instantiate the void coroutine_handle<>  */
-      void_coro_handle_type =
-	instantiate_coro_handle_for_promise_type (loc, NULL_TREE);
+      void_coro_handle_type
+	= instantiate_coro_handle_for_promise_type (loc, void_type_node);
       if (void_coro_handle_type == NULL_TREE)
 	return false;
 
+      void_coro_handle_address
+	= get_handle_type_address (loc, void_coro_handle_type);
+      if (!void_coro_handle_address)
+	return false;
+
       /* A table to hold the state, per coroutine decl.  */
       gcc_checking_assert (coroutine_info_table == NULL);
       coroutine_info_table =
@@ -552,13 +647,17 @@  coro_promise_type_found_p (tree fndecl, location_t loc)
 	}
 
       /* Try to find the handle type for the promise.  */
-      tree handle_type =
-	instantiate_coro_handle_for_promise_type (loc, coro_info->promise_type);
+      tree handle_type
+	= instantiate_coro_handle_for_promise_type (loc, coro_info->promise_type);
       if (handle_type == NULL_TREE)
 	return false;
+      tree from_address = get_handle_type_from_address (loc, handle_type);
+      if (from_address == NULL_TREE)
+	return false;
 
       /* Complete this, we're going to use it.  */
       coro_info->handle_type = complete_type_or_else (handle_type, fndecl);
+      coro_info->from_address = from_address;
 
       /* Diagnostic would be emitted by complete_type_or_else.  */
       if (!coro_info->handle_type)
@@ -674,6 +773,15 @@  get_coroutine_promise_proxy (tree decl)
   return NULL_TREE;
 }
 
+static tree
+get_coroutine_from_address (tree decl)
+{
+  if (coroutine_info *info = get_coroutine_info (decl))
+    return info->from_address;
+
+  return NULL_TREE;
+}
+
 static tree
 lookup_promise_method (tree fndecl, tree member_id, location_t loc,
 		       bool musthave)
@@ -2232,7 +2340,6 @@  build_actor_fn (location_t loc, tree coro_frame_type, tree actor, tree fnbody,
 {
   verify_stmt_tree (fnbody);
   /* Some things we inherit from the original function.  */
-  tree handle_type = get_coroutine_handle_type (orig);
   tree promise_type = get_coroutine_promise_type (orig);
   tree promise_proxy = get_coroutine_promise_proxy (orig);
 
@@ -2392,8 +2499,9 @@  build_actor_fn (location_t loc, tree coro_frame_type, tree actor, tree fnbody,
   tree ash = build_class_member_access_expr (actor_frame, ash_m, NULL_TREE,
 					     false, tf_warning_or_error);
   /* So construct the self-handle from the frame address.  */
-  tree hfa_m = lookup_member (handle_type, coro_from_address_identifier, 1,
-			      0, tf_warning_or_error);
+  tree hfa_m = get_coroutine_from_address (orig);
+  /* Should have been set earlier by coro_promise_type_found_p.  */
+  gcc_assert (hfa_m);
 
   r = build1 (CONVERT_EXPR, build_pointer_type (void_type_node), actor_fp);
   vec<tree, va_gc> *args = make_tree_vector_single (r);
@@ -2488,12 +2596,14 @@  build_actor_fn (location_t loc, tree coro_frame_type, tree actor, tree fnbody,
   r = build_stmt (loc, LABEL_EXPR, continue_label);
   add_stmt (r);
 
+  /* Should have been set earlier by the coro_initialized code.  */
+  gcc_assert (void_coro_handle_address);
+
   /* We want to force a tail-call even for O0/1, so this expands the resume
      call into its underlying implementation.  */
-  tree addr = lookup_member (void_coro_handle_type, coro_address_identifier,
-			       1, 0, tf_warning_or_error);
-  addr = build_new_method_call (continuation, addr, NULL, NULL_TREE,
-				  LOOKUP_NORMAL, NULL, tf_warning_or_error);
+  tree addr = build_new_method_call (continuation, void_coro_handle_address,
+				     NULL, NULL_TREE, LOOKUP_NORMAL, NULL,
+				     tf_warning_or_error);
   tree resume = build_call_expr_loc
     (loc, builtin_decl_explicit (BUILT_IN_CORO_RESUME), 1, addr);
 
diff --git a/gcc/testsuite/g++.dg/coroutines/pr103868.C b/gcc/testsuite/g++.dg/coroutines/pr103868.C
index 8687effb5637..fd05769db3db 100644
--- a/gcc/testsuite/g++.dg/coroutines/pr103868.C
+++ b/gcc/testsuite/g++.dg/coroutines/pr103868.C
@@ -79,6 +79,8 @@  namespace std {
 template <typename...> struct coroutine_traits;
 template <typename = void> struct coroutine_handle {
   operator coroutine_handle<>();
+  static coroutine_handle from_address(void*);
+  void* address();
 };
 struct suspend_always {
   bool await_ready();
diff --git a/gcc/testsuite/g++.dg/coroutines/pr105287.C b/gcc/testsuite/g++.dg/coroutines/pr105287.C
index 9790945287da..c54d1fd5b845 100644
--- a/gcc/testsuite/g++.dg/coroutines/pr105287.C
+++ b/gcc/testsuite/g++.dg/coroutines/pr105287.C
@@ -4,6 +4,8 @@  namespace std {
 template <typename _Result> struct coroutine_traits : _Result {};
 template <typename = void> struct coroutine_handle {
   operator coroutine_handle<>();
+  static coroutine_handle from_address(void*);
+  void* address();
 };
 }
 struct coro1 {
diff --git a/gcc/testsuite/g++.dg/coroutines/pr105301.C b/gcc/testsuite/g++.dg/coroutines/pr105301.C
index 33a0b03cf5d9..b3a3fe1365e3 100644
--- a/gcc/testsuite/g++.dg/coroutines/pr105301.C
+++ b/gcc/testsuite/g++.dg/coroutines/pr105301.C
@@ -29,7 +29,10 @@  struct suspend_always {
 
 namespace std {
 template <class PromiseType = void>
-struct coroutine_handle {};
+struct coroutine_handle {
+  static coroutine_handle from_address(void*);
+  void* address();
+};
 }
 
 struct bad_promise_6 {
diff --git a/gcc/testsuite/g++.dg/coroutines/pr105475-1.C b/gcc/testsuite/g++.dg/coroutines/pr105475-1.C
new file mode 100644
index 000000000000..8fc0a70e5d2a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr105475-1.C
@@ -0,0 +1,27 @@ 
+// https://gcc.gnu.org/PR105475
+// Test the case where we have everything we need for coroutine_handle<void>,
+// but not for coroutine_handle<promise>.
+namespace std {
+struct awaitable {
+  bool await_ready() noexcept { return false; }
+  void await_suspend(auto) noexcept {}
+  bool await_resume() noexcept { return true; }
+};
+
+template <typename T> struct coroutine_handle {
+  static coroutine_handle from_address(void *address);
+};
+
+template <typename T = void> struct coroutine_traits {
+  struct promise_type {
+    awaitable initial_suspend() { return {}; }
+    awaitable final_suspend() noexcept { return {}; }
+    void return_void() {}
+    T get_return_object() { return T(); }
+    void unhandled_exception() {}
+  };
+};
+} // namespace std
+
+void foo() { co_return; }
+// { dg-error "'address' is not a member of 'std::coroutine_handle<void>'" "" { target *-*-* } {.-1} }
diff --git a/gcc/testsuite/g++.dg/coroutines/pr105475-2.C b/gcc/testsuite/g++.dg/coroutines/pr105475-2.C
new file mode 100644
index 000000000000..7c9d76e4f415
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr105475-2.C
@@ -0,0 +1,29 @@ 
+// { dg-do compile }
+// https://gcc.gnu.org/PR105475
+// Test the case where we lack 'from_address' (i.e. the member required from
+// coroutine_handle<void>), but not address()
+namespace std {
+
+struct awaitable {
+  bool await_ready() noexcept { return false; }
+  void await_suspend(auto) noexcept {}
+  bool await_resume() noexcept { return true; }
+};
+
+template <typename T> struct coroutine_handle {
+  void* address();
+};
+
+template <typename T = void> struct coroutine_traits {
+  struct promise_type {
+    awaitable initial_suspend() { return {}; }
+    awaitable final_suspend() noexcept { return {}; }
+    void return_void() {}
+    T get_return_object() { return T(); }
+    void unhandled_exception() {}
+  };
+};
+} // namespace std
+
+void foo() { co_return; }
+// { dg-error "'from_address' is not a member of 'std::coroutine_handle<std::coroutine_traits<void>::promise_type>'" "" { target *-*-* } {.-1} }
diff --git a/gcc/testsuite/g++.dg/coroutines/pr105475-3.C b/gcc/testsuite/g++.dg/coroutines/pr105475-3.C
new file mode 100644
index 000000000000..75e0624ad719
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr105475-3.C
@@ -0,0 +1,29 @@ 
+// https://gcc.gnu.org/PR105475
+// Test the case where we create a non-static from_address.
+#include <coroutine>
+
+struct promise;
+
+struct task
+{ using promise_type = promise; };
+
+struct promise
+{
+  void return_void () {}
+  std::suspend_never final_suspend() noexcept { return {}; }
+  std::suspend_never initial_suspend() noexcept { return {}; }
+  void unhandled_exception () {}
+  task get_return_object() { return {}; }
+};
+
+/* Invalid.  */
+namespace std
+{
+  template<>
+  struct coroutine_handle<promise>
+  { coroutine_handle from_address(void*); };
+};
+
+task foo()
+{ co_return; }
+// { dg-error "std::__n4861::coroutine_handle<promise>::from_address' must be a non-overloaded static function" "" { target *-*-* } {.-1} }
diff --git a/gcc/testsuite/g++.dg/coroutines/pr105475-4.C b/gcc/testsuite/g++.dg/coroutines/pr105475-4.C
new file mode 100644
index 000000000000..398d5c894e26
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr105475-4.C
@@ -0,0 +1,41 @@ 
+// https://gcc.gnu.org/PR105475
+// Test the case where we create a static address.
+struct promise;
+
+namespace std
+{
+  template<typename>
+  struct coroutine_handle
+  {
+    static coroutine_handle from_address(void*);
+
+    static void* address();
+  };
+
+  struct suspend_never
+  {
+    bool await_ready();
+    void await_suspend(coroutine_handle<void>);
+    void await_resume();
+  };
+
+  template<typename T>
+  struct coroutine_traits
+  { using promise_type = typename T::promise_type; };
+};
+
+
+struct task
+{ using promise_type = promise; };
+
+struct promise
+{
+  void return_void () {}
+  std::suspend_never final_suspend() noexcept { return {}; }
+  std::suspend_never initial_suspend() noexcept { return {}; }
+  void unhandled_exception () {}
+  task get_return_object() { return {}; }
+};
+
+task foo() { co_return; }
+// { dg-error "std::coroutine_handle<void>::address' must be a non-overloaded method" "" { target *-*-* } {.-1} }
diff --git a/gcc/testsuite/g++.dg/coroutines/pr105475-5.C b/gcc/testsuite/g++.dg/coroutines/pr105475-5.C
new file mode 100644
index 000000000000..aeb96b0fb338
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr105475-5.C
@@ -0,0 +1,29 @@ 
+// https://gcc.gnu.org/PR105475
+// Test the case where we have type members instead of functions for
+// from_address and address.
+namespace std {
+struct awaitable {
+  bool await_ready() noexcept { return false; }
+  void await_suspend(auto) noexcept {}
+  bool await_resume() noexcept { return true; }
+};
+
+template <typename T>
+struct coroutine_handle {
+  struct from_address {};
+  struct address {};
+};
+
+template <typename T = void> struct coroutine_traits {
+  struct promise_type {
+    awaitable initial_suspend() { return {}; }
+    awaitable final_suspend() noexcept { return {}; }
+    void return_void() {}
+    T get_return_object() { return T(); }
+    void unhandled_exception() {}
+  };
+};
+} // namespace std
+
+void foo() { co_return; }
+// { dg-error "'std::coroutine_handle<void>::address' must be a non-overloaded method" "" { target *-*-* } {.-1} }
diff --git a/gcc/testsuite/g++.dg/coroutines/pr105475-6.C b/gcc/testsuite/g++.dg/coroutines/pr105475-6.C
new file mode 100644
index 000000000000..c7059f292e1c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr105475-6.C
@@ -0,0 +1,29 @@ 
+// https://gcc.gnu.org/PR105475
+// Test the case where we have data members instead of functions for
+// from_address and address.
+namespace std {
+struct awaitable {
+  bool await_ready() noexcept { return false; }
+  void await_suspend(auto) noexcept {}
+  bool await_resume() noexcept { return true; }
+};
+
+template <typename T>
+struct coroutine_handle {
+  static int from_address;
+  int address;
+};
+
+template <typename T = void> struct coroutine_traits {
+  struct promise_type {
+    awaitable initial_suspend() { return {}; }
+    awaitable final_suspend() noexcept { return {}; }
+    void return_void() {}
+    T get_return_object() { return T(); }
+    void unhandled_exception() {}
+  };
+};
+} // namespace std
+
+void foo() { co_return; }
+// { dg-error "'std::coroutine_handle<void>::address' must be a non-overloaded method" "" { target *-*-* } {.-1} }
diff --git a/gcc/testsuite/g++.dg/coroutines/pr105475-broken-spec-2.C b/gcc/testsuite/g++.dg/coroutines/pr105475-broken-spec-2.C
new file mode 100644
index 000000000000..8f469ab00952
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr105475-broken-spec-2.C
@@ -0,0 +1,33 @@ 
+// https://gcc.gnu.org/PR105475
+// Test the case where we specialize coroutine_handle<void> and remove
+// 'address'.
+namespace std {
+
+struct awaitable {
+  bool await_ready() noexcept { return false; }
+  void await_suspend(auto) noexcept {}
+  bool await_resume() noexcept { return true; }
+};
+
+template <typename T> struct coroutine_handle {
+  static coroutine_handle from_address(void *address);
+  void* address();
+};
+
+template <> struct coroutine_handle<void> {
+  static coroutine_handle from_address(void *address);
+};
+
+template <typename T = void> struct coroutine_traits {
+  struct promise_type {
+    awaitable initial_suspend() { return {}; }
+    awaitable final_suspend() noexcept { return {}; }
+    void return_void() {}
+    T get_return_object() { return T(); }
+    void unhandled_exception() {}
+  };
+};
+} // namespace std
+
+void foo() { co_return; }
+// { dg-error "'address' is not a member of 'std::coroutine_handle<void>'" "" { target *-*-* } {.-1} }
diff --git a/gcc/testsuite/g++.dg/coroutines/pr105475-broken-spec.C b/gcc/testsuite/g++.dg/coroutines/pr105475-broken-spec.C
new file mode 100644
index 000000000000..07bf9b06aed9
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr105475-broken-spec.C
@@ -0,0 +1,29 @@ 
+// https://gcc.gnu.org/PR105475
+// Test the case where we specialize coroutine_handle and break it.
+#include <coroutine>
+
+struct promise;
+
+struct task
+{ using promise_type = promise; };
+
+struct promise
+{
+  void return_void () {}
+  std::suspend_never final_suspend() noexcept { return {}; }
+  std::suspend_never initial_suspend() noexcept { return {}; }
+  void unhandled_exception () {}
+  task get_return_object() { return {}; }
+};
+
+/* Invalid.  */
+namespace std
+{
+  template<>
+  struct coroutine_handle<promise>
+  {};
+};
+
+task foo()
+{ co_return; }
+// { dg-error "'from_address' is not a member of 'std::__n4861::coroutine_handle<promise>'" "" { target *-*-* } {.-1} }
diff --git a/gcc/testsuite/g++.dg/coroutines/pr105475.C b/gcc/testsuite/g++.dg/coroutines/pr105475.C
new file mode 100644
index 000000000000..cf031a0c5283
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr105475.C
@@ -0,0 +1,28 @@ 
+// https://gcc.gnu.org/PR105475
+namespace std {
+
+struct handle {};
+
+struct awaitable {
+  bool await_ready() noexcept { return false; }
+  void await_suspend(handle) noexcept {}
+  bool await_resume() noexcept { return true; }
+};
+
+template <typename T> struct coroutine_handle {
+  static handle from_address(void *address) noexcept { return {}; }
+};
+
+template <typename T = void> struct coroutine_traits {
+  struct promise_type {
+    awaitable initial_suspend() { return {}; }
+    awaitable final_suspend() noexcept { return {}; }
+    void return_void() {}
+    T get_return_object() { return T(); }
+    void unhandled_exception() {}
+  };
+};
+} // namespace std
+
+void foo() { co_return; }
+// { dg-error "'address' is not a member of 'std::coroutine_handle<void>'" "" { target *-*-* } {.-1} }
diff --git a/gcc/testsuite/g++.dg/coroutines/pr94528.C b/gcc/testsuite/g++.dg/coroutines/pr94528.C
index 80e7273f1789..f5adba5749a4 100644
--- a/gcc/testsuite/g++.dg/coroutines/pr94528.C
+++ b/gcc/testsuite/g++.dg/coroutines/pr94528.C
@@ -5,8 +5,15 @@  template <typename _Result, typename> struct coroutine_traits {
   using promise_type = _Result::promise_type;
 };
 template <typename = void> struct coroutine_handle;
-template <> struct coroutine_handle<> { public: };
-template <typename> struct coroutine_handle : coroutine_handle<> {};
+template <> struct coroutine_handle<> {
+public:
+  static coroutine_handle from_address(void*);
+  void* address();
+};
+template <typename> struct coroutine_handle : coroutine_handle<> {
+  static coroutine_handle from_address(void*);
+  void* address();
+};
 struct suspend_always {
   bool await_ready();
   void await_suspend(coroutine_handle<>);
diff --git a/gcc/testsuite/g++.dg/coroutines/pr94879-folly-1.C b/gcc/testsuite/g++.dg/coroutines/pr94879-folly-1.C
index 11bcce04b7dc..6e091526fe77 100644
--- a/gcc/testsuite/g++.dg/coroutines/pr94879-folly-1.C
+++ b/gcc/testsuite/g++.dg/coroutines/pr94879-folly-1.C
@@ -5,8 +5,14 @@  template <typename a> a b(a &&);
 template <typename c> struct d { c e; };
 template <typename f, typename> struct coroutine_traits : f {};
 template <typename = void> struct coroutine_handle;
-template <> struct coroutine_handle<> {};
-template <typename> struct coroutine_handle : coroutine_handle<> {};
+template <> struct coroutine_handle<> {
+  static coroutine_handle from_address(void*);
+  void* address();
+};
+template <typename> struct coroutine_handle : coroutine_handle<> {
+  static coroutine_handle from_address(void*);
+  void* address();
+};
 struct g {};
 } // namespace std
 
diff --git a/gcc/testsuite/g++.dg/coroutines/pr94883-folly-2.C b/gcc/testsuite/g++.dg/coroutines/pr94883-folly-2.C
index ce06cfddb0ad..98c5a7e3eeef 100644
--- a/gcc/testsuite/g++.dg/coroutines/pr94883-folly-2.C
+++ b/gcc/testsuite/g++.dg/coroutines/pr94883-folly-2.C
@@ -2,8 +2,14 @@ 
 namespace std {
 template <typename a, typename...> struct coroutine_traits : a {};
 template <typename = void> struct coroutine_handle;
-template <> struct coroutine_handle<> {};
-template <typename> struct coroutine_handle : coroutine_handle<> {};
+template <> struct coroutine_handle<> {
+  static coroutine_handle from_address(void*);
+  void* address();
+};
+template <typename> struct coroutine_handle : coroutine_handle<> {
+  static coroutine_handle from_address(void*);
+  void* address();
+};
 struct b {
   bool await_ready();
   void await_suspend(coroutine_handle<>);
diff --git a/gcc/testsuite/g++.dg/coroutines/pr98118.C b/gcc/testsuite/g++.dg/coroutines/pr98118.C
index d09ffff21426..9a37dc3596a9 100644
--- a/gcc/testsuite/g++.dg/coroutines/pr98118.C
+++ b/gcc/testsuite/g++.dg/coroutines/pr98118.C
@@ -2,8 +2,14 @@  namespace std {
 inline namespace __n4861 {
 template <typename _Result, typename> struct coroutine_traits : _Result {};
 template <typename = void> struct coroutine_handle;
-template <> struct coroutine_handle<> {};
-template <typename> struct coroutine_handle : coroutine_handle<> {};
+template <> struct coroutine_handle<> {
+  static coroutine_handle from_address(void*);
+  void* address();
+};
+template <typename> struct coroutine_handle : coroutine_handle<> {
+  static coroutine_handle from_address(void*);
+  void* address();
+};
 struct suspend_never {
   bool await_ready() noexcept;
   void await_suspend(coroutine_handle<>) noexcept;