This fixes an issue where lambdas declared in the initializer of a static data member within the class body do not get a mangling scope of that variable; this results in mangled names that do not conform to the ABI spec.
To do this, the patch splits up grokfield for this case specifically, allowing a declaration to be build and used in start_lambda_scope before parsing the initializer, so that record_lambda_scope works correctly. As a drive-by, this also fixes the issue of a static member not being visible within its own initializer. PR c++/107741 gcc/c-family/ChangeLog: * c-opts.cc (c_common_post_options): Bump ABI version. gcc/ChangeLog: * common.opt: Add -fabi-version=20. * doc/invoke.texi: Likewise. gcc/cp/ChangeLog: * cp-tree.h (start_initialized_static_member): Declare. (finish_initialized_static_member): Declare. * decl2.cc (start_initialized_static_member): New function. (finish_initialized_static_member): New function. * lambda.cc (record_lambda_scope): Support falling back to old ABI (maybe with warning). * parser.cc (cp_parser_member_declaration): Build decl early when parsing an initialized static data member. gcc/testsuite/ChangeLog: * g++.dg/abi/macro0.C: Bump ABI version. * g++.dg/abi/mangle74.C: Remove XFAILs. * g++.dg/other/fold1.C: Restore originally raised error. * g++.dg/abi/lambda-ctx2-19.C: New test. * g++.dg/abi/lambda-ctx2-19vs20.C: New test. * g++.dg/abi/lambda-ctx2-20.C: New test. * g++.dg/abi/lambda-ctx2.h: New test. * g++.dg/cpp0x/static-member-init-1.C: New test. Signed-off-by: Nathaniel Shead <nathanielosh...@gmail.com> --- gcc/c-family/c-opts.cc | 2 +- gcc/common.opt | 5 +- gcc/cp/cp-tree.h | 3 + gcc/cp/decl2.cc | 77 +++++++++++++++++++ gcc/cp/lambda.cc | 33 ++++++-- gcc/cp/parser.cc | 74 ++++++++++++------ gcc/doc/invoke.texi | 3 + gcc/testsuite/g++.dg/abi/lambda-ctx2-19.C | 10 +++ gcc/testsuite/g++.dg/abi/lambda-ctx2-19vs20.C | 8 ++ gcc/testsuite/g++.dg/abi/lambda-ctx2-20.C | 10 +++ gcc/testsuite/g++.dg/abi/lambda-ctx2.h | 27 +++++++ gcc/testsuite/g++.dg/abi/macro0.C | 2 +- gcc/testsuite/g++.dg/abi/mangle74.C | 4 +- .../g++.dg/cpp0x/static-member-init-1.C | 5 ++ gcc/testsuite/g++.dg/other/fold1.C | 2 +- 15 files changed, 230 insertions(+), 35 deletions(-) create mode 100644 gcc/testsuite/g++.dg/abi/lambda-ctx2-19.C create mode 100644 gcc/testsuite/g++.dg/abi/lambda-ctx2-19vs20.C create mode 100644 gcc/testsuite/g++.dg/abi/lambda-ctx2-20.C create mode 100644 gcc/testsuite/g++.dg/abi/lambda-ctx2.h create mode 100644 gcc/testsuite/g++.dg/cpp0x/static-member-init-1.C diff --git a/gcc/c-family/c-opts.cc b/gcc/c-family/c-opts.cc index b81d1350b1a..87b231861a6 100644 --- a/gcc/c-family/c-opts.cc +++ b/gcc/c-family/c-opts.cc @@ -1084,7 +1084,7 @@ c_common_post_options (const char **pfilename) /* Change flag_abi_version to be the actual current ABI level, for the benefit of c_cpp_builtins, and to make comparison simpler. */ - const int latest_abi_version = 19; + const int latest_abi_version = 20; /* Generate compatibility aliases for ABI v13 (8.2) by default. */ const int abi_compat_default = 13; diff --git a/gcc/common.opt b/gcc/common.opt index e2ac99df1d0..4c2560a0632 100644 --- a/gcc/common.opt +++ b/gcc/common.opt @@ -1034,10 +1034,13 @@ Driver Undocumented ; Default in G++ 13. ; ; 19: Emits ABI tags if needed in structured binding mangled names. -; Ignores cv-quals on [[no_unique_object]] members. +; Ignores cv-quals on [[no_unique_address]] members. ; Mangles constraints on function templates. ; Default in G++ 14. ; +; 20: Fix mangling of lambdas in static data member initializers. +; Default in G++ 15. +; ; Additional positive integers will be assigned as new versions of ; the ABI become the default version of the ABI. fabi-version= diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 1d741ecedc3..f77a325bdd0 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -7224,6 +7224,9 @@ extern tree grokfield (const cp_declarator *, cp_decl_specifier_seq *, tree, bool, tree, tree); extern tree grokbitfield (const cp_declarator *, cp_decl_specifier_seq *, tree, tree, tree); +extern tree start_initialized_static_member (const cp_declarator *, + cp_decl_specifier_seq *, tree); +extern void finish_initialized_static_member (tree, tree, tree); extern tree splice_template_attributes (tree *, tree); extern bool any_dependent_type_attributes_p (tree); extern tree cp_reconstruct_complex_type (tree, tree); diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc index 869701bceee..5e534935dfd 100644 --- a/gcc/cp/decl2.cc +++ b/gcc/cp/decl2.cc @@ -1252,6 +1252,83 @@ grokfield (const cp_declarator *declarator, return NULL_TREE; } +/* Like grokfield, but just for the initial grok of an initialized static + member. Used to be able to push the new decl before parsing the + initialiser. */ + +tree +start_initialized_static_member (const cp_declarator *declarator, + cp_decl_specifier_seq *declspecs, + tree attrlist) +{ + tree value = grokdeclarator (declarator, declspecs, FIELD, SD_INITIALIZED, + &attrlist); + if (!value || error_operand_p (value)) + return error_mark_node; + if (TREE_CODE (value) == TYPE_DECL) + { + error_at (declarator->init_loc, + "typedef %qD is initialized (use %qs instead)", + value, "decltype"); + return error_mark_node; + } + else if (TREE_CODE (value) == FUNCTION_DECL) + { + if (TREE_CODE (TREE_TYPE (value)) == METHOD_TYPE) + error_at (declarator->init_loc, + "invalid initializer for member function %qD", + value); + else if (TREE_CODE (TREE_TYPE (value)) == FUNCTION_TYPE) + error_at (declarator->init_loc, + "initializer specified for static member function %qD", + value); + else + gcc_unreachable (); + return error_mark_node; + } + else if (TREE_CODE (value) == FIELD_DECL) + { + /* NSDM marked 'static', grokdeclarator has already errored. */ + gcc_checking_assert (seen_error ()); + return error_mark_node; + } + gcc_checking_assert (VAR_P (value)); + + DECL_CONTEXT (value) = current_class_type; + if (processing_template_decl) + { + value = push_template_decl (value); + if (error_operand_p (value)) + return error_mark_node; + } + + if (attrlist) + cplus_decl_attributes (&value, attrlist, 0); + + finish_member_declaration (value); + DECL_INITIALIZED_IN_CLASS_P (value) = true; + + return value; +} + +/* Finish a declaration prepared with start_initialized_static_member. */ + +void +finish_initialized_static_member (tree decl, tree init, tree asmspec) +{ + if (decl == error_mark_node) + return; + gcc_checking_assert (VAR_P (decl)); + + int flags; + if (init && DIRECT_LIST_INIT_P (init)) + flags = LOOKUP_NORMAL; + else + flags = LOOKUP_IMPLICIT; + finish_static_data_member_decl (decl, init, /*init_const_expr_p=*/true, + asmspec, flags); +} + /* Like `grokfield', but for bitfields. WIDTH is the width of the bitfield, a constant expression. The other parameters are as for grokfield. */ diff --git a/gcc/cp/lambda.cc b/gcc/cp/lambda.cc index be8a0fe01cb..3166f5eab2c 100644 --- a/gcc/cp/lambda.cc +++ b/gcc/cp/lambda.cc @@ -32,6 +32,7 @@ along with GCC; see the file COPYING3. If not see #include "gimplify.h" #include "target.h" #include "decl.h" +#include "flags.h" /* Constructor for a lambda expression. */ @@ -1540,13 +1541,35 @@ finish_lambda_scope (void) void record_lambda_scope (tree lambda) { - LAMBDA_EXPR_EXTRA_SCOPE (lambda) = lambda_scope.scope; - if (lambda_scope.scope) + tree closure = LAMBDA_EXPR_CLOSURE (lambda); + gcc_checking_assert (closure); + + /* Before ABI v20, lambdas in static data member initializers did not + get a dedicated lambda scope. */ + tree scope = lambda_scope.scope; + if (scope + && VAR_P (scope) + && DECL_CLASS_SCOPE_P (scope) + && DECL_INITIALIZED_IN_CLASS_P (scope)) { - tree closure = LAMBDA_EXPR_CLOSURE (lambda); - gcc_checking_assert (closure); - maybe_key_decl (lambda_scope.scope, TYPE_NAME (closure)); + if (!abi_version_at_least (20)) + scope = NULL_TREE; + if (warn_abi && abi_version_crosses (20) && !processing_template_decl) + { + if (abi_version_at_least (20)) + warning_at (location_of (closure), OPT_Wabi, + "the mangled name of %qT changed in " + "%<-fabi-version=20%> (GCC 15.1)", closure); + else + warning_at (location_of (closure), OPT_Wabi, + "the mangled name of %qT changes in " + "%<-fabi-version=20%> (GCC 15.1)", closure); + } } + + LAMBDA_EXPR_EXTRA_SCOPE (lambda) = scope; + if (scope) + maybe_key_decl (scope, TYPE_NAME (closure)); } // Compare lambda template heads TMPL_A and TMPL_B, used for both diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc index f548dc31c2b..dd986444449 100644 --- a/gcc/cp/parser.cc +++ b/gcc/cp/parser.cc @@ -28737,6 +28737,7 @@ cp_parser_member_declaration (cp_parser* parser) tree first_attribute; tree initializer; bool named_bitfld = false; + bool decl_was_initialized_p = false; /* Peek at the next token. */ token = cp_lexer_peek_token (parser->lexer); @@ -28906,9 +28907,8 @@ cp_parser_member_declaration (cp_parser* parser) pure-specifier. It is not correct to parse the initializer before registering the member declaration since the member declaration should be in scope while - its initializer is processed. However, the rest of the - front end does not yet provide an interface that allows - us to handle this correctly. */ + its initializer is processed. As such we might build + decl pre-emptively. */ if (cp_lexer_next_token_is (parser->lexer, CPP_EQ)) { /* In [class.mem]: @@ -28935,18 +28935,31 @@ cp_parser_member_declaration (cp_parser* parser) initializer = cp_parser_pure_specifier (parser); else if (decl_specifiers.storage_class != sc_static) initializer = cp_parser_save_nsdmi (parser); - else if (cxx_dialect >= cxx11) + else { - /* Don't require a constant rvalue in C++11, since we - might want a reference constant. We'll enforce - constancy later. */ - cp_lexer_consume_token (parser->lexer); - /* Parse the initializer. */ - initializer = cp_parser_initializer_clause (parser); + decl = start_initialized_static_member (declarator, + &decl_specifiers, + attributes); + start_lambda_scope (decl); + + if (cxx_dialect >= cxx11) + { + /* Don't require a constant rvalue in C++11, since we + might want a reference constant. We'll enforce + constancy later. */ + cp_lexer_consume_token (parser->lexer); + /* Parse the initializer. */ + initializer = cp_parser_initializer_clause (parser); + } + else + /* Parse the initializer. */ + initializer = cp_parser_constant_initializer (parser); + + finish_lambda_scope (); + finish_initialized_static_member (decl, initializer, + asm_specification); + decl_was_initialized_p = true; } - else - /* Parse the initializer. */ - initializer = cp_parser_constant_initializer (parser); } else if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_BRACE) && !function_declarator_p (declarator)) @@ -28956,7 +28969,17 @@ cp_parser_member_declaration (cp_parser* parser) if (decl_specifiers.storage_class != sc_static) initializer = cp_parser_save_nsdmi (parser); else - initializer = cp_parser_initializer (parser); + { + decl = start_initialized_static_member (declarator, + &decl_specifiers, + attributes); + start_lambda_scope (decl); + initializer = cp_parser_initializer (parser); + finish_lambda_scope (); + finish_initialized_static_member (decl, initializer, + asm_specification); + decl_was_initialized_p = true; + } } /* Detect invalid bit-field cases such as @@ -29031,16 +29054,19 @@ cp_parser_member_declaration (cp_parser* parser) declarator->id_loc = token->location; /* Create the declaration. */ - decl = grokfield (declarator, &decl_specifiers, - initializer, /*init_const_expr_p=*/true, - asm_specification, attributes); - - if (parser->fully_implicit_function_template_p) + if (!decl_was_initialized_p) { - if (friend_p) - finish_fully_implicit_template (parser, 0); - else - decl = finish_fully_implicit_template (parser, decl); + decl = grokfield (declarator, &decl_specifiers, + initializer, /*init_const_expr_p=*/true, + asm_specification, attributes); + + if (parser->fully_implicit_function_template_p) + { + if (friend_p) + finish_fully_implicit_template (parser, 0); + else + decl = finish_fully_implicit_template (parser, decl); + } } } @@ -29094,7 +29120,7 @@ cp_parser_member_declaration (cp_parser* parser) assume_semicolon = true; } - if (decl) + if (decl && !decl_was_initialized_p) { /* Add DECL to the list of members. */ if (!friend_p diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 0a7a81b2067..e2e7f29f9ce 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -3018,6 +3018,9 @@ that have additional context. Version 19, which first appeard in G++ 14, fixes manglings of structured bindings to include ABI tags. +Version 20, which first appeared in G++ 15, fixes manglings of lambdas +in static data member initializers. + See also @option{-Wabi}. @opindex fabi-compat-version diff --git a/gcc/testsuite/g++.dg/abi/lambda-ctx2-19.C b/gcc/testsuite/g++.dg/abi/lambda-ctx2-19.C new file mode 100644 index 00000000000..35d394da8c5 --- /dev/null +++ b/gcc/testsuite/g++.dg/abi/lambda-ctx2-19.C @@ -0,0 +1,10 @@ +// { dg-do compile { target c++17 } } +// { dg-options -fabi-version=19 } + +#include "lambda-ctx2.h" + +// A and B demangle incorrectly due to static data members missing a lambda scope +// { dg-final { scan-assembler {_ZNK1AUlvE_clEv:} } } +// { dg-final { scan-assembler {_ZNK1BIiEUlvE2_clEv:} } } +// { dg-final { scan-assembler {_ZNK1BIiEUlvE3_clEv:} } } +// { dg-final { scan-assembler {_ZNK1CIiE1xMUlvE_clEv:} } } diff --git a/gcc/testsuite/g++.dg/abi/lambda-ctx2-19vs20.C b/gcc/testsuite/g++.dg/abi/lambda-ctx2-19vs20.C new file mode 100644 index 00000000000..d4662291e0c --- /dev/null +++ b/gcc/testsuite/g++.dg/abi/lambda-ctx2-19vs20.C @@ -0,0 +1,8 @@ +// { dg-do compile { target c++17 } } +// { dg-options "-fabi-version=20 -Wabi=19" } + +#include "lambda-ctx2.h" + +// { dg-regexp {[^\n]*lambda-ctx2.h:[:0-9]* warning: the mangled name of .A::<lambda>.[^\n]*\n} } +// { dg-regexp {[^\n]*lambda-ctx2.h:[:0-9]* warning: the mangled name of .B<int>::<lambda>.[^\n]*\n} } +// { dg-regexp {[^\n]*lambda-ctx2.h:[:0-9]* warning: the mangled name of .B<int>::<lambda>.[^\n]*\n} } diff --git a/gcc/testsuite/g++.dg/abi/lambda-ctx2-20.C b/gcc/testsuite/g++.dg/abi/lambda-ctx2-20.C new file mode 100644 index 00000000000..764f6061876 --- /dev/null +++ b/gcc/testsuite/g++.dg/abi/lambda-ctx2-20.C @@ -0,0 +1,10 @@ +// { dg-do compile { target c++17 } } +// { dg-options -fabi-version=20 } + +#include "lambda-ctx2.h" + +// These correctly associate lambdas in A::x and B::x. +// { dg-final { scan-assembler {_ZNK1A1xMUlvE_clEv:} } } +// { dg-final { scan-assembler {_ZNK1BIiE1xMUlvE_clEv:} } } +// { dg-final { scan-assembler {_ZNK1BIiE1xMUlvE0_clEv:} } } +// { dg-final { scan-assembler {_ZNK1CIiE1xMUlvE_clEv:} } } diff --git a/gcc/testsuite/g++.dg/abi/lambda-ctx2.h b/gcc/testsuite/g++.dg/abi/lambda-ctx2.h new file mode 100644 index 00000000000..e359254db90 --- /dev/null +++ b/gcc/testsuite/g++.dg/abi/lambda-ctx2.h @@ -0,0 +1,27 @@ +// PR c++/107741 + +void side_effect(); + +struct A { + static constexpr auto x = []{ return 1; }; +}; + +template <typename> +struct B { + static inline auto x = (side_effect(), []{ return 2; }(), []{ return 3; }()); +}; + +template <typename> +struct C { + static int x; +}; + +template <typename T> +int C<T>::x = (side_effect(), []{ return 4; }()); + +template int C<int>::x; + +int f() { + A::x(); + return B<int>::x; +} diff --git a/gcc/testsuite/g++.dg/abi/macro0.C b/gcc/testsuite/g++.dg/abi/macro0.C index 183184e0f0a..f6a57c11ae7 100644 --- a/gcc/testsuite/g++.dg/abi/macro0.C +++ b/gcc/testsuite/g++.dg/abi/macro0.C @@ -1,6 +1,6 @@ // This testcase will need to be kept in sync with c_common_post_options. // { dg-options "-fabi-version=0" } -#if __GXX_ABI_VERSION != 1019 +#if __GXX_ABI_VERSION != 1020 #error "Incorrect value of __GXX_ABI_VERSION" #endif diff --git a/gcc/testsuite/g++.dg/abi/mangle74.C b/gcc/testsuite/g++.dg/abi/mangle74.C index 7451ce81495..14fe9b9fbf2 100644 --- a/gcc/testsuite/g++.dg/abi/mangle74.C +++ b/gcc/testsuite/g++.dg/abi/mangle74.C @@ -26,5 +26,5 @@ int thorn () } // { dg-final { scan-assembler "_ZNK3varMUlvE_clEv:" } } -// { dg-final { scan-assembler "_ZNK3Foo3barMUlvE_clEv:" { xfail *-*-* } } } -// { dg-final { scan-assembler-not "_ZNK3FooUlvE_clEv:" { xfail *-*-* } } } +// { dg-final { scan-assembler "_ZNK3Foo3barMUlvE_clEv:" } } +// { dg-final { scan-assembler-not "_ZNK3FooUlvE_clEv:" } } diff --git a/gcc/testsuite/g++.dg/cpp0x/static-member-init-1.C b/gcc/testsuite/g++.dg/cpp0x/static-member-init-1.C new file mode 100644 index 00000000000..e64e77faade --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/static-member-init-1.C @@ -0,0 +1,5 @@ +// { dg-do compile { target c++11 } } + +struct S { + static constexpr const void* x = &x; +}; diff --git a/gcc/testsuite/g++.dg/other/fold1.C b/gcc/testsuite/g++.dg/other/fold1.C index 8d8df3de68e..d2fc518c220 100644 --- a/gcc/testsuite/g++.dg/other/fold1.C +++ b/gcc/testsuite/g++.dg/other/fold1.C @@ -3,6 +3,6 @@ struct A { - static const int i = i; // { dg-error "not declared" } + static const int i = j; // { dg-error "not declared" } int x[i]; // { dg-error "11:size of array .x. is not an integral constant-expression" } }; -- 2.47.0