[v3,1/6] c++: Fix mangling of lambdas in static data member initializers [PR107741]

Message ID 677bcabd.a70a0220.30746a.51ad@mx.google.com
State New
Headers
Series c++: Add some missing LAMBDA_EXPR_EXTRA_SCOPEs |

Commit Message

Nathaniel Shead Jan. 6, 2025, 12:21 p.m. UTC
  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 <nathanieloshead@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
  

Comments

Jason Merrill Jan. 17, 2025, 12:10 a.m. UTC | #1
On 1/6/25 7:21 AM, Nathaniel Shead wrote:
> 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.

OK.

> 	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 <nathanieloshead@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" }
>   };
  

Patch

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" }
 };