From: Jakub Jelinek Date: Sat, 31 Jan 2026 09:19:26 +0000 (+0100) Subject: c++: Handle argument merging push_local_extern_decl_alias [PR123825] X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d6386e0984556173339c06b2b7cfe2dd55eeb07b;p=thirdparty%2Fgcc.git c++: Handle argument merging push_local_extern_decl_alias [PR123825] 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 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. --- diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 006ce23960c..59ab4e40430 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -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); diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc index c24bbc889ed..6dabf349bef 100644 --- a/gcc/cp/decl.cc +++ b/gcc/cp/decl.cc @@ -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), + "% 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), - "% 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; diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc index 460c9d30201..9a732e14979 100644 --- a/gcc/cp/name-lookup.cc +++ b/gcc/cp/name-lookup.cc @@ -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 index 00000000000..cb53b32b8ea --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/attr-indeterminate5.C @@ -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 index 00000000000..555a9473ee3 --- /dev/null +++ b/gcc/testsuite/g++.dg/reflect/has_identifier3.C @@ -0,0 +1,23 @@ +// PR c++/123825 +// { dg-do compile { target c++26 } } +// { dg-additional-options "-freflection" } +// Test std::meta::has_identifier. + +#include + +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 index 00000000000..5383de07dd2 --- /dev/null +++ b/gcc/testsuite/g++.dg/reflect/identifier_of3.C @@ -0,0 +1,73 @@ +// PR c++/123825 +// { dg-do compile { target c++26 } } +// { dg-additional-options "-freflection" } +// Test std::meta::identifier_of. + +#include + +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")); +}