]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++: Handle argument merging push_local_extern_decl_alias [PR123825]
authorJakub Jelinek <jakub@redhat.com>
Sat, 31 Jan 2026 09:19:26 +0000 (10:19 +0100)
committerJakub Jelinek <jakub@gcc.gnu.org>
Sat, 31 Jan 2026 09:19:26 +0000 (10:19 +0100)
The first testcase comes directly from
https://eel.is/c++draft/meta.reflection#names-1.8.2
and shows that we don't handle for -freflection the function
argument name difference handling isn't performed for local externs,
which doesn't go the duplicate_decls route but directly remembers
the alias.
The following patch handles outlines the DECL_ARGUMENTS handling
from duplicate_decls and uses it in push_local_extern_decl_alias
(with some minor differences and for now not propagating attributes
back for the push_local_extern_decl_alias case unless -freflection).

ALso, I found that the addition of a new alias created completely broken
DECL_ARGUMENTS (copied at most the first PARM_DECL, never more than that).
That is because copy_decl clears DECL_CHAIN, so the loop always stopped
after the first iteration.

2026-01-31  Jakub Jelinek  <jakub@redhat.com>

PR c++/123825
* cp-tree.h (merge_decl_arguments): Declare.
* decl.cc (duplicate_decls): Outline DECL_ARGUMENTS handling
into ...
(merge_decl_arguments): ... new function.
* name-lookup.cc (push_local_extern_decl_alias): Call
merge_decl_arguments.  Don't copy just the first PARM_DECL when
creating a new alias FUNCTION_DECL.

* g++.dg/reflect/has_identifier3.C: New test.
* g++.dg/reflect/identifier_of3.C: New test.
* g++.dg/cpp26/attr-indeterminate5.C: New test.

gcc/cp/cp-tree.h
gcc/cp/decl.cc
gcc/cp/name-lookup.cc
gcc/testsuite/g++.dg/cpp26/attr-indeterminate5.C [new file with mode: 0644]
gcc/testsuite/g++.dg/reflect/has_identifier3.C [new file with mode: 0644]
gcc/testsuite/g++.dg/reflect/identifier_of3.C [new file with mode: 0644]

index 006ce23960c78ed156ec411a525d3479b45deb11..59ab4e40430d4521cd2b62407aa3bef0ef7aa218 100644 (file)
@@ -7456,6 +7456,7 @@ extern int decls_match                            (tree, tree, bool = true);
 extern bool maybe_version_functions            (tree, tree);
 extern bool validate_constexpr_redeclaration   (tree, tree);
 extern bool merge_default_template_args                (tree, tree, bool);
+extern void merge_decl_arguments               (tree, tree, bool, bool, bool);
 extern tree duplicate_decls                    (tree, tree,
                                                 bool hiding = false,
                                                 bool was_hidden = false);
index c24bbc889ed71bd53d29a9f0ecb9e5208a763e32..6dabf349befb725b577265aae846f3cbd15458b1 100644 (file)
@@ -1781,6 +1781,97 @@ merge_default_template_args (tree new_parms, tree old_parms, bool class_p)
   return true;
 }
 
+/* Helper function for duplicate_decls and push_local_extern_decl_alias.
+   Merge parameter attributes and names between NEWDECL and OLDDECL.
+   NEW_DEFINES_FUNCTION and TYPES_MATCH argument like variables in
+   duplicate_decls, EXTERN_ALIAS false for duplicate_decls and true for
+   push_local_extern_decl_alias.  */
+
+void
+merge_decl_arguments (tree newdecl, tree olddecl, bool new_defines_function,
+                     bool types_match, bool extern_alias)
+{
+  tree oldarg, newarg;
+  for (oldarg = DECL_ARGUMENTS (olddecl), newarg = DECL_ARGUMENTS (newdecl);
+       oldarg && newarg;
+       oldarg = DECL_CHAIN (oldarg), newarg = DECL_CHAIN (newarg))
+    {
+      DECL_ATTRIBUTES (newarg)
+       = (*targetm.merge_decl_attributes) (oldarg, newarg);
+      if (lookup_attribute (NULL, "indeterminate", DECL_ATTRIBUTES (newarg))
+         && !lookup_attribute (NULL, "indeterminate",
+                               DECL_ATTRIBUTES (oldarg)))
+       {
+         auto_diagnostic_group d;
+         error_at (DECL_SOURCE_LOCATION (newarg),
+                   "%<indeterminate%> attribute not specified for parameter "
+                   "%qD on the first declaration of its function", newarg);
+         inform (DECL_SOURCE_LOCATION (oldarg), "earlier declaration");
+       }
+      /* ??? Should attributes propagate out from a block extern?  If so,
+        we should do that for the function itself, not just parameters.  */
+      if (!extern_alias || flag_reflection)
+       DECL_ATTRIBUTES (oldarg) = DECL_ATTRIBUTES (newarg);
+      /* Merge names for std::meta::has_identifier and
+        std::meta::{,u8}identifier_of purposes.  If they are different and
+        both oldarg and newarg are named, add flag to force that
+        std::meta::has_identifier returns false.  If one is named and one is
+        unnamed, if neither is a olddecl nor newdecl is definition, propagate
+        DECL_NAME to both.  Otherwise stash the old name into "old parm name"
+        artificial attribute.  */
+      if (flag_reflection && DECL_NAME (oldarg) != DECL_NAME (newarg))
+       {
+         if (DECL_NAME (oldarg) && DECL_NAME (newarg))
+           {
+             /* Different names.  */
+             MULTIPLE_NAMES_PARM_P (oldarg) = 1;
+             MULTIPLE_NAMES_PARM_P (newarg) = 1;
+           }
+         else if (!new_defines_function
+                  && types_match
+                  && DECL_INITIAL (olddecl) == NULL_TREE)
+           {
+             /* For 2 non-definitions with matching types, one is named and
+                one unnamed, propagate name to both.  */
+             if (DECL_NAME (oldarg))
+               DECL_NAME (newarg) = DECL_NAME (oldarg);
+             else
+               DECL_NAME (oldarg) = DECL_NAME (newarg);
+           }
+         /* Depending on which PARM_DECL we'll keep, look at the other
+            PARM_DECL's name.  */
+         else if (tree name = ((new_defines_function || !types_match)
+                               ? DECL_NAME (oldarg) : DECL_NAME (newarg)))
+           {
+             tree opn = lookup_attribute ("old parm name",
+                                          DECL_ATTRIBUTES (oldarg));
+             if (opn)
+               {
+                 if (TREE_VALUE (TREE_VALUE (opn)) == name)
+                   /* Name already in "old parm name" attribute.  */;
+                 else
+                   {
+                     /* Different names.  */
+                     MULTIPLE_NAMES_PARM_P (oldarg) = 1;
+                     MULTIPLE_NAMES_PARM_P (newarg) = 1;
+                   }
+               }
+             else
+               {
+                 /* Save name into attribute.  */
+                 DECL_ATTRIBUTES (newarg)
+                   = tree_cons (get_identifier ("old parm name"),
+                                tree_cons (NULL_TREE, name, NULL_TREE),
+                                DECL_ATTRIBUTES (newarg));
+                 DECL_ATTRIBUTES (oldarg) = DECL_ATTRIBUTES (newarg);
+               }
+           }
+         else if (extern_alias)
+           DECL_NAME (newarg) = DECL_NAME (oldarg);
+       }
+    }
+}
+
 /* If NEWDECL is a redeclaration of OLDDECL, merge the declarations.
    If the redeclaration is invalid, a diagnostic is issued, and the
    error_mark_node is returned.  Otherwise, OLDDECL is returned.
@@ -3038,88 +3129,8 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden)
 
   if (TREE_CODE (newdecl) == FUNCTION_DECL)
     {
-      tree parm;
-
-      /* Merge parameter attributes. */
-      tree oldarg, newarg;
-      for (oldarg = DECL_ARGUMENTS (olddecl),
-          newarg = DECL_ARGUMENTS (newdecl);
-          oldarg && newarg;
-          oldarg = DECL_CHAIN (oldarg), newarg = DECL_CHAIN (newarg))
-       {
-          DECL_ATTRIBUTES (newarg)
-           = (*targetm.merge_decl_attributes) (oldarg, newarg);
-         if (lookup_attribute (NULL, "indeterminate",
-                               DECL_ATTRIBUTES (newarg))
-             && !lookup_attribute (NULL, "indeterminate",
-                                   DECL_ATTRIBUTES (oldarg)))
-           {
-             auto_diagnostic_group d;
-             error_at (DECL_SOURCE_LOCATION (newarg),
-                       "%<indeterminate%> attribute not specified "
-                       "for parameter %qD on the first declaration of "
-                       "its function", newarg);
-             inform (DECL_SOURCE_LOCATION (oldarg),
-                     "earlier declaration");
-           }
-          DECL_ATTRIBUTES (oldarg) = DECL_ATTRIBUTES (newarg);
-         /* Merge names for std::meta::has_identifier and
-            std::meta::{,u8}identifier_of purposes.  If they are different
-            and both oldarg and newarg are named, add flag to force that
-            std::meta::has_identifier returns false.  If one is named and
-            one is unnamed, if neither is a olddecl nor newdecl is definition,
-            propagate DECL_NAME to both.  Otherwise stash the old name into
-            "old parm name" artificial attribute.  */
-         if (flag_reflection && DECL_NAME (oldarg) != DECL_NAME (newarg))
-           {
-             if (DECL_NAME (oldarg) && DECL_NAME (newarg))
-               {
-                 /* Different names.  */
-                 MULTIPLE_NAMES_PARM_P (oldarg) = 1;
-                 MULTIPLE_NAMES_PARM_P (newarg) = 1;
-               }
-             else if (!new_defines_function
-                      && types_match
-                      && DECL_INITIAL (olddecl) == NULL_TREE)
-               {
-                 /* For 2 non-definitions with matching types,
-                    one is named and one unnamed, propagate name
-                    to both.  */
-                 if (DECL_NAME (oldarg))
-                   DECL_NAME (newarg) = DECL_NAME (oldarg);
-                 else
-                   DECL_NAME (oldarg) = DECL_NAME (newarg);
-               }
-             /* Depending on which PARM_DECL we'll keep, look at the other
-                PARM_DECL's name.  */
-             else if (tree name = ((new_defines_function || !types_match)
-                                   ? DECL_NAME (oldarg) : DECL_NAME (newarg)))
-               {
-                 tree opn = lookup_attribute ("old parm name",
-                                              DECL_ATTRIBUTES (oldarg));
-                 if (opn)
-                   {
-                     if (TREE_VALUE (TREE_VALUE (opn)) == name)
-                       /* Name already in "old parm name" attribute.  */;
-                     else
-                       {
-                         /* Different names.  */
-                         MULTIPLE_NAMES_PARM_P (oldarg) = 1;
-                         MULTIPLE_NAMES_PARM_P (newarg) = 1;
-                       }
-                   }
-                 else
-                   {
-                     /* Save name into attribute.  */
-                     DECL_ATTRIBUTES (newarg)
-                       = tree_cons (get_identifier ("old parm name"),
-                                    tree_cons (NULL_TREE, name, NULL_TREE),
-                                    DECL_ATTRIBUTES (newarg));
-                     DECL_ATTRIBUTES (oldarg) = DECL_ATTRIBUTES (newarg);
-                   }
-               }
-           }
-       }
+      merge_decl_arguments (newdecl, olddecl, new_defines_function,
+                           types_match, false);
 
       if (DECL_TEMPLATE_INSTANTIATION (olddecl)
          && !DECL_TEMPLATE_INSTANTIATION (newdecl))
@@ -3198,7 +3209,7 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden)
       DECL_ABSTRACT_P (newdecl) = DECL_ABSTRACT_P (olddecl);
 
       /* Update newdecl's parms to point at olddecl.  */
-      for (parm = DECL_ARGUMENTS (newdecl); parm;
+      for (tree parm = DECL_ARGUMENTS (newdecl); parm;
           parm = DECL_CHAIN (parm))
        DECL_CONTEXT (parm) = olddecl;
 
index 460c9d30201fc66ed282ba35b49c7acf48375b5e..9a732e14979a7321bf8f8aa864b7057e0f8bc2cb 100644 (file)
@@ -3736,6 +3736,8 @@ push_local_extern_decl_alias (tree decl)
              alias = *iter;
              if (!validate_constexpr_redeclaration (alias, decl))
                return;
+             if (TREE_CODE (decl) == FUNCTION_DECL)
+               merge_decl_arguments (decl, alias, false, true, true);
              break;
            }
 
@@ -3749,7 +3751,9 @@ push_local_extern_decl_alias (tree decl)
              for (tree *chain = &DECL_ARGUMENTS (alias);
                   *chain; chain = &DECL_CHAIN (*chain))
                {
+                 tree next = DECL_CHAIN (*chain);
                  *chain = copy_decl (*chain);
+                 DECL_CHAIN (*chain) = next;
                  DECL_CONTEXT (*chain) = alias;
                }
 
diff --git a/gcc/testsuite/g++.dg/cpp26/attr-indeterminate5.C b/gcc/testsuite/g++.dg/cpp26/attr-indeterminate5.C
new file mode 100644 (file)
index 0000000..cb53b32
--- /dev/null
@@ -0,0 +1,23 @@
+// C++ 26 P2795R5 - Erroneous behaviour for uninitialized reads
+// { dg-do compile { target c++11 } }
+// { dg-skip-if "" { c++26 } { "-ftrivial-auto-var-init=*" } { "" } }
+
+struct S { S (); S (const S &); ~S (); int s; };
+void foo (S s);                                                        // { dg-message "earlier declaration" }
+void bar (S s [[indeterminate]]);
+void baz (S s [[indeterminate]]);
+
+void
+fred ()
+{
+  void foo (S t [[indeterminate]]);                            // { dg-error "'indeterminate' attribute not specified for parameter 't' on the first declaration of its function" }
+  void bar (S t [[indeterminate]]);
+  void baz (S t);
+  void qux (S t);                                              // { dg-message "earlier declaration" }
+  void corge (S t [[indeterminate]]);
+  void garply (S t [[indeterminate]]);
+}
+
+void qux (S u [[indeterminate]]);                              // { dg-error "'indeterminate' attribute not specified for parameter 'u' on the first declaration of its function" }
+void corge (S u [[indeterminate]]);
+void garply (S u);
diff --git a/gcc/testsuite/g++.dg/reflect/has_identifier3.C b/gcc/testsuite/g++.dg/reflect/has_identifier3.C
new file mode 100644 (file)
index 0000000..555a947
--- /dev/null
@@ -0,0 +1,23 @@
+// PR c++/123825
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+// Test std::meta::has_identifier.
+
+#include <meta>
+
+void fun (int);
+constexpr std::meta::info r = parameters_of (^^fun)[0];
+static_assert (!has_identifier (r));
+
+void fun (int x);
+static_assert (has_identifier (r));
+
+void fun (int x);
+static_assert (has_identifier (r));
+
+void
+poison ()
+{
+  void fun (int y);
+}
+static_assert (!has_identifier (r));
diff --git a/gcc/testsuite/g++.dg/reflect/identifier_of3.C b/gcc/testsuite/g++.dg/reflect/identifier_of3.C
new file mode 100644 (file)
index 0000000..5383de0
--- /dev/null
@@ -0,0 +1,73 @@
+// PR c++/123825
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+// Test std::meta::identifier_of.
+
+#include <meta>
+
+void foo (int, int x, int y, int z);
+
+int
+bar (int, int x, int y, int z, int)
+{
+  return x + y + z;
+}
+
+constexpr auto foo1 = parameters_of (^^foo)[0];
+constexpr auto foo2 = parameters_of (^^foo)[1];
+constexpr auto foo3 = parameters_of (^^foo)[2];
+constexpr auto foo4 = parameters_of (^^foo)[3];
+constexpr auto bar1 = parameters_of (^^bar)[0];
+constexpr auto bar2 = parameters_of (^^bar)[1];
+constexpr auto bar3 = parameters_of (^^bar)[2];
+constexpr auto bar4 = parameters_of (^^bar)[3];
+constexpr auto bar5 = parameters_of (^^bar)[4];
+static_assert (!has_identifier (foo1));
+static_assert (identifier_of (foo2) == std::string_view ("x"));
+static_assert (identifier_of (foo3) == std::string_view ("y"));
+static_assert (identifier_of (foo4) == std::string_view ("z"));
+static_assert (!has_identifier (bar1));
+static_assert (identifier_of (bar2) == std::string_view ("x"));
+static_assert (identifier_of (bar3) == std::string_view ("y"));
+static_assert (identifier_of (bar4) == std::string_view ("z"));
+static_assert (!has_identifier (bar5));
+
+void
+baz ()
+{
+  void foo (int w, int, int v, int z);
+  int bar (int, int, int v, int z, int u);
+  void qux (int, int x, int y, int z);
+  constexpr auto qux1 = parameters_of (^^qux)[0];
+  constexpr auto qux2 = parameters_of (^^qux)[1];
+  constexpr auto qux3 = parameters_of (^^qux)[2];
+  constexpr auto qux4 = parameters_of (^^qux)[3];
+  static_assert (!has_identifier (qux1));
+  static_assert (identifier_of (qux2) == std::string_view ("x"));
+  static_assert (identifier_of (qux3) == std::string_view ("y"));
+  static_assert (identifier_of (qux4) == std::string_view ("z"));
+}
+
+static_assert (identifier_of (foo1) == std::string_view ("w"));
+static_assert (identifier_of (foo2) == std::string_view ("x"));
+static_assert (!has_identifier (foo3));
+static_assert (identifier_of (foo4) == std::string_view ("z"));
+static_assert (!has_identifier (bar1));
+static_assert (identifier_of (bar2) == std::string_view ("x"));
+static_assert (!has_identifier (bar3));
+static_assert (identifier_of (bar4) == std::string_view ("z"));
+static_assert (identifier_of (bar5) == std::string_view ("u"));
+
+void
+fred ()
+{
+  void qux (int w, int, int v, int z);
+  constexpr auto qux1 = parameters_of (^^qux)[0];
+  constexpr auto qux2 = parameters_of (^^qux)[1];
+  constexpr auto qux3 = parameters_of (^^qux)[2];
+  constexpr auto qux4 = parameters_of (^^qux)[3];
+  static_assert (identifier_of (qux1) == std::string_view ("w"));
+  static_assert (identifier_of (qux2) == std::string_view ("x"));
+  static_assert (!has_identifier (qux3));
+  static_assert (identifier_of (qux4) == std::string_view ("z"));
+}