c++: Diagnose taking address of an immediate member function [PR102753]
authorJakub Jelinek <jakub@redhat.com>
Wed, 27 Oct 2021 07:03:28 +0000 (09:03 +0200)
committerJakub Jelinek <jakub@redhat.com>
Wed, 27 Oct 2021 07:03:28 +0000 (09:03 +0200)
The consteval20.C testcase ICEs, because while we have in cp_build_addr_expr_1
diagnostics for taking address of an immediate function (and as an exception
deal with build_address from immediate invocation), I forgot to diagnose
taking address of a member function which is done in a different place.
I hope (s.*&S::foo) () is not an immediate invocation like
(*&foo) () is not, so this patch just diagnoses taking address of a member
function when not in immediate context.

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.

2021-10-27  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/call.c
gcc/cp/constexpr.c
gcc/cp/cp-tree.h
gcc/cp/typeck.c
gcc/testsuite/g++.dg/cpp23/consteval-if11.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/consteval13.C
gcc/testsuite/g++.dg/cpp2a/consteval20.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/consteval21.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/consteval22.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/consteval23.C [new file with mode: 0644]

index c5601d9..20e66c6 100644 (file)
@@ -9025,6 +9025,20 @@ build_trivial_dtor_call (tree instance, bool no_ptr_deref)
                 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 nargs)
 {
   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 *cand, int flags, tsubst_flags_t complain)
     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 *cand, int flags, tsubst_flags_t complain)
 
   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
index 6f83d30..daa6358 100644 (file)
@@ -7276,6 +7276,10 @@ find_immediate_fndecl (tree *tp, int */*walk_subtrees*/, void */*data*/)
 {
   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;
 }
 
index 5248ecd..f387b50 100644 (file)
@@ -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_initialization_if_possible (tree, tree, bool,
                                                        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);
index ab0f9da..d5f5000 100644 (file)
@@ -6773,9 +6773,19 @@ cp_build_addr_expr_1 (tree arg, bool strict_lvalue, tsubst_flags_t complain)
            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 strict_lvalue, tsubst_flags_t complain)
       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",
diff --git a/gcc/testsuite/g++.dg/cpp23/consteval-if11.C b/gcc/testsuite/g++.dg/cpp23/consteval-if11.C
new file mode 100644 (file)
index 0000000..a22736c
--- /dev/null
@@ -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);
index a2e1750..09ea6b7 100644 (file)
@@ -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);
 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval20.C b/gcc/testsuite/g++.dg/cpp2a/consteval20.C
new file mode 100644 (file)
index 0000000..2c35963
--- /dev/null
@@ -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);
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval21.C b/gcc/testsuite/g++.dg/cpp2a/consteval21.C
new file mode 100644 (file)
index 0000000..06ec705
--- /dev/null
@@ -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);
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval22.C b/gcc/testsuite/g++.dg/cpp2a/consteval22.C
new file mode 100644 (file)
index 0000000..5c36371
--- /dev/null
@@ -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" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval23.C b/gcc/testsuite/g++.dg/cpp2a/consteval23.C
new file mode 100644 (file)
index 0000000..4c7e844
--- /dev/null
@@ -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);