[v2] c++: Diagnose taking address of an immediate member function [PR102753]

Message ID 20211019120021.GL304296@tucnak
State Committed
Headers
Series [v2] c++: Diagnose taking address of an immediate member function [PR102753] |

Commit Message

Jakub Jelinek Oct. 19, 2021, noon UTC
  On Mon, Oct 18, 2021 at 12:42:00PM -0400, Jason Merrill wrote:
> > --- gcc/cp/typeck.c.jj	2021-10-05 09:53:55.382734051 +0200
> > +++ gcc/cp/typeck.c	2021-10-15 19:28:38.034213437 +0200
> > @@ -6773,9 +6773,21 @@ cp_build_addr_expr_1 (tree arg, bool str
> >   	    return error_mark_node;
> >   	  }
> > +	if (TREE_CODE (t) == FUNCTION_DECL
> > +	    && DECL_IMMEDIATE_FUNCTION_P (t)
> > +	    && cp_unevaluated_operand == 0
> > +	    && (current_function_decl == NULL_TREE
> > +		|| !DECL_IMMEDIATE_FUNCTION_P (current_function_decl)))
> 
> This doesn't cover some of the other cases of immediate context; we should
> probably factor most of immediate_invocation_p out into a function called
> something like in_immediate_context and use it here, and in several other
> places as well.

You're right, I've done that for the two spots in cp_build_addr_expr_1
and added testsuite coverage for where it changed behavior.
While doing that I've discovered further issues.

One is that we weren't diagnosing PMFs referring to immediate methods
returned from immediate functions (either directly or embedded in
aggregates).  I'm not sure if it can only appear as PTRMEM_CST which
I've handled (cp_walk_subtree only walks the type and not the
PTRMEM_CST_MEMBER) or something else.

Another issue is that while default arg in immediate function
containing &immediate_fn works properly, if it is immediate_fn
instead, we were incorrectly rejecting it.
I've handled this in build_over_call, though with this usage
in_consteval_if_p is slightly misnamed, it stands for in consteval
if or some other reason why we are currently in immediate function context.
Though, that flag alone can't be all the reasons for being in immediate
function contexts, as I've tried the other reasons can't be handled in such
a bool and need to be tested too.

And another thing isn't in a patch, but I'm wondering whether we don't
handle it incorrectly.  constexpr.c has:
  /* Check that immediate invocation does not return an expression referencing
     any immediate function decls.  They need to be allowed while parsing
     immediate functions, but can't leak outside of them.  */
  if (is_consteval
      && t != r
      && (current_function_decl == NULL_TREE
	  || !DECL_IMMEDIATE_FUNCTION_P (current_function_decl)))
as condition for the discovery of embedded immediate FUNCTION_DECLs
(or now PTRMEM_CSTs).  If I remove the && (current... ..._decl))
then g++.dg/cpp2a/consteval7.C's
struct S { int b; int (*c) (); };
consteval S baz () { return { 5, foo }; }
consteval int qux () { S s = baz (); return s.b + s.c (); }
consteval int quux () { constexpr S s = baz (); return s.b + s.c (); }
quux line fails, but based on
http://eel.is/c++draft/expr.const#11
I wonder if it shouldn't fail (clang++ -std=c++20 rejects it),
and be only accepted without the constexpr keyword before S s.
Also wonder about e.g.
consteval int foo () { return 42; }

consteval int
bar ()
{
  auto fn1 = foo;  // This must be ok
  constexpr auto fn2 = foo; // Isn't this an error?
  return fn1 () + fn2 ();
}

constexpr int
baz ()
{
  if consteval {
    auto fn1 = foo; // This must be ok
    constexpr auto fn2 = foo; // Isn't this an error?
    return fn1 () + fn2 ();
  }
  return 0;
}

auto a = bar ();

static_assert (bar () == 84);
static_assert (baz () == 84);
(again, clang++ -std=c++20 rejects the fn2 = foo; case,
but doesn't implement consteval if, so can't test the other one).
For taking address of an immediate function or method if it is taken
outside of immediate function context we already have diagnostics
about it, but shouldn't the immediate FUNCTION_DECL discovery in
cxx_eval_outermost_constant_expression be instead guarded with something
like
  if (is_consteval || in_immediate_context ())
and be done regardless of whether t != r?

2021-10-19  Jakub Jelinek  <jakub@redhat.com>

	PR c++/102753
	* cp-tree.h (in_immediate_context): Declare.
	* call.c (in_immediate_context): New function.
	(immediate_invocation_p): Use it.
	(build_over_call): Temporarily set in_consteval_if_p for
	convert_default_arg calls of immediate invocations.
	* typeck.c (cp_build_addr_expr_1): Diagnose taking address of
	an immediate method.  Use t instead of TREE_OPERAND (arg, 1).
	Use in_immediate_context function.
	* constexpr.c (find_immediate_fndecl): Handle PTRMEM_CST
	which refers to immediate function decl.

	* g++.dg/cpp2a/consteval20.C: New test.
	* g++.dg/cpp2a/consteval21.C: New test.
	* g++.dg/cpp2a/consteval22.C: New test.
	* g++.dg/cpp2a/consteval23.C: New test.
	* g++.dg/cpp23/consteval-if11.C: New test.



	Jakub
  

Comments

Jason Merrill Oct. 20, 2021, 11:16 p.m. UTC | #1
On 10/19/21 08:00, Jakub Jelinek wrote:
> On Mon, Oct 18, 2021 at 12:42:00PM -0400, Jason Merrill wrote:
>>> --- gcc/cp/typeck.c.jj	2021-10-05 09:53:55.382734051 +0200
>>> +++ gcc/cp/typeck.c	2021-10-15 19:28:38.034213437 +0200
>>> @@ -6773,9 +6773,21 @@ cp_build_addr_expr_1 (tree arg, bool str
>>>    	    return error_mark_node;
>>>    	  }
>>> +	if (TREE_CODE (t) == FUNCTION_DECL
>>> +	    && DECL_IMMEDIATE_FUNCTION_P (t)
>>> +	    && cp_unevaluated_operand == 0
>>> +	    && (current_function_decl == NULL_TREE
>>> +		|| !DECL_IMMEDIATE_FUNCTION_P (current_function_decl)))
>>
>> This doesn't cover some of the other cases of immediate context; we should
>> probably factor most of immediate_invocation_p out into a function called
>> something like in_immediate_context and use it here, and in several other
>> places as well.
> 
> You're right, I've done that for the two spots in cp_build_addr_expr_1
> and added testsuite coverage for where it changed behavior.
> While doing that I've discovered further issues.
> 
> One is that we weren't diagnosing PMFs referring to immediate methods
> returned from immediate functions (either directly or embedded in
> aggregates).  I'm not sure if it can only appear as PTRMEM_CST which
> I've handled (cp_walk_subtree only walks the type and not the
> PTRMEM_CST_MEMBER) or something else.
> 
> Another issue is that while default arg in immediate function
> containing &immediate_fn works properly, if it is immediate_fn
> instead, we were incorrectly rejecting it.
> I've handled this in build_over_call, though with this usage
> in_consteval_if_p is slightly misnamed, it stands for in consteval
> if or some other reason why we are currently in immediate function context.
> Though, that flag alone can't be all the reasons for being in immediate
> function contexts, as I've tried the other reasons can't be handled in such
> a bool and need to be tested too.
> 
> And another thing isn't in a patch, but I'm wondering whether we don't
> handle it incorrectly.  constexpr.c has:
>    /* Check that immediate invocation does not return an expression referencing
>       any immediate function decls.  They need to be allowed while parsing
>       immediate functions, but can't leak outside of them.  */
>    if (is_consteval
>        && t != r
>        && (current_function_decl == NULL_TREE
> 	  || !DECL_IMMEDIATE_FUNCTION_P (current_function_decl)))
> as condition for the discovery of embedded immediate FUNCTION_DECLs
> (or now PTRMEM_CSTs).  If I remove the && (current... ..._decl))
> then g++.dg/cpp2a/consteval7.C's
> struct S { int b; int (*c) (); };
> consteval S baz () { return { 5, foo }; }
> consteval int qux () { S s = baz (); return s.b + s.c (); }
> consteval int quux () { constexpr S s = baz (); return s.b + s.c (); }
> quux line fails, but based on
> http://eel.is/c++draft/expr.const#11
> I wonder if it shouldn't fail (clang++ -std=c++20 rejects it),
> and be only accepted without the constexpr keyword before S s.
> Also wonder about e.g.
> consteval int foo () { return 42; }
> 
> consteval int
> bar ()
> {
>    auto fn1 = foo;  // This must be ok
>    constexpr auto fn2 = foo; // Isn't this an error?
>    return fn1 () + fn2 ();
> }
> 
> constexpr int
> baz ()
> {
>    if consteval {
>      auto fn1 = foo; // This must be ok
>      constexpr auto fn2 = foo; // Isn't this an error?
>      return fn1 () + fn2 ();
>    }
>    return 0;
> }
> 
> auto a = bar ();
> 
> static_assert (bar () == 84);
> static_assert (baz () == 84);
> (again, clang++ -std=c++20 rejects the fn2 = foo; case,
> but doesn't implement consteval if, so can't test the other one).
> For taking address of an immediate function or method if it is taken
> outside of immediate function context we already have diagnostics
> about it, but shouldn't the immediate FUNCTION_DECL discovery in
> cxx_eval_outermost_constant_expression be instead guarded with something
> like
>    if (is_consteval || in_immediate_context ())
> and be done regardless of whether t != r?
> 
> 2021-10-19  Jakub Jelinek  <jakub@redhat.com>
> 
> 	PR c++/102753
> 	* cp-tree.h (in_immediate_context): Declare.
> 	* call.c (in_immediate_context): New function.
> 	(immediate_invocation_p): Use it.
> 	(build_over_call): Temporarily set in_consteval_if_p for
> 	convert_default_arg calls of immediate invocations.
> 	* typeck.c (cp_build_addr_expr_1): Diagnose taking address of
> 	an immediate method.  Use t instead of TREE_OPERAND (arg, 1).
> 	Use in_immediate_context function.
> 	* constexpr.c (find_immediate_fndecl): Handle PTRMEM_CST
> 	which refers to immediate function decl.
> 
> 	* g++.dg/cpp2a/consteval20.C: New test.
> 	* g++.dg/cpp2a/consteval21.C: New test.
> 	* g++.dg/cpp2a/consteval22.C: New test.
> 	* g++.dg/cpp2a/consteval23.C: New test.
> 	* g++.dg/cpp23/consteval-if11.C: New test.
> 
> --- gcc/cp/cp-tree.h.jj	2021-10-15 11:58:44.968133548 +0200
> +++ gcc/cp/cp-tree.h	2021-10-19 10:40:58.375799274 +0200
> @@ -6547,6 +6547,7 @@ extern tree perform_direct_initializatio
>                                                          tsubst_flags_t);
>   extern vec<tree,va_gc> *resolve_args (vec<tree,va_gc>*, tsubst_flags_t);
>   extern tree in_charge_arg_for_name		(tree);
> +extern bool in_immediate_context		();
>   extern tree build_cxx_call			(tree, int, tree *,
>   						 tsubst_flags_t,
>   						 tree = NULL_TREE);
> --- gcc/cp/call.c.jj	2021-10-15 11:58:44.947133850 +0200
> +++ gcc/cp/call.c	2021-10-19 13:04:42.333421774 +0200
> @@ -9025,6 +9025,19 @@ build_trivial_dtor_call (tree instance,
>   		 instance, clobber);
>   }
>   
> +/* Return true if in an immediate function context.  */

or an unevaluated operand, or a subexpression of an immediate invocation.

Hmm...that suggests that in consteval23.C, bar(foo) should also be OK, 
because 'foo' is a subexpression of an immediate invocation.  We can 
handle that by...

> +
> +bool
> +in_immediate_context ()
> +{
> +  return (cp_unevaluated_operand != 0
> +	  || (current_function_decl != NULL_TREE
> +	      && DECL_IMMEDIATE_FUNCTION_P (current_function_decl))
> +	  || (current_binding_level->kind == sk_function_parms
> +	      && current_binding_level->immediate_fn_ctx_p)
> +	  || in_consteval_if_p);
> +}
> +
>   /* Return true if a call to FN with number of arguments NARGS
>      is an immediate invocation.  */
>   
> @@ -9451,6 +9459,12 @@ build_over_call (struct z_candidate *can
>       }
>   
>     /* Default arguments */
> +  bool save_in_consteval_if_p = in_consteval_if_p;
> +  /* If the call is immediate function invocation, make sure
> +     taking address of immediate functions is allowed in default
> +     arguments.  */
> +  if (immediate_invocation_p (STRIP_TEMPLATE (fn), nargs))
> +    in_consteval_if_p = true;

...moving this earlier in build_over_call, e.g. shortly after 
not_really_used:

You can also use make_temp_override.

And update the comment for saved_scope::consteval_if_p to note that it 
is also set while processing an immediate invocation.

>     for (; parm && parm != void_list_node; parm = TREE_CHAIN (parm), i++)
>       {
>         if (TREE_VALUE (parm) == error_mark_node)
> @@ -9463,6 +9477,7 @@ build_over_call (struct z_candidate *can
>           return error_mark_node;
>         argarray[j++] = val;
>       }
> +  in_consteval_if_p = save_in_consteval_if_p;
>   
>     /* Ellipsis */
>     int magic = magic_varargs_p (fn);
> --- gcc/cp/typeck.c.jj	2021-10-18 11:01:08.635858336 +0200
> +++ gcc/cp/typeck.c	2021-10-19 10:42:18.454671723 +0200
> @@ -6773,9 +6773,19 @@ cp_build_addr_expr_1 (tree arg, bool str
>   	    return error_mark_node;
>   	  }
>   
> +	if (TREE_CODE (t) == FUNCTION_DECL
> +	    && DECL_IMMEDIATE_FUNCTION_P (t)
> +	    && !in_immediate_context ())
> +	  {
> +	    if (complain & tf_error)
> +	      error_at (loc, "taking address of an immediate function %qD",
> +			t);
> +	    return error_mark_node;
> +	  }
> +
>   	type = build_ptrmem_type (context_for_name_lookup (t),
>   				  TREE_TYPE (t));
> -	t = make_ptrmem_cst (type, TREE_OPERAND (arg, 1));
> +	t = make_ptrmem_cst (type, t);
>   	return t;
>         }
>   
> @@ -6800,9 +6810,7 @@ cp_build_addr_expr_1 (tree arg, bool str
>         tree stripped_arg = tree_strip_any_location_wrapper (arg);
>         if (TREE_CODE (stripped_arg) == FUNCTION_DECL
>   	  && DECL_IMMEDIATE_FUNCTION_P (stripped_arg)
> -	  && cp_unevaluated_operand == 0
> -	  && (current_function_decl == NULL_TREE
> -	      || !DECL_IMMEDIATE_FUNCTION_P (current_function_decl)))
> +	  && !in_immediate_context ())
>   	{
>   	  if (complain & tf_error)
>   	    error_at (loc, "taking address of an immediate function %qD",
> --- gcc/cp/constexpr.c.jj	2021-10-19 09:24:41.938242276 +0200
> +++ gcc/cp/constexpr.c	2021-10-19 12:22:35.583964001 +0200
> @@ -7276,6 +7276,10 @@ find_immediate_fndecl (tree *tp, int */*
>   {
>     if (TREE_CODE (*tp) == FUNCTION_DECL && DECL_IMMEDIATE_FUNCTION_P (*tp))
>       return *tp;
> +  if (TREE_CODE (*tp) == PTRMEM_CST
> +      && TREE_CODE (PTRMEM_CST_MEMBER (*tp)) == FUNCTION_DECL
> +      && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (*tp)))
> +    return PTRMEM_CST_MEMBER (*tp);
>     return NULL_TREE;
>   }
>   
> --- gcc/testsuite/g++.dg/cpp2a/consteval20.C.jj	2021-10-19 10:29:30.471484783 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/consteval20.C	2021-10-19 10:29:30.471484783 +0200
> @@ -0,0 +1,24 @@
> +// PR c++/102753
> +// { dg-do compile { target c++20 } }
> +
> +struct S {
> +  consteval int foo () const { return 42; }
> +};
> +
> +constexpr S s;
> +
> +int
> +bar ()
> +{
> +  return (s.*&S::foo) ();		// { dg-error "taking address of an immediate function" }
> +}
> +
> +constexpr auto a = &S::foo;		// { dg-error "taking address of an immediate function" }
> +
> +consteval int
> +baz ()
> +{
> +  return (s.*&S::foo) ();
> +}
> +
> +static_assert (baz () == 42);
> --- gcc/testsuite/g++.dg/cpp2a/consteval21.C.jj	2021-10-19 11:09:18.890838778 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/consteval21.C	2021-10-19 11:57:28.309175141 +0200
> @@ -0,0 +1,35 @@
> +// PR c++/102753
> +// { dg-do compile { target c++20 } }
> +
> +struct S {
> +  constexpr S () : s (0) {}
> +  consteval int foo () { return 1; }
> +  virtual consteval int bar () { return 2; }
> +  int s;
> +};
> +
> +consteval int foo () { return 42; }
> +
> +consteval int
> +bar (int (*fn) () = &foo)
> +{
> +  return fn ();
> +}
> +
> +consteval int
> +baz (int (S::*fn) () = &S::foo)
> +{
> +  S s;
> +  return (s.*fn) ();
> +}
> +
> +consteval int
> +qux (int (S::*fn) () = &S::bar)
> +{
> +  S s;
> +  return (s.*fn) ();
> +}
> +
> +static_assert (bar () == 42);
> +static_assert (baz () == 1);
> +static_assert (qux () == 2);
> --- gcc/testsuite/g++.dg/cpp2a/consteval22.C.jj	2021-10-19 11:21:44.271346868 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/consteval22.C	2021-10-19 12:23:31.783173408 +0200
> @@ -0,0 +1,34 @@
> +// PR c++/102753
> +// { dg-do compile { target c++20 } }
> +
> +struct S {
> +  constexpr S () : s (0) {}
> +  consteval int foo () { return 1; }
> +  virtual consteval int bar () { return 2; }
> +  int s;
> +};
> +typedef int (S::*P) ();
> +
> +consteval P
> +foo ()
> +{
> +  return &S::foo;
> +}
> +
> +consteval P
> +bar ()
> +{
> +  return &S::bar;
> +}
> +
> +consteval int
> +baz ()
> +{
> +  S s;
> +  return (s.*(foo ())) () + (s.*(bar ())) ();
> +}
> +
> +static_assert (baz () == 3);
> +
> +constexpr P a = foo ();		// { dg-error "immediate evaluation returns address of immediate function" }
> +constexpr P b = bar ();		// { dg-error "immediate evaluation returns address of immediate function" }
> --- gcc/testsuite/g++.dg/cpp2a/consteval23.C.jj	2021-10-19 12:23:54.235857548 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/consteval23.C	2021-10-19 12:24:33.931299123 +0200
> @@ -0,0 +1,12 @@
> +// PR c++/102753
> +// { dg-do compile { target c++20 } }
> +
> +consteval int foo () { return 42; }
> +
> +consteval int
> +bar (int (*fn) () = foo)
> +{
> +  return fn ();
> +}
> +
> +static_assert (bar () == 42);
> --- gcc/testsuite/g++.dg/cpp23/consteval-if11.C.jj	2021-10-19 11:17:25.964982502 +0200
> +++ gcc/testsuite/g++.dg/cpp23/consteval-if11.C	2021-10-19 11:35:38.878602026 +0200
> @@ -0,0 +1,27 @@
> +// PR c++/102753
> +// { dg-do compile { target c++20 } }
> +// { dg-options "" }
> +
> +struct S {
> +  constexpr S () : s (0) {}
> +  consteval int foo () { return 1; }
> +  virtual consteval int bar () { return 2; }
> +  int s;
> +};
> +
> +consteval int foo () { return 42; }
> +
> +constexpr int
> +bar ()
> +{
> +  if consteval {	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
> +    int (*fn1) () = foo;
> +    int (S::*fn2) () = &S::foo;
> +    int (S::*fn3) () = &S::bar;
> +    S s;
> +    return fn1 () + (s.*fn2) () + (s.*fn3) ();
> +  }
> +  return 0;
> +}
> +
> +static_assert (bar () == 45);
> 
> 
> 	Jakub
>
  
Jakub Jelinek Oct. 21, 2021, 11:17 a.m. UTC | #2
On Wed, Oct 20, 2021 at 07:16:44PM -0400, Jason Merrill wrote:
> or an unevaluated operand, or a subexpression of an immediate invocation.
> 
> Hmm...that suggests that in consteval23.C, bar(foo) should also be OK,

Ouch.  That opens a big can of worms, see below.

> because 'foo' is a subexpression of an immediate invocation.  We can handle
> that by...
> 
> > +
> > +bool
> > +in_immediate_context ()
> > +{
> > +  return (cp_unevaluated_operand != 0
> > +	  || (current_function_decl != NULL_TREE
> > +	      && DECL_IMMEDIATE_FUNCTION_P (current_function_decl))
> > +	  || (current_binding_level->kind == sk_function_parms
> > +	      && current_binding_level->immediate_fn_ctx_p)
> > +	  || in_consteval_if_p);
> > +}
> > +
> >   /* Return true if a call to FN with number of arguments NARGS
> >      is an immediate invocation.  */
> > @@ -9451,6 +9459,12 @@ build_over_call (struct z_candidate *can
> >       }
> >     /* Default arguments */
> > +  bool save_in_consteval_if_p = in_consteval_if_p;
> > +  /* If the call is immediate function invocation, make sure
> > +     taking address of immediate functions is allowed in default
> > +     arguments.  */
> > +  if (immediate_invocation_p (STRIP_TEMPLATE (fn), nargs))
> > +    in_consteval_if_p = true;
> 
> ...moving this earlier in build_over_call, e.g. shortly after
> not_really_used:
> 
> You can also use make_temp_override.

make_temp_override can't be used, because in_consteval_if_p is BOOL_BITFIELD
under the hood and binding a reference to a bit-field is not valid.

In the patch I've used a specialized in_consteval_if_p_temp_override class
instead.  And it either needs to be restored after all the argument
processing, because we call immediate_invocation_p again and because of
the in_consteval_if_p override it is then false (the patch calls icip.reset
()), or we'd need to remember the result of the first immediate_invocation_p
call in some bool temporary and reuse that later.
Updated patch below.

This fixes the static_assert (bar (foo) == 42); case newly added to
consteval23.C, but unfortunately that isn't enough.
Consider incremental patch to the testcase:
--- gcc/testsuite/g++.dg/cpp2a/consteval23.C.jj	2021-10-21 11:32:48.570586881 +0200
+++ gcc/testsuite/g++.dg/cpp2a/consteval23.C	2021-10-21 12:47:42.281769906 +0200
@@ -2,6 +2,7 @@
 // { dg-do compile { target c++20 } }
 
 consteval int foo () { return 42; }
+constexpr auto baz (int (*fn) ()) { return fn; }
 
 consteval int
 bar (int (*fn) () = foo)
@@ -11,3 +12,5 @@ bar (int (*fn) () = foo)
 
 static_assert (bar () == 42);
 static_assert (bar (foo) == 42);
+static_assert (bar (&foo) == 42);
+static_assert (bar (baz (&foo)) == 42);

If even the last two lines are supposed to be valid, then I'm afraid we
need to reconsider the errors performed in cp_build_addr_expr_1, because
when that is called, we often don't know if we are in a subexpression
of immediate invocation or not, whether it is because we are in template,
something non-type dependent but other argumeents of the function call are
type dependent, or because it is an overloaded function and some overloads
are consteval and others are not and we are deep in a middle of some
argument parsing and other arguments haven't been parsed at all yet, etc.

I'm afraid I don't have a good idea where to move that diagnostic to though,
it would need to be done somewhere where we are certain we aren't in a
subexpression of immediate invocation.  Given statement expressions, even
diagnostics after parsing whole statements might not be good enough, e.g.
void
qux ()
{
  static_assert (bar (({ constexpr auto a = 1; foo; })) == 42);
}
But if we e.g. diagnose it somewhere after parsing the whole function (and
for templates only after instantiating it) plus where we handle
non-automatic variable initializers etc., will we handle all spots where
we should diagnose it?  It better should be done before cp_fold...
In whatever spot is right doing something similar to what
cp_walk_tree find_immediate_fndecl, does, except probably error out on each
case we see (with the right locus) instead of just finding the first match.

Note, trying clang++ on godbolt on the consteval23.C testcase with the above
ammendments, it diagnoses the = foo default argument, that seems to be a
clang bug to me, but if I comment out the
static_assert (bar () == 42); test and comment out the default argument
bar (int (*fn) () /* = foo */)
then it succeeds.  It even accepts the above qux with statement expression.

2021-10-21  Jakub Jelinek  <jakub@redhat.com>

	PR c++/102753
	* cp-tree.h (saved_scope): Document that consteval_if_p member
	is also set while processing immediate invocation.
	(in_immediate_context): Declare.
	* call.c (in_immediate_context): New function.
	(immediate_invocation_p): Use it.
	(struct in_consteval_if_p_temp_override): New class.
	(build_over_call): Temporarily set in_consteval_if_p for processing
	immediate invocation arguments.
	* typeck.c (cp_build_addr_expr_1): Diagnose taking address of
	an immediate method.  Use t instead of TREE_OPERAND (arg, 1).
	Use in_immediate_context function.
	* constexpr.c (find_immediate_fndecl): Handle PTRMEM_CST
	which refers to immediate function decl.

	* g++.dg/cpp2a/consteval13.C: Don't expect errors.
	* g++.dg/cpp2a/consteval20.C: New test.
	* g++.dg/cpp2a/consteval21.C: New test.
	* g++.dg/cpp2a/consteval22.C: New test.
	* g++.dg/cpp2a/consteval23.C: New test.
	* g++.dg/cpp23/consteval-if11.C: New test.

--- gcc/cp/cp-tree.h.jj	2021-10-20 08:35:26.850254029 +0200
+++ gcc/cp/cp-tree.h	2021-10-21 11:36:51.636186234 +0200
@@ -1825,7 +1825,8 @@ struct GTY(()) saved_scope {
      if-statement.  */
   BOOL_BITFIELD discarded_stmt : 1;
   /* Nonzero if we are parsing or instantiating the compound-statement
-     of consteval if statement.  */
+     of consteval if statement.  Also set while processing an immediate
+     invocation.  */
   BOOL_BITFIELD consteval_if_p : 1;
 
   int unevaluated_operand;
@@ -6547,6 +6548,7 @@ extern tree perform_direct_initializatio
                                                        tsubst_flags_t);
 extern vec<tree,va_gc> *resolve_args (vec<tree,va_gc>*, tsubst_flags_t);
 extern tree in_charge_arg_for_name		(tree);
+extern bool in_immediate_context		();
 extern tree build_cxx_call			(tree, int, tree *,
 						 tsubst_flags_t,
 						 tree = NULL_TREE);
--- gcc/cp/call.c.jj	2021-10-20 08:35:26.808254618 +0200
+++ gcc/cp/call.c	2021-10-21 12:10:42.591798807 +0200
@@ -9025,6 +9025,20 @@ build_trivial_dtor_call (tree instance,
 		 instance, clobber);
 }
 
+/* Return true if in an immediate function context, or an unevaluated operand,
+   or a subexpression of an immediate invocation.  */
+
+bool
+in_immediate_context ()
+{
+  return (cp_unevaluated_operand != 0
+	  || (current_function_decl != NULL_TREE
+	      && DECL_IMMEDIATE_FUNCTION_P (current_function_decl))
+	  || (current_binding_level->kind == sk_function_parms
+	      && current_binding_level->immediate_fn_ctx_p)
+	  || in_consteval_if_p);
+}
+
 /* Return true if a call to FN with number of arguments NARGS
    is an immediate invocation.  */
 
@@ -9033,18 +9047,25 @@ immediate_invocation_p (tree fn, int nar
 {
   return (TREE_CODE (fn) == FUNCTION_DECL
 	  && DECL_IMMEDIATE_FUNCTION_P (fn)
-	  && cp_unevaluated_operand == 0
-	  && (current_function_decl == NULL_TREE
-	      || !DECL_IMMEDIATE_FUNCTION_P (current_function_decl))
-	  && (current_binding_level->kind != sk_function_parms
-	      || !current_binding_level->immediate_fn_ctx_p)
-	  && !in_consteval_if_p
+	  && !in_immediate_context ()
 	  /* As an exception, we defer std::source_location::current ()
 	     invocations until genericization because LWG3396 mandates
 	     special behavior for it.  */
 	  && (nargs > 1 || !source_location_current_p (fn)));
 }
 
+/* temp_override for in_consteval_if_p, which can't use make_temp_override
+   because it is a bitfield.  */
+
+struct in_consteval_if_p_temp_override {
+  bool save_in_consteval_if_p;
+  in_consteval_if_p_temp_override ()
+    : save_in_consteval_if_p (in_consteval_if_p) {}
+  void reset () { in_consteval_if_p = save_in_consteval_if_p; }
+  ~in_consteval_if_p_temp_override ()
+  { reset (); }
+};
+
 /* Subroutine of the various build_*_call functions.  Overload resolution
    has chosen a winning candidate CAND; build up a CALL_EXPR accordingly.
    ARGS is a TREE_LIST of the unconverted arguments to the call.  FLAGS is a
@@ -9254,6 +9275,12 @@ build_over_call (struct z_candidate *can
     nargs = parmlen;
   argarray = XALLOCAVEC (tree, nargs);
 
+  in_consteval_if_p_temp_override icip;
+  /* If the call is immediate function invocation, make sure
+     taking address of immediate functions is allowed in its arguments.  */
+  if (immediate_invocation_p (STRIP_TEMPLATE (fn), nargs))
+    in_consteval_if_p = true;
+
   /* The implicit parameters to a constructor are not considered by overload
      resolution, and must be of the proper type.  */
   if (DECL_CONSTRUCTOR_P (fn))
@@ -9498,6 +9525,7 @@ build_over_call (struct z_candidate *can
 
   gcc_assert (j <= nargs);
   nargs = j;
+  icip.reset ();
 
   /* Avoid performing argument transformation if warnings are disabled.
      When tf_warning is set and at least one of the warnings is active
--- gcc/cp/typeck.c.jj	2021-10-20 08:35:26.865253819 +0200
+++ gcc/cp/typeck.c	2021-10-21 11:29:43.040182567 +0200
@@ -6773,9 +6773,19 @@ cp_build_addr_expr_1 (tree arg, bool str
 	    return error_mark_node;
 	  }
 
+	if (TREE_CODE (t) == FUNCTION_DECL
+	    && DECL_IMMEDIATE_FUNCTION_P (t)
+	    && !in_immediate_context ())
+	  {
+	    if (complain & tf_error)
+	      error_at (loc, "taking address of an immediate function %qD",
+			t);
+	    return error_mark_node;
+	  }
+
 	type = build_ptrmem_type (context_for_name_lookup (t),
 				  TREE_TYPE (t));
-	t = make_ptrmem_cst (type, TREE_OPERAND (arg, 1));
+	t = make_ptrmem_cst (type, t);
 	return t;
       }
 
@@ -6800,9 +6810,7 @@ cp_build_addr_expr_1 (tree arg, bool str
       tree stripped_arg = tree_strip_any_location_wrapper (arg);
       if (TREE_CODE (stripped_arg) == FUNCTION_DECL
 	  && DECL_IMMEDIATE_FUNCTION_P (stripped_arg)
-	  && cp_unevaluated_operand == 0
-	  && (current_function_decl == NULL_TREE
-	      || !DECL_IMMEDIATE_FUNCTION_P (current_function_decl)))
+	  && !in_immediate_context ())
 	{
 	  if (complain & tf_error)
 	    error_at (loc, "taking address of an immediate function %qD",
--- gcc/cp/constexpr.c.jj	2021-10-20 08:35:26.825254380 +0200
+++ gcc/cp/constexpr.c	2021-10-21 11:29:43.041182553 +0200
@@ -7276,6 +7276,10 @@ find_immediate_fndecl (tree *tp, int */*
 {
   if (TREE_CODE (*tp) == FUNCTION_DECL && DECL_IMMEDIATE_FUNCTION_P (*tp))
     return *tp;
+  if (TREE_CODE (*tp) == PTRMEM_CST
+      && TREE_CODE (PTRMEM_CST_MEMBER (*tp)) == FUNCTION_DECL
+      && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (*tp)))
+    return PTRMEM_CST_MEMBER (*tp);
   return NULL_TREE;
 }
 
--- gcc/testsuite/g++.dg/cpp2a/consteval13.C.jj	2020-01-12 11:54:37.140402440 +0100
+++ gcc/testsuite/g++.dg/cpp2a/consteval13.C	2021-10-21 12:24:33.935180868 +0200
@@ -10,8 +10,8 @@ void
 foo ()
 {
    auto qux = [] (fnptr a = quux ()) consteval { return a (); };
-   constexpr auto c = qux (baz);	// { dg-error "28:taking address of an immediate function" }
-   constexpr auto d = qux (bar);	// { dg-error "28:taking address of an immediate function" }
+   constexpr auto c = qux (baz);
+   constexpr auto d = qux (bar);
    static_assert (c == 1);
    static_assert (d == 42);
 }
--- gcc/testsuite/g++.dg/cpp2a/consteval20.C.jj	2021-10-21 11:29:43.041182553 +0200
+++ gcc/testsuite/g++.dg/cpp2a/consteval20.C	2021-10-21 11:29:43.041182553 +0200
@@ -0,0 +1,24 @@
+// PR c++/102753
+// { dg-do compile { target c++20 } }
+
+struct S {
+  consteval int foo () const { return 42; }
+};
+
+constexpr S s;
+
+int
+bar ()
+{
+  return (s.*&S::foo) ();		// { dg-error "taking address of an immediate function" }
+}
+
+constexpr auto a = &S::foo;		// { dg-error "taking address of an immediate function" }
+
+consteval int
+baz ()
+{
+  return (s.*&S::foo) ();
+}
+
+static_assert (baz () == 42);
--- gcc/testsuite/g++.dg/cpp2a/consteval21.C.jj	2021-10-21 11:29:43.041182553 +0200
+++ gcc/testsuite/g++.dg/cpp2a/consteval21.C	2021-10-21 11:29:43.041182553 +0200
@@ -0,0 +1,35 @@
+// PR c++/102753
+// { dg-do compile { target c++20 } }
+
+struct S {
+  constexpr S () : s (0) {}
+  consteval int foo () { return 1; }
+  virtual consteval int bar () { return 2; }
+  int s;
+};
+
+consteval int foo () { return 42; }
+
+consteval int
+bar (int (*fn) () = &foo)
+{
+  return fn ();
+}
+
+consteval int
+baz (int (S::*fn) () = &S::foo)
+{
+  S s;
+  return (s.*fn) ();
+}
+
+consteval int
+qux (int (S::*fn) () = &S::bar)
+{
+  S s;
+  return (s.*fn) ();
+}
+
+static_assert (bar () == 42);
+static_assert (baz () == 1);
+static_assert (qux () == 2);
--- gcc/testsuite/g++.dg/cpp2a/consteval22.C.jj	2021-10-21 11:29:43.041182553 +0200
+++ gcc/testsuite/g++.dg/cpp2a/consteval22.C	2021-10-21 11:29:43.041182553 +0200
@@ -0,0 +1,34 @@
+// PR c++/102753
+// { dg-do compile { target c++20 } }
+
+struct S {
+  constexpr S () : s (0) {}
+  consteval int foo () { return 1; }
+  virtual consteval int bar () { return 2; }
+  int s;
+};
+typedef int (S::*P) ();
+
+consteval P
+foo ()
+{
+  return &S::foo;
+}
+
+consteval P
+bar ()
+{
+  return &S::bar;
+}
+
+consteval int
+baz ()
+{
+  S s;
+  return (s.*(foo ())) () + (s.*(bar ())) ();
+}
+
+static_assert (baz () == 3);
+
+constexpr P a = foo ();		// { dg-error "immediate evaluation returns address of immediate function" }
+constexpr P b = bar ();		// { dg-error "immediate evaluation returns address of immediate function" }
--- gcc/testsuite/g++.dg/cpp2a/consteval23.C.jj	2021-10-21 11:29:43.041182553 +0200
+++ gcc/testsuite/g++.dg/cpp2a/consteval23.C	2021-10-21 11:32:48.570586881 +0200
@@ -0,0 +1,13 @@
+// PR c++/102753
+// { dg-do compile { target c++20 } }
+
+consteval int foo () { return 42; }
+
+consteval int
+bar (int (*fn) () = foo)
+{
+  return fn ();
+}
+
+static_assert (bar () == 42);
+static_assert (bar (foo) == 42);
--- gcc/testsuite/g++.dg/cpp23/consteval-if11.C.jj	2021-10-21 11:29:43.041182553 +0200
+++ gcc/testsuite/g++.dg/cpp23/consteval-if11.C	2021-10-21 11:29:43.041182553 +0200
@@ -0,0 +1,27 @@
+// PR c++/102753
+// { dg-do compile { target c++20 } }
+// { dg-options "" }
+
+struct S {
+  constexpr S () : s (0) {}
+  consteval int foo () { return 1; }
+  virtual consteval int bar () { return 2; }
+  int s;
+};
+
+consteval int foo () { return 42; }
+
+constexpr int
+bar ()
+{
+  if consteval {	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
+    int (*fn1) () = foo;
+    int (S::*fn2) () = &S::foo;
+    int (S::*fn3) () = &S::bar;
+    S s;
+    return fn1 () + (s.*fn2) () + (s.*fn3) ();
+  }
+  return 0;
+}
+
+static_assert (bar () == 45);


	Jakub
  
Jason Merrill Oct. 26, 2021, 8:58 p.m. UTC | #3
On 10/21/21 07:17, Jakub Jelinek wrote:
> On Wed, Oct 20, 2021 at 07:16:44PM -0400, Jason Merrill wrote:
>> or an unevaluated operand, or a subexpression of an immediate invocation.
>>
>> Hmm...that suggests that in consteval23.C, bar(foo) should also be OK,
> 
> Ouch.  That opens a big can of worms, see below.
> 
>> because 'foo' is a subexpression of an immediate invocation.  We can handle
>> that by...
>>
>>> +
>>> +bool
>>> +in_immediate_context ()
>>> +{
>>> +  return (cp_unevaluated_operand != 0
>>> +	  || (current_function_decl != NULL_TREE
>>> +	      && DECL_IMMEDIATE_FUNCTION_P (current_function_decl))
>>> +	  || (current_binding_level->kind == sk_function_parms
>>> +	      && current_binding_level->immediate_fn_ctx_p)
>>> +	  || in_consteval_if_p);
>>> +}
>>> +
>>>    /* Return true if a call to FN with number of arguments NARGS
>>>       is an immediate invocation.  */
>>> @@ -9451,6 +9459,12 @@ build_over_call (struct z_candidate *can
>>>        }
>>>      /* Default arguments */
>>> +  bool save_in_consteval_if_p = in_consteval_if_p;
>>> +  /* If the call is immediate function invocation, make sure
>>> +     taking address of immediate functions is allowed in default
>>> +     arguments.  */
>>> +  if (immediate_invocation_p (STRIP_TEMPLATE (fn), nargs))
>>> +    in_consteval_if_p = true;
>>
>> ...moving this earlier in build_over_call, e.g. shortly after
>> not_really_used:
>>
>> You can also use make_temp_override.
> 
> make_temp_override can't be used, because in_consteval_if_p is BOOL_BITFIELD
> under the hood and binding a reference to a bit-field is not valid.
> 
> In the patch I've used a specialized in_consteval_if_p_temp_override class
> instead.  And it either needs to be restored after all the argument
> processing, because we call immediate_invocation_p again and because of
> the in_consteval_if_p override it is then false (the patch calls icip.reset
> ()), or we'd need to remember the result of the first immediate_invocation_p
> call in some bool temporary and reuse that later.
> Updated patch below.
> 
> This fixes the static_assert (bar (foo) == 42); case newly added to
> consteval23.C, but unfortunately that isn't enough.
> Consider incremental patch to the testcase:
> --- gcc/testsuite/g++.dg/cpp2a/consteval23.C.jj	2021-10-21 11:32:48.570586881 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/consteval23.C	2021-10-21 12:47:42.281769906 +0200
> @@ -2,6 +2,7 @@
>   // { dg-do compile { target c++20 } }
>   
>   consteval int foo () { return 42; }
> +constexpr auto baz (int (*fn) ()) { return fn; }
>   
>   consteval int
>   bar (int (*fn) () = foo)
> @@ -11,3 +12,5 @@ bar (int (*fn) () = foo)
>   
>   static_assert (bar () == 42);
>   static_assert (bar (foo) == 42);
> +static_assert (bar (&foo) == 42);
> +static_assert (bar (baz (&foo)) == 42);
> 
> If even the last two lines are supposed to be valid, then I'm afraid we
> need to reconsider the errors performed in cp_build_addr_expr_1, because
> when that is called, we often don't know if we are in a subexpression
> of immediate invocation or not, whether it is because we are in template,
> something non-type dependent but other argumeents of the function call are
> type dependent, or because it is an overloaded function and some overloads
> are consteval and others are not and we are deep in a middle of some
> argument parsing and other arguments haven't been parsed at all yet, etc.
> 
> I'm afraid I don't have a good idea where to move that diagnostic to though,
> it would need to be done somewhere where we are certain we aren't in a
> subexpression of immediate invocation.  Given statement expressions, even
> diagnostics after parsing whole statements might not be good enough, e.g.
> void
> qux ()
> {
>    static_assert (bar (({ constexpr auto a = 1; foo; })) == 42);
> }

I suppose (a wrapper for) fold_build_cleanup_point_expr would be a 
possible place to check, since that's called for full-expressions.

> But if we e.g. diagnose it somewhere after parsing the whole function (and
> for templates only after instantiating it) plus where we handle
> non-automatic variable initializers etc., will we handle all spots where
> we should diagnose it?  It better should be done before cp_fold...

> In whatever spot is right doing something similar to what
> cp_walk_tree find_immediate_fndecl, does, except probably error out on each
> case we see (with the right locus) instead of just finding the first match.
> 
> Note, trying clang++ on godbolt on the consteval23.C testcase with the above
> ammendments, it diagnoses the = foo default argument, that seems to be a
> clang bug to me, but if I comment out the
> static_assert (bar () == 42); test and comment out the default argument
> bar (int (*fn) () /* = foo */)
> then it succeeds.  It even accepts the above qux with statement expression.

This patch is OK.

> 2021-10-21  Jakub Jelinek  <jakub@redhat.com>
> 
> 	PR c++/102753
> 	* cp-tree.h (saved_scope): Document that consteval_if_p member
> 	is also set while processing immediate invocation.
> 	(in_immediate_context): Declare.
> 	* call.c (in_immediate_context): New function.
> 	(immediate_invocation_p): Use it.
> 	(struct in_consteval_if_p_temp_override): New class.
> 	(build_over_call): Temporarily set in_consteval_if_p for processing
> 	immediate invocation arguments.
> 	* typeck.c (cp_build_addr_expr_1): Diagnose taking address of
> 	an immediate method.  Use t instead of TREE_OPERAND (arg, 1).
> 	Use in_immediate_context function.
> 	* constexpr.c (find_immediate_fndecl): Handle PTRMEM_CST
> 	which refers to immediate function decl.
> 
> 	* g++.dg/cpp2a/consteval13.C: Don't expect errors.
> 	* g++.dg/cpp2a/consteval20.C: New test.
> 	* g++.dg/cpp2a/consteval21.C: New test.
> 	* g++.dg/cpp2a/consteval22.C: New test.
> 	* g++.dg/cpp2a/consteval23.C: New test.
> 	* g++.dg/cpp23/consteval-if11.C: New test.
> 
> --- gcc/cp/cp-tree.h.jj	2021-10-20 08:35:26.850254029 +0200
> +++ gcc/cp/cp-tree.h	2021-10-21 11:36:51.636186234 +0200
> @@ -1825,7 +1825,8 @@ struct GTY(()) saved_scope {
>        if-statement.  */
>     BOOL_BITFIELD discarded_stmt : 1;
>     /* Nonzero if we are parsing or instantiating the compound-statement
> -     of consteval if statement.  */
> +     of consteval if statement.  Also set while processing an immediate
> +     invocation.  */
>     BOOL_BITFIELD consteval_if_p : 1;
>   
>     int unevaluated_operand;
> @@ -6547,6 +6548,7 @@ extern tree perform_direct_initializatio
>                                                          tsubst_flags_t);
>   extern vec<tree,va_gc> *resolve_args (vec<tree,va_gc>*, tsubst_flags_t);
>   extern tree in_charge_arg_for_name		(tree);
> +extern bool in_immediate_context		();
>   extern tree build_cxx_call			(tree, int, tree *,
>   						 tsubst_flags_t,
>   						 tree = NULL_TREE);
> --- gcc/cp/call.c.jj	2021-10-20 08:35:26.808254618 +0200
> +++ gcc/cp/call.c	2021-10-21 12:10:42.591798807 +0200
> @@ -9025,6 +9025,20 @@ build_trivial_dtor_call (tree instance,
>   		 instance, clobber);
>   }
>   
> +/* Return true if in an immediate function context, or an unevaluated operand,
> +   or a subexpression of an immediate invocation.  */
> +
> +bool
> +in_immediate_context ()
> +{
> +  return (cp_unevaluated_operand != 0
> +	  || (current_function_decl != NULL_TREE
> +	      && DECL_IMMEDIATE_FUNCTION_P (current_function_decl))
> +	  || (current_binding_level->kind == sk_function_parms
> +	      && current_binding_level->immediate_fn_ctx_p)
> +	  || in_consteval_if_p);
> +}
> +
>   /* Return true if a call to FN with number of arguments NARGS
>      is an immediate invocation.  */
>   
> @@ -9033,18 +9047,25 @@ immediate_invocation_p (tree fn, int nar
>   {
>     return (TREE_CODE (fn) == FUNCTION_DECL
>   	  && DECL_IMMEDIATE_FUNCTION_P (fn)
> -	  && cp_unevaluated_operand == 0
> -	  && (current_function_decl == NULL_TREE
> -	      || !DECL_IMMEDIATE_FUNCTION_P (current_function_decl))
> -	  && (current_binding_level->kind != sk_function_parms
> -	      || !current_binding_level->immediate_fn_ctx_p)
> -	  && !in_consteval_if_p
> +	  && !in_immediate_context ()
>   	  /* As an exception, we defer std::source_location::current ()
>   	     invocations until genericization because LWG3396 mandates
>   	     special behavior for it.  */
>   	  && (nargs > 1 || !source_location_current_p (fn)));
>   }
>   
> +/* temp_override for in_consteval_if_p, which can't use make_temp_override
> +   because it is a bitfield.  */
> +
> +struct in_consteval_if_p_temp_override {
> +  bool save_in_consteval_if_p;
> +  in_consteval_if_p_temp_override ()
> +    : save_in_consteval_if_p (in_consteval_if_p) {}
> +  void reset () { in_consteval_if_p = save_in_consteval_if_p; }
> +  ~in_consteval_if_p_temp_override ()
> +  { reset (); }
> +};
> +
>   /* Subroutine of the various build_*_call functions.  Overload resolution
>      has chosen a winning candidate CAND; build up a CALL_EXPR accordingly.
>      ARGS is a TREE_LIST of the unconverted arguments to the call.  FLAGS is a
> @@ -9254,6 +9275,12 @@ build_over_call (struct z_candidate *can
>       nargs = parmlen;
>     argarray = XALLOCAVEC (tree, nargs);
>   
> +  in_consteval_if_p_temp_override icip;
> +  /* If the call is immediate function invocation, make sure
> +     taking address of immediate functions is allowed in its arguments.  */
> +  if (immediate_invocation_p (STRIP_TEMPLATE (fn), nargs))
> +    in_consteval_if_p = true;
> +
>     /* The implicit parameters to a constructor are not considered by overload
>        resolution, and must be of the proper type.  */
>     if (DECL_CONSTRUCTOR_P (fn))
> @@ -9498,6 +9525,7 @@ build_over_call (struct z_candidate *can
>   
>     gcc_assert (j <= nargs);
>     nargs = j;
> +  icip.reset ();
>   
>     /* Avoid performing argument transformation if warnings are disabled.
>        When tf_warning is set and at least one of the warnings is active
> --- gcc/cp/typeck.c.jj	2021-10-20 08:35:26.865253819 +0200
> +++ gcc/cp/typeck.c	2021-10-21 11:29:43.040182567 +0200
> @@ -6773,9 +6773,19 @@ cp_build_addr_expr_1 (tree arg, bool str
>   	    return error_mark_node;
>   	  }
>   
> +	if (TREE_CODE (t) == FUNCTION_DECL
> +	    && DECL_IMMEDIATE_FUNCTION_P (t)
> +	    && !in_immediate_context ())
> +	  {
> +	    if (complain & tf_error)
> +	      error_at (loc, "taking address of an immediate function %qD",
> +			t);
> +	    return error_mark_node;
> +	  }
> +
>   	type = build_ptrmem_type (context_for_name_lookup (t),
>   				  TREE_TYPE (t));
> -	t = make_ptrmem_cst (type, TREE_OPERAND (arg, 1));
> +	t = make_ptrmem_cst (type, t);
>   	return t;
>         }
>   
> @@ -6800,9 +6810,7 @@ cp_build_addr_expr_1 (tree arg, bool str
>         tree stripped_arg = tree_strip_any_location_wrapper (arg);
>         if (TREE_CODE (stripped_arg) == FUNCTION_DECL
>   	  && DECL_IMMEDIATE_FUNCTION_P (stripped_arg)
> -	  && cp_unevaluated_operand == 0
> -	  && (current_function_decl == NULL_TREE
> -	      || !DECL_IMMEDIATE_FUNCTION_P (current_function_decl)))
> +	  && !in_immediate_context ())
>   	{
>   	  if (complain & tf_error)
>   	    error_at (loc, "taking address of an immediate function %qD",
> --- gcc/cp/constexpr.c.jj	2021-10-20 08:35:26.825254380 +0200
> +++ gcc/cp/constexpr.c	2021-10-21 11:29:43.041182553 +0200
> @@ -7276,6 +7276,10 @@ find_immediate_fndecl (tree *tp, int */*
>   {
>     if (TREE_CODE (*tp) == FUNCTION_DECL && DECL_IMMEDIATE_FUNCTION_P (*tp))
>       return *tp;
> +  if (TREE_CODE (*tp) == PTRMEM_CST
> +      && TREE_CODE (PTRMEM_CST_MEMBER (*tp)) == FUNCTION_DECL
> +      && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (*tp)))
> +    return PTRMEM_CST_MEMBER (*tp);
>     return NULL_TREE;
>   }
>   
> --- gcc/testsuite/g++.dg/cpp2a/consteval13.C.jj	2020-01-12 11:54:37.140402440 +0100
> +++ gcc/testsuite/g++.dg/cpp2a/consteval13.C	2021-10-21 12:24:33.935180868 +0200
> @@ -10,8 +10,8 @@ void
>   foo ()
>   {
>      auto qux = [] (fnptr a = quux ()) consteval { return a (); };
> -   constexpr auto c = qux (baz);	// { dg-error "28:taking address of an immediate function" }
> -   constexpr auto d = qux (bar);	// { dg-error "28:taking address of an immediate function" }
> +   constexpr auto c = qux (baz);
> +   constexpr auto d = qux (bar);
>      static_assert (c == 1);
>      static_assert (d == 42);
>   }
> --- gcc/testsuite/g++.dg/cpp2a/consteval20.C.jj	2021-10-21 11:29:43.041182553 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/consteval20.C	2021-10-21 11:29:43.041182553 +0200
> @@ -0,0 +1,24 @@
> +// PR c++/102753
> +// { dg-do compile { target c++20 } }
> +
> +struct S {
> +  consteval int foo () const { return 42; }
> +};
> +
> +constexpr S s;
> +
> +int
> +bar ()
> +{
> +  return (s.*&S::foo) ();		// { dg-error "taking address of an immediate function" }
> +}
> +
> +constexpr auto a = &S::foo;		// { dg-error "taking address of an immediate function" }
> +
> +consteval int
> +baz ()
> +{
> +  return (s.*&S::foo) ();
> +}
> +
> +static_assert (baz () == 42);
> --- gcc/testsuite/g++.dg/cpp2a/consteval21.C.jj	2021-10-21 11:29:43.041182553 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/consteval21.C	2021-10-21 11:29:43.041182553 +0200
> @@ -0,0 +1,35 @@
> +// PR c++/102753
> +// { dg-do compile { target c++20 } }
> +
> +struct S {
> +  constexpr S () : s (0) {}
> +  consteval int foo () { return 1; }
> +  virtual consteval int bar () { return 2; }
> +  int s;
> +};
> +
> +consteval int foo () { return 42; }
> +
> +consteval int
> +bar (int (*fn) () = &foo)
> +{
> +  return fn ();
> +}
> +
> +consteval int
> +baz (int (S::*fn) () = &S::foo)
> +{
> +  S s;
> +  return (s.*fn) ();
> +}
> +
> +consteval int
> +qux (int (S::*fn) () = &S::bar)
> +{
> +  S s;
> +  return (s.*fn) ();
> +}
> +
> +static_assert (bar () == 42);
> +static_assert (baz () == 1);
> +static_assert (qux () == 2);
> --- gcc/testsuite/g++.dg/cpp2a/consteval22.C.jj	2021-10-21 11:29:43.041182553 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/consteval22.C	2021-10-21 11:29:43.041182553 +0200
> @@ -0,0 +1,34 @@
> +// PR c++/102753
> +// { dg-do compile { target c++20 } }
> +
> +struct S {
> +  constexpr S () : s (0) {}
> +  consteval int foo () { return 1; }
> +  virtual consteval int bar () { return 2; }
> +  int s;
> +};
> +typedef int (S::*P) ();
> +
> +consteval P
> +foo ()
> +{
> +  return &S::foo;
> +}
> +
> +consteval P
> +bar ()
> +{
> +  return &S::bar;
> +}
> +
> +consteval int
> +baz ()
> +{
> +  S s;
> +  return (s.*(foo ())) () + (s.*(bar ())) ();
> +}
> +
> +static_assert (baz () == 3);
> +
> +constexpr P a = foo ();		// { dg-error "immediate evaluation returns address of immediate function" }
> +constexpr P b = bar ();		// { dg-error "immediate evaluation returns address of immediate function" }
> --- gcc/testsuite/g++.dg/cpp2a/consteval23.C.jj	2021-10-21 11:29:43.041182553 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/consteval23.C	2021-10-21 11:32:48.570586881 +0200
> @@ -0,0 +1,13 @@
> +// PR c++/102753
> +// { dg-do compile { target c++20 } }
> +
> +consteval int foo () { return 42; }
> +
> +consteval int
> +bar (int (*fn) () = foo)
> +{
> +  return fn ();
> +}
> +
> +static_assert (bar () == 42);
> +static_assert (bar (foo) == 42);
> --- gcc/testsuite/g++.dg/cpp23/consteval-if11.C.jj	2021-10-21 11:29:43.041182553 +0200
> +++ gcc/testsuite/g++.dg/cpp23/consteval-if11.C	2021-10-21 11:29:43.041182553 +0200
> @@ -0,0 +1,27 @@
> +// PR c++/102753
> +// { dg-do compile { target c++20 } }
> +// { dg-options "" }
> +
> +struct S {
> +  constexpr S () : s (0) {}
> +  consteval int foo () { return 1; }
> +  virtual consteval int bar () { return 2; }
> +  int s;
> +};
> +
> +consteval int foo () { return 42; }
> +
> +constexpr int
> +bar ()
> +{
> +  if consteval {	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
> +    int (*fn1) () = foo;
> +    int (S::*fn2) () = &S::foo;
> +    int (S::*fn3) () = &S::bar;
> +    S s;
> +    return fn1 () + (s.*fn2) () + (s.*fn3) ();
> +  }
> +  return 0;
> +}
> +
> +static_assert (bar () == 45);
> 
> 
> 	Jakub
>
  
Jakub Jelinek Oct. 29, 2021, 3:24 p.m. UTC | #4
On Tue, Oct 26, 2021 at 04:58:11PM -0400, Jason Merrill wrote:
> > I'm afraid I don't have a good idea where to move that diagnostic to though,
> > it would need to be done somewhere where we are certain we aren't in a
> > subexpression of immediate invocation.  Given statement expressions, even
> > diagnostics after parsing whole statements might not be good enough, e.g.
> > void
> > qux ()
> > {
> >    static_assert (bar (({ constexpr auto a = 1; foo; })) == 42);
> > }
> 
> I suppose (a wrapper for) fold_build_cleanup_point_expr would be a possible
> place to check, since that's called for full-expressions.

I've played a little bit with this (tried to do it at cp_fold time), but
there are problems with that.
cp_fold of course isn't a good spot for this because it can be called from
fold_for_warn and at that point we don't know if we are inside of immediate
invocation's argument or not, or it can be called even inside of consteval
fn bodies etc.  So, let's suppose we do a separate cp_walk_tree just for
this if cxx_dialect >= cxx20 e.g. from cp_fold_function and
cp_fully_fold_init or some other useful spot, like in the patch below
we avoid walking into THEN_CLAUSE of IF_STMT_CONSTEVAL_P IF_STMTs.
And if this would be done before cp_fold_function's cp_fold_r walk,
we'd also need calls to source_location_current_p as an exception.
The major problem is the location used for the error_at,
e.g. the ADDR_EXPRs pretty much never EXPR_HAS_LOCATION and PTRMEM_CST
doesn't even have location, so while we would report diagnostics, it would
be always
cc1plus: error: taking address of an immediate function ‘consteval int S::foo() const’
etc.
I guess one option is to report it even later, during gimplification where
gimplify_expr etc. track input_location, but what to do with static
initializers?
Another option would be to have a walk_tree_1 variant that would be updating
input_location similarly to how gimplify_expr does that, i.e.
  saved_location = input_location;
  if (save_expr != error_mark_node
      && EXPR_HAS_LOCATION (*expr_p))
    input_location = EXPR_LOCATION (*expr_p);
...
  input_location = saved_location;
but probably using RAII because walk_tree_1 has a lot of returns in it.
And turn walk_tree_1 into a template instantiated twice, once as walk_tree_1
without the input_location handling in it and once with it under some
different name?
Or do we have some other expression walker that does update input_location
as it goes?

--- gcc/cp/typeck.c.jj	2021-10-27 09:03:07.555043491 +0200
+++ gcc/cp/typeck.c	2021-10-29 15:59:57.871449304 +0200
@@ -6773,16 +6773,6 @@ cp_build_addr_expr_1 (tree arg, bool str
 	    return error_mark_node;
 	  }
 
-	if (TREE_CODE (t) == FUNCTION_DECL
-	    && DECL_IMMEDIATE_FUNCTION_P (t)
-	    && !in_immediate_context ())
-	  {
-	    if (complain & tf_error)
-	      error_at (loc, "taking address of an immediate function %qD",
-			t);
-	    return error_mark_node;
-	  }
-
 	type = build_ptrmem_type (context_for_name_lookup (t),
 				  TREE_TYPE (t));
 	t = make_ptrmem_cst (type, t);
@@ -6809,15 +6799,6 @@ cp_build_addr_expr_1 (tree arg, bool str
     {
       tree stripped_arg = tree_strip_any_location_wrapper (arg);
       if (TREE_CODE (stripped_arg) == FUNCTION_DECL
-	  && DECL_IMMEDIATE_FUNCTION_P (stripped_arg)
-	  && !in_immediate_context ())
-	{
-	  if (complain & tf_error)
-	    error_at (loc, "taking address of an immediate function %qD",
-		      stripped_arg);
-	  return error_mark_node;
-	}
-      if (TREE_CODE (stripped_arg) == FUNCTION_DECL
 	  && !mark_used (stripped_arg, complain) && !(complain & tf_error))
 	return error_mark_node;
       val = build_address (arg);
--- gcc/cp/cp-gimplify.c.jj	2021-09-18 09:47:08.409573816 +0200
+++ gcc/cp/cp-gimplify.c	2021-10-29 16:48:42.308261319 +0200
@@ -902,6 +902,17 @@ cp_fold_r (tree *stmt_p, int *walk_subtr
 	}
       cp_walk_tree (&OMP_FOR_PRE_BODY (stmt), cp_fold_r, data, NULL);
       *walk_subtrees = 0;
+      return NULL;
+    }
+
+  if (code == IF_STMT && IF_STMT_CONSTEVAL_P (stmt))
+    {
+      /* Don't walk THEN_CLAUSE (stmt) for consteval if.  IF_COND is always
+	 boolean_false_node.  */
+      cp_walk_tree (&ELSE_CLAUSE (stmt), cp_fold_r, data, NULL);
+      cp_walk_tree (&IF_SCOPE (stmt), cp_fold_r, data, NULL);
+      *walk_subtrees = 0;
+      return NULL;
     }
 
   return NULL;
@@ -1418,9 +1429,9 @@ cp_genericize_r (tree *stmt_p, int *walk
 	}
 
       if (tree fndecl = cp_get_callee_fndecl_nofold (stmt))
-	if (DECL_IMMEDIATE_FUNCTION_P (fndecl))
+	if (DECL_IMMEDIATE_FUNCTION_P (fndecl)
+	    && source_location_current_p (fndecl))
 	  {
-	    gcc_assert (source_location_current_p (fndecl));
 	    *stmt_p = cxx_constant_value (stmt);
 	    break;
 	  }
@@ -2319,8 +2330,28 @@ cp_fold (tree x)
 	}
       goto unary;
 
+    case PTRMEM_CST:
+      if (TREE_CODE (PTRMEM_CST_MEMBER (x)) == FUNCTION_DECL
+	  && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (x)))
+	{
+	  error_at (input_location,
+		    "taking address of an immediate function %qD",
+		    PTRMEM_CST_MEMBER (x));
+	  x = error_mark_node;
+	  break;
+	}
+      break;
+
     case ADDR_EXPR:
       loc = EXPR_LOCATION (x);
+      if (TREE_CODE (TREE_OPERAND (x, 0)) == FUNCTION_DECL
+	  && DECL_IMMEDIATE_FUNCTION_P (TREE_OPERAND (x, 0)))
+	{
+	  error_at (loc, "taking address of an immediate function %qD",
+		    TREE_OPERAND (x, 0));
+	  x = error_mark_node;
+	  break;
+	}
       op0 = cp_fold_maybe_rvalue (TREE_OPERAND (x, 0), false);
 
       /* Cope with user tricks that amount to offsetof.  */


	Jakub
  
Jason Merrill Nov. 23, 2021, 8:45 p.m. UTC | #5
On 10/29/21 11:24, Jakub Jelinek wrote:
> On Tue, Oct 26, 2021 at 04:58:11PM -0400, Jason Merrill wrote:
>>> I'm afraid I don't have a good idea where to move that diagnostic to though,
>>> it would need to be done somewhere where we are certain we aren't in a
>>> subexpression of immediate invocation.  Given statement expressions, even
>>> diagnostics after parsing whole statements might not be good enough, e.g.
>>> void
>>> qux ()
>>> {
>>>     static_assert (bar (({ constexpr auto a = 1; foo; })) == 42);
>>> }
>>
>> I suppose (a wrapper for) fold_build_cleanup_point_expr would be a possible
>> place to check, since that's called for full-expressions.
> 
> I've played a little bit with this (tried to do it at cp_fold time), but
> there are problems with that.
> cp_fold of course isn't a good spot for this because it can be called from
> fold_for_warn and at that point we don't know if we are inside of immediate
> invocation's argument or not, or it can be called even inside of consteval
> fn bodies etc.

How about checking in cp_fold_r instead of cp_fold?

> So, let's suppose we do a separate cp_walk_tree just for
> this if cxx_dialect >= cxx20 e.g. from cp_fold_function and
> cp_fully_fold_init or some other useful spot, like in the patch below
> we avoid walking into THEN_CLAUSE of IF_STMT_CONSTEVAL_P IF_STMTs.
> And if this would be done before cp_fold_function's cp_fold_r walk,
> we'd also need calls to source_location_current_p as an exception.
> The major problem is the location used for the error_at,
> e.g. the ADDR_EXPRs pretty much never EXPR_HAS_LOCATION and PTRMEM_CST
> doesn't even have location, so while we would report diagnostics, it would
> be always
> cc1plus: error: taking address of an immediate function ‘consteval int S::foo() const’
> etc.

I've checked in a patch to give PTRMEM_CST a location wrapper; perhaps 
that will be helpful.

> I guess one option is to report it even later, during gimplification where
> gimplify_expr etc. track input_location, but what to do with static
> initializers?
> Another option would be to have a walk_tree_1 variant that would be updating
> input_location similarly to how gimplify_expr does that, i.e.
>    saved_location = input_location;
>    if (save_expr != error_mark_node
>        && EXPR_HAS_LOCATION (*expr_p))
>      input_location = EXPR_LOCATION (*expr_p);
> ...
>    input_location = saved_location;
> but probably using RAII because walk_tree_1 has a lot of returns in it.

iloc_sentinel seems relevant.

> And turn walk_tree_1 into a template instantiated twice, once as walk_tree_1
> without the input_location handling in it and once with it under some
> different name?

Maybe just add the handling to walk_tree_1?

> Or do we have some other expression walker that does update input_location
> as it goes?
> 
> --- gcc/cp/typeck.c.jj	2021-10-27 09:03:07.555043491 +0200
> +++ gcc/cp/typeck.c	2021-10-29 15:59:57.871449304 +0200
> @@ -6773,16 +6773,6 @@ cp_build_addr_expr_1 (tree arg, bool str
>   	    return error_mark_node;
>   	  }
>   
> -	if (TREE_CODE (t) == FUNCTION_DECL
> -	    && DECL_IMMEDIATE_FUNCTION_P (t)
> -	    && !in_immediate_context ())
> -	  {
> -	    if (complain & tf_error)
> -	      error_at (loc, "taking address of an immediate function %qD",
> -			t);
> -	    return error_mark_node;
> -	  }
> -
>   	type = build_ptrmem_type (context_for_name_lookup (t),
>   				  TREE_TYPE (t));
>   	t = make_ptrmem_cst (type, t);
> @@ -6809,15 +6799,6 @@ cp_build_addr_expr_1 (tree arg, bool str
>       {
>         tree stripped_arg = tree_strip_any_location_wrapper (arg);
>         if (TREE_CODE (stripped_arg) == FUNCTION_DECL
> -	  && DECL_IMMEDIATE_FUNCTION_P (stripped_arg)
> -	  && !in_immediate_context ())
> -	{
> -	  if (complain & tf_error)
> -	    error_at (loc, "taking address of an immediate function %qD",
> -		      stripped_arg);
> -	  return error_mark_node;
> -	}
> -      if (TREE_CODE (stripped_arg) == FUNCTION_DECL
>   	  && !mark_used (stripped_arg, complain) && !(complain & tf_error))
>   	return error_mark_node;
>         val = build_address (arg);
> --- gcc/cp/cp-gimplify.c.jj	2021-09-18 09:47:08.409573816 +0200
> +++ gcc/cp/cp-gimplify.c	2021-10-29 16:48:42.308261319 +0200
> @@ -902,6 +902,17 @@ cp_fold_r (tree *stmt_p, int *walk_subtr
>   	}
>         cp_walk_tree (&OMP_FOR_PRE_BODY (stmt), cp_fold_r, data, NULL);
>         *walk_subtrees = 0;
> +      return NULL;
> +    }
> +
> +  if (code == IF_STMT && IF_STMT_CONSTEVAL_P (stmt))
> +    {
> +      /* Don't walk THEN_CLAUSE (stmt) for consteval if.  IF_COND is always
> +	 boolean_false_node.  */
> +      cp_walk_tree (&ELSE_CLAUSE (stmt), cp_fold_r, data, NULL);
> +      cp_walk_tree (&IF_SCOPE (stmt), cp_fold_r, data, NULL);
> +      *walk_subtrees = 0;
> +      return NULL;
>       }
>   
>     return NULL;
> @@ -1418,9 +1429,9 @@ cp_genericize_r (tree *stmt_p, int *walk
>   	}
>   
>         if (tree fndecl = cp_get_callee_fndecl_nofold (stmt))
> -	if (DECL_IMMEDIATE_FUNCTION_P (fndecl))
> +	if (DECL_IMMEDIATE_FUNCTION_P (fndecl)
> +	    && source_location_current_p (fndecl))
>   	  {
> -	    gcc_assert (source_location_current_p (fndecl));
>   	    *stmt_p = cxx_constant_value (stmt);
>   	    break;
>   	  }
> @@ -2319,8 +2330,28 @@ cp_fold (tree x)
>   	}
>         goto unary;
>   
> +    case PTRMEM_CST:
> +      if (TREE_CODE (PTRMEM_CST_MEMBER (x)) == FUNCTION_DECL
> +	  && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (x)))
> +	{
> +	  error_at (input_location,
> +		    "taking address of an immediate function %qD",
> +		    PTRMEM_CST_MEMBER (x));
> +	  x = error_mark_node;
> +	  break;
> +	}
> +      break;
> +
>       case ADDR_EXPR:
>         loc = EXPR_LOCATION (x);
> +      if (TREE_CODE (TREE_OPERAND (x, 0)) == FUNCTION_DECL
> +	  && DECL_IMMEDIATE_FUNCTION_P (TREE_OPERAND (x, 0)))
> +	{
> +	  error_at (loc, "taking address of an immediate function %qD",
> +		    TREE_OPERAND (x, 0));
> +	  x = error_mark_node;
> +	  break;
> +	}
>         op0 = cp_fold_maybe_rvalue (TREE_OPERAND (x, 0), false);
>   
>         /* Cope with user tricks that amount to offsetof.  */
> 
> 
> 	Jakub
>
  
Jakub Jelinek Nov. 24, 2021, 4:02 p.m. UTC | #6
On Tue, Nov 23, 2021 at 03:45:20PM -0500, Jason Merrill wrote:
> > I've played a little bit with this (tried to do it at cp_fold time), but
> > there are problems with that.
> > cp_fold of course isn't a good spot for this because it can be called from
> > fold_for_warn and at that point we don't know if we are inside of immediate
> > invocation's argument or not, or it can be called even inside of consteval
> > fn bodies etc.
> 
> How about checking in cp_fold_r instead of cp_fold?

That seems to work.

> > So, let's suppose we do a separate cp_walk_tree just for
> > this if cxx_dialect >= cxx20 e.g. from cp_fold_function and
> > cp_fully_fold_init or some other useful spot, like in the patch below
> > we avoid walking into THEN_CLAUSE of IF_STMT_CONSTEVAL_P IF_STMTs.
> > And if this would be done before cp_fold_function's cp_fold_r walk,
> > we'd also need calls to source_location_current_p as an exception.
> > The major problem is the location used for the error_at,
> > e.g. the ADDR_EXPRs pretty much never EXPR_HAS_LOCATION and PTRMEM_CST
> > doesn't even have location, so while we would report diagnostics, it would
> > be always
> > cc1plus: error: taking address of an immediate function ‘consteval int S::foo() const’
> > etc.
> 
> I've checked in a patch to give PTRMEM_CST a location wrapper; perhaps that
> will be helpful.

Unfortunately, the location wrappers are optimized away before we get a
chance to use them in cp_fold_r.
So, on the following patch, we get the location right on PTRMEM_CSTs not
used inside of initializers, but for PTRMEM_CSTs in initializers we report
them at UNKNOWN_LOCATION.
As PTRMEM_CST is a C++ FE tree, I wonder if we couldn't instead of your
patch or in addition to it do:
 struct GTY(()) ptrmem_cst {
   struct tree_common common;
   tree member;
+  location_t locus;
 };
#define PTRMEM_CST_LOCATION(NODE) \
  (((ptrmem_cst_t)PTRMEM_CST_CHECK (NODE))->locus)
and in make_ptrmem_cst set PTRMEM_CST_LOCATION to input_location.

> > I guess one option is to report it even later, during gimplification where
> > gimplify_expr etc. track input_location, but what to do with static
> > initializers?
> > Another option would be to have a walk_tree_1 variant that would be updating
> > input_location similarly to how gimplify_expr does that, i.e.
> >    saved_location = input_location;
> >    if (save_expr != error_mark_node
> >        && EXPR_HAS_LOCATION (*expr_p))
> >      input_location = EXPR_LOCATION (*expr_p);
> > ...
> >    input_location = saved_location;
> > but probably using RAII because walk_tree_1 has a lot of returns in it.
> 
> iloc_sentinel seems relevant.
> 
> > And turn walk_tree_1 into a template instantiated twice, once as walk_tree_1
> > without the input_location handling in it and once with it under some
> > different name?
> 
> Maybe just add the handling to walk_tree_1?

The vast majority of walk_tree_1 users don't want it to change
input_location as it goes.  But if we have PTRMEM_CST_LOCATION, I'd think we
don't really need it.

--- gcc/cp/typeck.c.jj	2021-11-24 09:54:11.521738651 +0100
+++ gcc/cp/typeck.c	2021-11-24 16:28:52.281468302 +0100
@@ -6780,19 +6780,15 @@ cp_build_addr_expr_1 (tree arg, bool str
 	    return error_mark_node;
 	  }
 
-	if (TREE_CODE (t) == FUNCTION_DECL
-	    && DECL_IMMEDIATE_FUNCTION_P (t)
-	    && !in_immediate_context ())
-	  {
-	    if (complain & tf_error)
-	      error_at (loc, "taking address of an immediate function %qD",
-			t);
-	    return error_mark_node;
-	  }
-
 	type = build_ptrmem_type (context_for_name_lookup (t),
 				  TREE_TYPE (t));
 	t = make_ptrmem_cst (type, t);
+
+	/* For addresses of immediate member functions ensure we have
+	   EXPR_LOCATION set for possible later diagnostics.  */
+	if (TREE_CODE (TREE_OPERAND (arg, 1)) == FUNCTION_DECL
+	    && DECL_IMMEDIATE_FUNCTION_P (TREE_OPERAND (arg, 1)))
+	  t = maybe_wrap_with_location (t, input_location);
 	return t;
       }
 
@@ -6816,15 +6812,6 @@ cp_build_addr_expr_1 (tree arg, bool str
     {
       tree stripped_arg = tree_strip_any_location_wrapper (arg);
       if (TREE_CODE (stripped_arg) == FUNCTION_DECL
-	  && DECL_IMMEDIATE_FUNCTION_P (stripped_arg)
-	  && !in_immediate_context ())
-	{
-	  if (complain & tf_error)
-	    error_at (loc, "taking address of an immediate function %qD",
-		      stripped_arg);
-	  return error_mark_node;
-	}
-      if (TREE_CODE (stripped_arg) == FUNCTION_DECL
 	  && !mark_used (stripped_arg, complain) && !(complain & tf_error))
 	return error_mark_node;
       val = build_address (arg);
@@ -6865,6 +6852,13 @@ cp_build_addr_expr_1 (tree arg, bool str
 			      complain);
     }
 
+  /* For addresses of immediate functions ensure we have EXPR_LOCATION
+     set for possible later diagnostics.  */
+  if (TREE_CODE (val) == ADDR_EXPR
+      && TREE_CODE (TREE_OPERAND (val, 0)) == FUNCTION_DECL
+      && DECL_IMMEDIATE_FUNCTION_P (TREE_OPERAND (val, 0)))
+    SET_EXPR_LOCATION (val, input_location);
+
   return val;
 }
 
--- gcc/cp/cp-gimplify.c.jj	2021-11-19 10:04:54.343314733 +0100
+++ gcc/cp/cp-gimplify.c	2021-11-24 16:20:54.930275872 +0100
@@ -900,8 +900,46 @@ struct cp_genericize_data
 static tree
 cp_fold_r (tree *stmt_p, int *walk_subtrees, void *data)
 {
-  tree stmt;
-  enum tree_code code;
+  tree stmt = *stmt_p;
+  enum tree_code code = TREE_CODE (stmt);
+  location_t loc = UNKNOWN_LOCATION;
+
+  switch (code)
+    {
+    case NON_LVALUE_EXPR:
+      if (TREE_CODE (TREE_OPERAND (stmt, 0)) != PTRMEM_CST)
+	break;
+      loc = EXPR_LOCATION (stmt);
+      stmt = TREE_OPERAND (stmt, 0);
+      /* FALLTHRU */
+    case PTRMEM_CST:
+      if (TREE_CODE (PTRMEM_CST_MEMBER (stmt)) == FUNCTION_DECL
+	  && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (stmt)))
+	{
+	  if (!((hash_set<tree> *) data)->add (stmt))
+	    error_at (loc,
+		      "taking address of an immediate function %qD",
+		      PTRMEM_CST_MEMBER (stmt));
+	  stmt = *stmt_p = build_zero_cst (TREE_TYPE (stmt));
+	  break;
+	}
+      break;
+
+    case ADDR_EXPR:
+      if (TREE_CODE (TREE_OPERAND (stmt, 0)) == FUNCTION_DECL
+	  && DECL_IMMEDIATE_FUNCTION_P (TREE_OPERAND (stmt, 0)))
+	{
+	  loc = EXPR_LOCATION (stmt);
+	  error_at (loc, "taking address of an immediate function %qD",
+		    TREE_OPERAND (stmt, 0));
+	  stmt = *stmt_p = build_zero_cst (TREE_TYPE (stmt));
+	  break;
+	}
+      break;
+
+    default:
+      break;
+    }
 
   *stmt_p = stmt = cp_fold (*stmt_p);
 
@@ -917,12 +955,16 @@ cp_fold_r (tree *stmt_p, int *walk_subtr
     }
 
   code = TREE_CODE (stmt);
-  if (code == OMP_FOR || code == OMP_SIMD || code == OMP_DISTRIBUTE
-      || code == OMP_LOOP || code == OMP_TASKLOOP || code == OACC_LOOP)
+  switch (code)
     {
       tree x;
       int i, n;
-
+    case OMP_FOR:
+    case OMP_SIMD:
+    case OMP_DISTRIBUTE:
+    case OMP_LOOP:
+    case OMP_TASKLOOP:
+    case OACC_LOOP:
       cp_walk_tree (&OMP_FOR_BODY (stmt), cp_fold_r, data, NULL);
       cp_walk_tree (&OMP_FOR_CLAUSES (stmt), cp_fold_r, data, NULL);
       cp_walk_tree (&OMP_FOR_INIT (stmt), cp_fold_r, data, NULL);
@@ -961,6 +1003,22 @@ cp_fold_r (tree *stmt_p, int *walk_subtr
 	}
       cp_walk_tree (&OMP_FOR_PRE_BODY (stmt), cp_fold_r, data, NULL);
       *walk_subtrees = 0;
+      return NULL;
+
+    case IF_STMT:
+      if (IF_STMT_CONSTEVAL_P (stmt))
+	{
+	  /* Don't walk THEN_CLAUSE (stmt) for consteval if.  IF_COND is always
+	     boolean_false_node.  */
+	  cp_walk_tree (&ELSE_CLAUSE (stmt), cp_fold_r, data, NULL);
+	  cp_walk_tree (&IF_SCOPE (stmt), cp_fold_r, data, NULL);
+	  *walk_subtrees = 0;
+	  return NULL;
+	}
+      break;
+
+    default:
+      break;
     }
 
   return NULL;
@@ -1477,9 +1535,9 @@ cp_genericize_r (tree *stmt_p, int *walk
 	}
 
       if (tree fndecl = cp_get_callee_fndecl_nofold (stmt))
-	if (DECL_IMMEDIATE_FUNCTION_P (fndecl))
+	if (DECL_IMMEDIATE_FUNCTION_P (fndecl)
+	    && source_location_current_p (fndecl))
 	  {
-	    gcc_assert (source_location_current_p (fndecl));
 	    *stmt_p = cxx_constant_value (stmt);
 	    break;
 	  }
--- gcc/cp/pt.c.jj	2021-11-24 15:05:23.336928234 +0100
+++ gcc/cp/pt.c	2021-11-24 15:34:29.018014159 +0100
@@ -17012,6 +17012,12 @@ tsubst_copy (tree t, tree args, tsubst_f
 	r = build1 (code, type, op0);
 	if (code == ALIGNOF_EXPR)
 	  ALIGNOF_EXPR_STD_P (r) = ALIGNOF_EXPR_STD_P (t);
+	/* For addresses of immediate functions ensure we have EXPR_LOCATION
+	   set for possible later diagnostics.  */
+	if (code == ADDR_EXPR
+	    && TREE_CODE (op0) == FUNCTION_DECL
+	    && DECL_IMMEDIATE_FUNCTION_P (op0))
+	  SET_EXPR_LOCATION (r, input_location);
 	return r;
       }
 
--- gcc/testsuite/g++.dg/cpp2a/consteval23.C.jj	2021-10-27 09:03:07.576043195 +0200
+++ gcc/testsuite/g++.dg/cpp2a/consteval23.C	2021-11-24 16:41:39.431532798 +0100
@@ -2,6 +2,7 @@
 // { dg-do compile { target c++20 } }
 
 consteval int foo () { return 42; }
+constexpr auto baz (int (*fn) ()) { return fn; }
 
 consteval int
 bar (int (*fn) () = foo)
@@ -11,3 +12,6 @@ bar (int (*fn) () = foo)
 
 static_assert (bar () == 42);
 static_assert (bar (foo) == 42);
+static_assert (bar (&foo) == 42);
+static_assert (bar (baz (foo)) == 42);
+static_assert (bar (baz (&foo)) == 42);
--- gcc/testsuite/g++.dg/cpp2a/consteval20.C.jj	2021-10-27 09:03:07.576043195 +0200
+++ gcc/testsuite/g++.dg/cpp2a/consteval20.C	2021-11-24 16:49:40.481679281 +0100
@@ -10,10 +10,14 @@ constexpr S s;
 int
 bar ()
 {
+  auto c = &S::foo;			// { dg-error "taking address of an immediate function" }
+  constexpr auto d = &S::foo;		// { dg-error "taking address of an immediate function" }
+  static auto e = &S::foo;		// { dg-error "taking address of an immediate function" }
   return (s.*&S::foo) ();		// { dg-error "taking address of an immediate function" }
 }
 
 constexpr auto a = &S::foo;		// { dg-error "taking address of an immediate function" }
+auto b = &S::foo;			// { dg-error "taking address of an immediate function" }
 
 consteval int
 baz ()


	Jakub
  

Patch

--- gcc/cp/cp-tree.h.jj	2021-10-15 11:58:44.968133548 +0200
+++ gcc/cp/cp-tree.h	2021-10-19 10:40:58.375799274 +0200
@@ -6547,6 +6547,7 @@  extern tree perform_direct_initializatio
                                                        tsubst_flags_t);
 extern vec<tree,va_gc> *resolve_args (vec<tree,va_gc>*, tsubst_flags_t);
 extern tree in_charge_arg_for_name		(tree);
+extern bool in_immediate_context		();
 extern tree build_cxx_call			(tree, int, tree *,
 						 tsubst_flags_t,
 						 tree = NULL_TREE);
--- gcc/cp/call.c.jj	2021-10-15 11:58:44.947133850 +0200
+++ gcc/cp/call.c	2021-10-19 13:04:42.333421774 +0200
@@ -9025,6 +9025,19 @@  build_trivial_dtor_call (tree instance,
 		 instance, clobber);
 }
 
+/* Return true if in an immediate function context.  */
+
+bool
+in_immediate_context ()
+{
+  return (cp_unevaluated_operand != 0
+	  || (current_function_decl != NULL_TREE
+	      && DECL_IMMEDIATE_FUNCTION_P (current_function_decl))
+	  || (current_binding_level->kind == sk_function_parms
+	      && current_binding_level->immediate_fn_ctx_p)
+	  || in_consteval_if_p);
+}
+
 /* Return true if a call to FN with number of arguments NARGS
    is an immediate invocation.  */
 
@@ -9033,12 +9046,7 @@  immediate_invocation_p (tree fn, int nar
 {
   return (TREE_CODE (fn) == FUNCTION_DECL
 	  && DECL_IMMEDIATE_FUNCTION_P (fn)
-	  && cp_unevaluated_operand == 0
-	  && (current_function_decl == NULL_TREE
-	      || !DECL_IMMEDIATE_FUNCTION_P (current_function_decl))
-	  && (current_binding_level->kind != sk_function_parms
-	      || !current_binding_level->immediate_fn_ctx_p)
-	  && !in_consteval_if_p
+	  && !in_immediate_context ()
 	  /* As an exception, we defer std::source_location::current ()
 	     invocations until genericization because LWG3396 mandates
 	     special behavior for it.  */
@@ -9451,6 +9459,12 @@  build_over_call (struct z_candidate *can
     }
 
   /* Default arguments */
+  bool save_in_consteval_if_p = in_consteval_if_p;
+  /* If the call is immediate function invocation, make sure
+     taking address of immediate functions is allowed in default
+     arguments.  */
+  if (immediate_invocation_p (STRIP_TEMPLATE (fn), nargs))
+    in_consteval_if_p = true;
   for (; parm && parm != void_list_node; parm = TREE_CHAIN (parm), i++)
     {
       if (TREE_VALUE (parm) == error_mark_node)
@@ -9463,6 +9477,7 @@  build_over_call (struct z_candidate *can
         return error_mark_node;
       argarray[j++] = val;
     }
+  in_consteval_if_p = save_in_consteval_if_p;
 
   /* Ellipsis */
   int magic = magic_varargs_p (fn);
--- gcc/cp/typeck.c.jj	2021-10-18 11:01:08.635858336 +0200
+++ gcc/cp/typeck.c	2021-10-19 10:42:18.454671723 +0200
@@ -6773,9 +6773,19 @@  cp_build_addr_expr_1 (tree arg, bool str
 	    return error_mark_node;
 	  }
 
+	if (TREE_CODE (t) == FUNCTION_DECL
+	    && DECL_IMMEDIATE_FUNCTION_P (t)
+	    && !in_immediate_context ())
+	  {
+	    if (complain & tf_error)
+	      error_at (loc, "taking address of an immediate function %qD",
+			t);
+	    return error_mark_node;
+	  }
+
 	type = build_ptrmem_type (context_for_name_lookup (t),
 				  TREE_TYPE (t));
-	t = make_ptrmem_cst (type, TREE_OPERAND (arg, 1));
+	t = make_ptrmem_cst (type, t);
 	return t;
       }
 
@@ -6800,9 +6810,7 @@  cp_build_addr_expr_1 (tree arg, bool str
       tree stripped_arg = tree_strip_any_location_wrapper (arg);
       if (TREE_CODE (stripped_arg) == FUNCTION_DECL
 	  && DECL_IMMEDIATE_FUNCTION_P (stripped_arg)
-	  && cp_unevaluated_operand == 0
-	  && (current_function_decl == NULL_TREE
-	      || !DECL_IMMEDIATE_FUNCTION_P (current_function_decl)))
+	  && !in_immediate_context ())
 	{
 	  if (complain & tf_error)
 	    error_at (loc, "taking address of an immediate function %qD",
--- gcc/cp/constexpr.c.jj	2021-10-19 09:24:41.938242276 +0200
+++ gcc/cp/constexpr.c	2021-10-19 12:22:35.583964001 +0200
@@ -7276,6 +7276,10 @@  find_immediate_fndecl (tree *tp, int */*
 {
   if (TREE_CODE (*tp) == FUNCTION_DECL && DECL_IMMEDIATE_FUNCTION_P (*tp))
     return *tp;
+  if (TREE_CODE (*tp) == PTRMEM_CST
+      && TREE_CODE (PTRMEM_CST_MEMBER (*tp)) == FUNCTION_DECL
+      && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (*tp)))
+    return PTRMEM_CST_MEMBER (*tp);
   return NULL_TREE;
 }
 
--- gcc/testsuite/g++.dg/cpp2a/consteval20.C.jj	2021-10-19 10:29:30.471484783 +0200
+++ gcc/testsuite/g++.dg/cpp2a/consteval20.C	2021-10-19 10:29:30.471484783 +0200
@@ -0,0 +1,24 @@ 
+// PR c++/102753
+// { dg-do compile { target c++20 } }
+
+struct S {
+  consteval int foo () const { return 42; }
+};
+
+constexpr S s;
+
+int
+bar ()
+{
+  return (s.*&S::foo) ();		// { dg-error "taking address of an immediate function" }
+}
+
+constexpr auto a = &S::foo;		// { dg-error "taking address of an immediate function" }
+
+consteval int
+baz ()
+{
+  return (s.*&S::foo) ();
+}
+
+static_assert (baz () == 42);
--- gcc/testsuite/g++.dg/cpp2a/consteval21.C.jj	2021-10-19 11:09:18.890838778 +0200
+++ gcc/testsuite/g++.dg/cpp2a/consteval21.C	2021-10-19 11:57:28.309175141 +0200
@@ -0,0 +1,35 @@ 
+// PR c++/102753
+// { dg-do compile { target c++20 } }
+
+struct S {
+  constexpr S () : s (0) {}
+  consteval int foo () { return 1; }
+  virtual consteval int bar () { return 2; }
+  int s;
+};
+
+consteval int foo () { return 42; }
+
+consteval int
+bar (int (*fn) () = &foo)
+{
+  return fn ();
+}
+
+consteval int
+baz (int (S::*fn) () = &S::foo)
+{
+  S s;
+  return (s.*fn) ();
+}
+
+consteval int
+qux (int (S::*fn) () = &S::bar)
+{
+  S s;
+  return (s.*fn) ();
+}
+
+static_assert (bar () == 42);
+static_assert (baz () == 1);
+static_assert (qux () == 2);
--- gcc/testsuite/g++.dg/cpp2a/consteval22.C.jj	2021-10-19 11:21:44.271346868 +0200
+++ gcc/testsuite/g++.dg/cpp2a/consteval22.C	2021-10-19 12:23:31.783173408 +0200
@@ -0,0 +1,34 @@ 
+// PR c++/102753
+// { dg-do compile { target c++20 } }
+
+struct S {
+  constexpr S () : s (0) {}
+  consteval int foo () { return 1; }
+  virtual consteval int bar () { return 2; }
+  int s;
+};
+typedef int (S::*P) ();
+
+consteval P
+foo ()
+{
+  return &S::foo;
+}
+
+consteval P
+bar ()
+{
+  return &S::bar;
+}
+
+consteval int
+baz ()
+{
+  S s;
+  return (s.*(foo ())) () + (s.*(bar ())) ();
+}
+
+static_assert (baz () == 3);
+
+constexpr P a = foo ();		// { dg-error "immediate evaluation returns address of immediate function" }
+constexpr P b = bar ();		// { dg-error "immediate evaluation returns address of immediate function" }
--- gcc/testsuite/g++.dg/cpp2a/consteval23.C.jj	2021-10-19 12:23:54.235857548 +0200
+++ gcc/testsuite/g++.dg/cpp2a/consteval23.C	2021-10-19 12:24:33.931299123 +0200
@@ -0,0 +1,12 @@ 
+// PR c++/102753
+// { dg-do compile { target c++20 } }
+
+consteval int foo () { return 42; }
+
+consteval int
+bar (int (*fn) () = foo)
+{
+  return fn ();
+}
+
+static_assert (bar () == 42);
--- gcc/testsuite/g++.dg/cpp23/consteval-if11.C.jj	2021-10-19 11:17:25.964982502 +0200
+++ gcc/testsuite/g++.dg/cpp23/consteval-if11.C	2021-10-19 11:35:38.878602026 +0200
@@ -0,0 +1,27 @@ 
+// PR c++/102753
+// { dg-do compile { target c++20 } }
+// { dg-options "" }
+
+struct S {
+  constexpr S () : s (0) {}
+  consteval int foo () { return 1; }
+  virtual consteval int bar () { return 2; }
+  int s;
+};
+
+consteval int foo () { return 42; }
+
+constexpr int
+bar ()
+{
+  if consteval {	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
+    int (*fn1) () = foo;
+    int (S::*fn2) () = &S::foo;
+    int (S::*fn3) () = &S::bar;
+    S s;
+    return fn1 () + (s.*fn2) () + (s.*fn3) ();
+  }
+  return 0;
+}
+
+static_assert (bar () == 45);