c++: 'this' capture clobbered during recursive inst [PR116756]

Message ID 20250110192019.1276622-1-ppalka@redhat.com
State New
Headers
Series c++: 'this' capture clobbered during recursive inst [PR116756] |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gcc_check--master-arm success Test passed
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gcc_check--master-aarch64 success Test passed

Commit Message

Patrick Palka Jan. 10, 2025, 7:20 p.m. UTC
  Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look
OK for trunk?

The documentation for LAMBDA_EXPR_THIS_CAPTURE seems outdated because
it says the field is only used at parse time, but apparently it's also
used at instantiation time.

Non-'this' captures don't seem to be affected, because there is no
corresponding LAMBDA_EXPR field that gets clobbered, and instead their
uses get resolved via the local specialization mechanism which is
recursion aware.

The bug also disappears if we explicitly use this in the openSeries call,
i.e. this->openSeries(...), because that sidesteps the use of
maybe_resolve_dummy / LAMBDA_EXPR_THIS_CAPTURE for resolving the
implicit object, and instead gets resolved via the local mechanism
specialization.

Maybe this suggests that there's a better way to fix this, but I'm not
sure...

-- >8 --

Here during instantiation of lambda::op() [with I = 0] we substitute
into the call self(self, cst<1>{}) which requires recursive instantiation
of the same lambda::op() [with I = 1] (which isn't deferred due to
lambda's deduced return type.  During this recursive instantiation, the
DECL_EXPR case of tsubst_stmt clobbers LAMBDA_EXPR_THIS_CAPTURE to point
to the inner lambda::op()'s capture proxy instead of the outer
lambda::op(), and the original value is never restored.

So later during substitution into the openSeries call in the outer
lambda::op() maybe_resolve_dummy uses the 'this' proxy belonging to the
inner lambda::op(), which leads to an context mismatch ICE during
gimplification of the proxy.

This patch naively fixes this by making us restore LAMBDA_EXPR_THIS_CAPTURE
after instantiating a lambda's op().

	PR c++/116756

gcc/cp/ChangeLog:

	* pt.cc (instantiate_body): Restore LAMBDA_EXPR_THIS_CAPTURE
	after instantiating a lambda's op().

gcc/testsuite/ChangeLog:

	* g++.dg/cpp1z/constexpr-if-lambda7.C: New test.
---
 gcc/cp/pt.cc                                  | 11 +++++++++
 .../g++.dg/cpp1z/constexpr-if-lambda7.C       | 24 +++++++++++++++++++
 2 files changed, 35 insertions(+)
 create mode 100644 gcc/testsuite/g++.dg/cpp1z/constexpr-if-lambda7.C
  

Comments

Jason Merrill Jan. 14, 2025, 2:06 a.m. UTC | #1
On 1/10/25 2:20 PM, Patrick Palka wrote:
> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look
> OK for trunk?
> 
> The documentation for LAMBDA_EXPR_THIS_CAPTURE seems outdated because
> it says the field is only used at parse time, but apparently it's also
> used at instantiation time.
> 
> Non-'this' captures don't seem to be affected, because there is no
> corresponding LAMBDA_EXPR field that gets clobbered, and instead their
> uses get resolved via the local specialization mechanism which is
> recursion aware.
> 
> The bug also disappears if we explicitly use this in the openSeries call,
> i.e. this->openSeries(...), because that sidesteps the use of
> maybe_resolve_dummy / LAMBDA_EXPR_THIS_CAPTURE for resolving the
> implicit object, and instead gets resolved via the local mechanism
> specialization.
> 
> Maybe this suggests that there's a better way to fix this, but I'm not
> sure...

That does sound like an interesting direction.  Maybe for a generic 
lambda, LAMBDA_EXPR_THIS_CAPTURE could just refer to the captured 
parameter, and we use retrieve_local_specialization to find the proxy?

> -- >8 --
> 
> Here during instantiation of lambda::op() [with I = 0] we substitute
> into the call self(self, cst<1>{}) which requires recursive instantiation
> of the same lambda::op() [with I = 1] (which isn't deferred due to
> lambda's deduced return type.  During this recursive instantiation, the
> DECL_EXPR case of tsubst_stmt clobbers LAMBDA_EXPR_THIS_CAPTURE to point
> to the inner lambda::op()'s capture proxy instead of the outer
> lambda::op(), and the original value is never restored.
> 
> So later during substitution into the openSeries call in the outer
> lambda::op() maybe_resolve_dummy uses the 'this' proxy belonging to the
> inner lambda::op(), which leads to an context mismatch ICE during
> gimplification of the proxy.
> 
> This patch naively fixes this by making us restore LAMBDA_EXPR_THIS_CAPTURE
> after instantiating a lambda's op().
> 
> 	PR c++/116756
> 
> gcc/cp/ChangeLog:
> 
> 	* pt.cc (instantiate_body): Restore LAMBDA_EXPR_THIS_CAPTURE
> 	after instantiating a lambda's op().
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp1z/constexpr-if-lambda7.C: New test.
> ---
>   gcc/cp/pt.cc                                  | 11 +++++++++
>   .../g++.dg/cpp1z/constexpr-if-lambda7.C       | 24 +++++++++++++++++++
>   2 files changed, 35 insertions(+)
>   create mode 100644 gcc/testsuite/g++.dg/cpp1z/constexpr-if-lambda7.C
> 
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index b22129d8a46..a141de56446 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -27512,6 +27512,13 @@ instantiate_body (tree pattern, tree args, tree d, bool nested_p)
>         local_specialization_stack lss (push_to_top ? lss_blank : lss_copy);
>         tree block = NULL_TREE;
>   
> +      tree saved_this_capture = NULL_TREE;
> +      if (LAMBDA_FUNCTION_P (d))
> +	/* Save/restore the 'this' capture, which gets clobbered by tsubst_stmt,
> +	   which causes problems in case of recursive op() instantiation.  */
> +	saved_this_capture
> +	  = LAMBDA_EXPR_THIS_CAPTURE (CLASSTYPE_LAMBDA_EXPR (DECL_CONTEXT (d)));
> +
>         /* Set up context.  */
>         if (nested_p)
>   	block = push_stmt_list ();
> @@ -27555,6 +27562,10 @@ instantiate_body (tree pattern, tree args, tree d, bool nested_p)
>   
>         if (DECL_OMP_DECLARE_REDUCTION_P (code_pattern))
>   	cp_check_omp_declare_reduction (d);
> +
> +      if (LAMBDA_FUNCTION_P (d))
> +	LAMBDA_EXPR_THIS_CAPTURE (CLASSTYPE_LAMBDA_EXPR (DECL_CONTEXT (d)))
> +	  = saved_this_capture;
>       }
>   
>     /* We're not deferring instantiation any more.  */
> diff --git a/gcc/testsuite/g++.dg/cpp1z/constexpr-if-lambda7.C b/gcc/testsuite/g++.dg/cpp1z/constexpr-if-lambda7.C
> new file mode 100644
> index 00000000000..8304c8f22e3
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp1z/constexpr-if-lambda7.C
> @@ -0,0 +1,24 @@
> +// PR c++/116756
> +// { dg-do compile { target c++17 } }
> +
> +template<int N> struct cst { static constexpr int value = N; };
> +
> +struct Store {
> +  void openDF() {
> +    auto lambda = [this](auto& self, auto I) {
> +      if constexpr (I.value == 0) {
> +        auto next = [&self] { self(self, cst<1>{}); };
> +        openSeries(next);
> +      } else {
> +        openSeries(0);
> +      }
> +    };
> +    lambda(lambda, cst<0>{});
> +  }
> +  template<class T> void openSeries(T) { }
> +};
> +
> +int main() {
> +  Store store;
> +  store.openDF();
> +}
  
Patrick Palka Jan. 16, 2025, 7:02 p.m. UTC | #2
On Mon, 13 Jan 2025, Jason Merrill wrote:

> On 1/10/25 2:20 PM, Patrick Palka wrote:
> > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look
> > OK for trunk?
> > 
> > The documentation for LAMBDA_EXPR_THIS_CAPTURE seems outdated because
> > it says the field is only used at parse time, but apparently it's also
> > used at instantiation time.
> > 
> > Non-'this' captures don't seem to be affected, because there is no
> > corresponding LAMBDA_EXPR field that gets clobbered, and instead their
> > uses get resolved via the local specialization mechanism which is
> > recursion aware.
> > 
> > The bug also disappears if we explicitly use this in the openSeries call,
> > i.e. this->openSeries(...), because that sidesteps the use of
> > maybe_resolve_dummy / LAMBDA_EXPR_THIS_CAPTURE for resolving the
> > implicit object, and instead gets resolved via the local mechanism
> > specialization.
> > 
> > Maybe this suggests that there's a better way to fix this, but I'm not
> > sure...
> 
> That does sound like an interesting direction.  Maybe for a generic lambda,
> LAMBDA_EXPR_THIS_CAPTURE could just refer to the captured parameter, and we
> use retrieve_local_specialization to find the proxy?

Like so?  Tested on x86_64-pc-linux-gnu, full bootstrap+regtest in
progress.

-- >8 --

Subject: [PATCH v2] c++: 'this' capture clobbered during recursive inst
 [PR116756]

Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look
OK for trunk?

-- >8 --

Here during instantiation of generic lambda's op() [with I = 0] we
substitute into the call self(self, cst<1>{}) which requires recursive
instantiation of the same op() [with I = 1] (which isn't deferred due to
lambda's deduced return type.  During this recursive instantiation, the
DECL_EXPR case of tsubst_stmt clobbers LAMBDA_EXPR_THIS_CAPTURE to point
to the child op()'s specialized capture proxy instead of the parent's,
and the original value is never restored.

So later when substituting into the openSeries call in the parent op()
maybe_resolve_dummy uses the 'this' proxy belonging to the child op(),
which leads to a context mismatch ICE during gimplification of the
proxy.

An earlier version of this patch fixed this by making instantiate_body
save/restore LAMBDA_EXPR_THIS_CAPTURE during a lambda op() instantiation.
But it seems cleaner to avoid overwriting LAMBDA_EXPR_THIS_CAPTURE in the
first place by making it point to the non-specialized capture proxy, and
instead call retrieve_local_specialization as needed, which is what this
patch implements.  It's simpler then to not clear LAMBDA_EXPR_THIS_CAPTURE
after parsing/regenerating a lambda.

	PR c++/116756

gcc/cp/ChangeLog:

	* lambda.cc (lambda_expr_this_capture): Call
	retrieve_local_specialization on the result of
	LAMBDA_EXPR_THIS_CAPTURE for a generic lambda.
	* parser.cc (cp_parser_lambda_expression): Don't clear
	LAMBDA_EXPR_THIS_CAPTURE.
	* pt.cc (tsubst_stmt) <case DECL_EXPR>: Don't overwrite
	LAMBDA_EXPR_THIS_CAPTURE.
	(tsubst_lambda_expr): Don't clear LAMBDA_EXPR_THIS_CAPTURE
	afterward.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp1z/constexpr-if-lambda7.C: New test.
---
 gcc/cp/lambda.cc                              |  6 +++++
 gcc/cp/parser.cc                              |  3 ---
 gcc/cp/pt.cc                                  | 11 +--------
 .../g++.dg/cpp1z/constexpr-if-lambda7.C       | 24 +++++++++++++++++++
 4 files changed, 31 insertions(+), 13 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp1z/constexpr-if-lambda7.C

diff --git a/gcc/cp/lambda.cc b/gcc/cp/lambda.cc
index be8a0fe01cb..4ee8f6c745d 100644
--- a/gcc/cp/lambda.cc
+++ b/gcc/cp/lambda.cc
@@ -785,6 +785,12 @@ lambda_expr_this_capture (tree lambda, int add_capture_p)
   tree result;
 
   tree this_capture = LAMBDA_EXPR_THIS_CAPTURE (lambda);
+  if (this_capture)
+    if (tree spec = retrieve_local_specialization (this_capture))
+      {
+	gcc_checking_assert (generic_lambda_fn_p (lambda_function (lambda)));
+	this_capture = spec;
+      }
 
   /* In unevaluated context this isn't an odr-use, so don't capture.  */
   if (cp_unevaluated_operand)
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 74f4f7cd6d8..16bbb87a815 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -11723,9 +11723,6 @@ cp_parser_lambda_expression (cp_parser* parser)
     parser->omp_array_section_p = saved_omp_array_section_p;
   }
 
-  /* This field is only used during parsing of the lambda.  */
-  LAMBDA_EXPR_THIS_CAPTURE (lambda_expr) = NULL_TREE;
-
   /* This lambda shouldn't have any proxies left at this point.  */
   gcc_assert (LAMBDA_EXPR_PENDING_PROXIES (lambda_expr) == NULL);
   /* And now that we're done, push proxies for an enclosing lambda.  */
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 961696f333e..64c7d3da405 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -18938,12 +18938,6 @@ tsubst_stmt (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 		else if (is_capture_proxy (DECL_EXPR_DECL (t)))
 		  {
 		    DECL_CONTEXT (decl) = current_function_decl;
-		    if (DECL_NAME (decl) == this_identifier)
-		      {
-			tree lam = DECL_CONTEXT (current_function_decl);
-			lam = CLASSTYPE_LAMBDA_EXPR (lam);
-			LAMBDA_EXPR_THIS_CAPTURE (lam) = decl;
-		      }
 		    insert_capture_proxy (decl);
 		  }
 		else if (DECL_IMPLICIT_TYPEDEF_P (t))
@@ -20146,8 +20140,7 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
     LAMBDA_EXPR_REGEN_INFO (r)
       = build_template_info (t, preserve_args (args));
 
-  gcc_assert (LAMBDA_EXPR_THIS_CAPTURE (t) == NULL_TREE
-	      && LAMBDA_EXPR_PENDING_PROXIES (t) == NULL);
+  gcc_assert (LAMBDA_EXPR_PENDING_PROXIES (t) == NULL);
 
   vec<tree,va_gc>* field_packs = NULL;
   unsigned name_independent_cnt = 0;
@@ -20362,8 +20355,6 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
       LAMBDA_EXPR_CAPTURE_LIST (r)
 	= nreverse (LAMBDA_EXPR_CAPTURE_LIST (r));
 
-      LAMBDA_EXPR_THIS_CAPTURE (r) = NULL_TREE;
-
       maybe_add_lambda_conv_op (type);
     }
 
diff --git a/gcc/testsuite/g++.dg/cpp1z/constexpr-if-lambda7.C b/gcc/testsuite/g++.dg/cpp1z/constexpr-if-lambda7.C
new file mode 100644
index 00000000000..8304c8f22e3
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1z/constexpr-if-lambda7.C
@@ -0,0 +1,24 @@
+// PR c++/116756
+// { dg-do compile { target c++17 } }
+
+template<int N> struct cst { static constexpr int value = N; };
+
+struct Store {
+  void openDF() {
+    auto lambda = [this](auto& self, auto I) {
+      if constexpr (I.value == 0) {
+        auto next = [&self] { self(self, cst<1>{}); };
+        openSeries(next);
+      } else {
+        openSeries(0);
+      }
+    };
+    lambda(lambda, cst<0>{});
+  }
+  template<class T> void openSeries(T) { }
+};
+
+int main() {
+  Store store;
+  store.openDF();
+}
  
Jason Merrill Jan. 21, 2025, 9:52 p.m. UTC | #3
On 1/16/25 2:02 PM, Patrick Palka wrote:
> On Mon, 13 Jan 2025, Jason Merrill wrote:
> 
>> On 1/10/25 2:20 PM, Patrick Palka wrote:
>>> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look
>>> OK for trunk?
>>>
>>> The documentation for LAMBDA_EXPR_THIS_CAPTURE seems outdated because
>>> it says the field is only used at parse time, but apparently it's also
>>> used at instantiation time.
>>>
>>> Non-'this' captures don't seem to be affected, because there is no
>>> corresponding LAMBDA_EXPR field that gets clobbered, and instead their
>>> uses get resolved via the local specialization mechanism which is
>>> recursion aware.
>>>
>>> The bug also disappears if we explicitly use this in the openSeries call,
>>> i.e. this->openSeries(...), because that sidesteps the use of
>>> maybe_resolve_dummy / LAMBDA_EXPR_THIS_CAPTURE for resolving the
>>> implicit object, and instead gets resolved via the local mechanism
>>> specialization.
>>>
>>> Maybe this suggests that there's a better way to fix this, but I'm not
>>> sure...
>>
>> That does sound like an interesting direction.  Maybe for a generic lambda,
>> LAMBDA_EXPR_THIS_CAPTURE could just refer to the captured parameter, and we
>> use retrieve_local_specialization to find the proxy?
> 
> Like so?  Tested on x86_64-pc-linux-gnu, full bootstrap+regtest in
> progress.
> 
> -- >8 --
> 
> Subject: [PATCH v2] c++: 'this' capture clobbered during recursive inst
>   [PR116756]
> 
> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look
> OK for trunk?

OK.

> -- >8 --
> 
> Here during instantiation of generic lambda's op() [with I = 0] we
> substitute into the call self(self, cst<1>{}) which requires recursive
> instantiation of the same op() [with I = 1] (which isn't deferred due to
> lambda's deduced return type.  During this recursive instantiation, the
> DECL_EXPR case of tsubst_stmt clobbers LAMBDA_EXPR_THIS_CAPTURE to point
> to the child op()'s specialized capture proxy instead of the parent's,
> and the original value is never restored.
> 
> So later when substituting into the openSeries call in the parent op()
> maybe_resolve_dummy uses the 'this' proxy belonging to the child op(),
> which leads to a context mismatch ICE during gimplification of the
> proxy.
> 
> An earlier version of this patch fixed this by making instantiate_body
> save/restore LAMBDA_EXPR_THIS_CAPTURE during a lambda op() instantiation.
> But it seems cleaner to avoid overwriting LAMBDA_EXPR_THIS_CAPTURE in the
> first place by making it point to the non-specialized capture proxy, and
> instead call retrieve_local_specialization as needed, which is what this
> patch implements.  It's simpler then to not clear LAMBDA_EXPR_THIS_CAPTURE
> after parsing/regenerating a lambda.
> 
> 	PR c++/116756
> 
> gcc/cp/ChangeLog:
> 
> 	* lambda.cc (lambda_expr_this_capture): Call
> 	retrieve_local_specialization on the result of
> 	LAMBDA_EXPR_THIS_CAPTURE for a generic lambda.
> 	* parser.cc (cp_parser_lambda_expression): Don't clear
> 	LAMBDA_EXPR_THIS_CAPTURE.
> 	* pt.cc (tsubst_stmt) <case DECL_EXPR>: Don't overwrite
> 	LAMBDA_EXPR_THIS_CAPTURE.
> 	(tsubst_lambda_expr): Don't clear LAMBDA_EXPR_THIS_CAPTURE
> 	afterward.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp1z/constexpr-if-lambda7.C: New test.
> ---
>   gcc/cp/lambda.cc                              |  6 +++++
>   gcc/cp/parser.cc                              |  3 ---
>   gcc/cp/pt.cc                                  | 11 +--------
>   .../g++.dg/cpp1z/constexpr-if-lambda7.C       | 24 +++++++++++++++++++
>   4 files changed, 31 insertions(+), 13 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp1z/constexpr-if-lambda7.C
> 
> diff --git a/gcc/cp/lambda.cc b/gcc/cp/lambda.cc
> index be8a0fe01cb..4ee8f6c745d 100644
> --- a/gcc/cp/lambda.cc
> +++ b/gcc/cp/lambda.cc
> @@ -785,6 +785,12 @@ lambda_expr_this_capture (tree lambda, int add_capture_p)
>     tree result;
>   
>     tree this_capture = LAMBDA_EXPR_THIS_CAPTURE (lambda);
> +  if (this_capture)
> +    if (tree spec = retrieve_local_specialization (this_capture))
> +      {
> +	gcc_checking_assert (generic_lambda_fn_p (lambda_function (lambda)));
> +	this_capture = spec;
> +      }
>   
>     /* In unevaluated context this isn't an odr-use, so don't capture.  */
>     if (cp_unevaluated_operand)
> diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
> index 74f4f7cd6d8..16bbb87a815 100644
> --- a/gcc/cp/parser.cc
> +++ b/gcc/cp/parser.cc
> @@ -11723,9 +11723,6 @@ cp_parser_lambda_expression (cp_parser* parser)
>       parser->omp_array_section_p = saved_omp_array_section_p;
>     }
>   
> -  /* This field is only used during parsing of the lambda.  */
> -  LAMBDA_EXPR_THIS_CAPTURE (lambda_expr) = NULL_TREE;
> -
>     /* This lambda shouldn't have any proxies left at this point.  */
>     gcc_assert (LAMBDA_EXPR_PENDING_PROXIES (lambda_expr) == NULL);
>     /* And now that we're done, push proxies for an enclosing lambda.  */
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 961696f333e..64c7d3da405 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -18938,12 +18938,6 @@ tsubst_stmt (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>   		else if (is_capture_proxy (DECL_EXPR_DECL (t)))
>   		  {
>   		    DECL_CONTEXT (decl) = current_function_decl;
> -		    if (DECL_NAME (decl) == this_identifier)
> -		      {
> -			tree lam = DECL_CONTEXT (current_function_decl);
> -			lam = CLASSTYPE_LAMBDA_EXPR (lam);
> -			LAMBDA_EXPR_THIS_CAPTURE (lam) = decl;
> -		      }
>   		    insert_capture_proxy (decl);
>   		  }
>   		else if (DECL_IMPLICIT_TYPEDEF_P (t))
> @@ -20146,8 +20140,7 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>       LAMBDA_EXPR_REGEN_INFO (r)
>         = build_template_info (t, preserve_args (args));
>   
> -  gcc_assert (LAMBDA_EXPR_THIS_CAPTURE (t) == NULL_TREE
> -	      && LAMBDA_EXPR_PENDING_PROXIES (t) == NULL);
> +  gcc_assert (LAMBDA_EXPR_PENDING_PROXIES (t) == NULL);
>   
>     vec<tree,va_gc>* field_packs = NULL;
>     unsigned name_independent_cnt = 0;
> @@ -20362,8 +20355,6 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>         LAMBDA_EXPR_CAPTURE_LIST (r)
>   	= nreverse (LAMBDA_EXPR_CAPTURE_LIST (r));
>   
> -      LAMBDA_EXPR_THIS_CAPTURE (r) = NULL_TREE;
> -
>         maybe_add_lambda_conv_op (type);
>       }
>   
> diff --git a/gcc/testsuite/g++.dg/cpp1z/constexpr-if-lambda7.C b/gcc/testsuite/g++.dg/cpp1z/constexpr-if-lambda7.C
> new file mode 100644
> index 00000000000..8304c8f22e3
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp1z/constexpr-if-lambda7.C
> @@ -0,0 +1,24 @@
> +// PR c++/116756
> +// { dg-do compile { target c++17 } }
> +
> +template<int N> struct cst { static constexpr int value = N; };
> +
> +struct Store {
> +  void openDF() {
> +    auto lambda = [this](auto& self, auto I) {
> +      if constexpr (I.value == 0) {
> +        auto next = [&self] { self(self, cst<1>{}); };
> +        openSeries(next);
> +      } else {
> +        openSeries(0);
> +      }
> +    };
> +    lambda(lambda, cst<0>{});
> +  }
> +  template<class T> void openSeries(T) { }
> +};
> +
> +int main() {
> +  Store store;
> +  store.openDF();
> +}
  

Patch

diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index b22129d8a46..a141de56446 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -27512,6 +27512,13 @@  instantiate_body (tree pattern, tree args, tree d, bool nested_p)
       local_specialization_stack lss (push_to_top ? lss_blank : lss_copy);
       tree block = NULL_TREE;
 
+      tree saved_this_capture = NULL_TREE;
+      if (LAMBDA_FUNCTION_P (d))
+	/* Save/restore the 'this' capture, which gets clobbered by tsubst_stmt,
+	   which causes problems in case of recursive op() instantiation.  */
+	saved_this_capture
+	  = LAMBDA_EXPR_THIS_CAPTURE (CLASSTYPE_LAMBDA_EXPR (DECL_CONTEXT (d)));
+
       /* Set up context.  */
       if (nested_p)
 	block = push_stmt_list ();
@@ -27555,6 +27562,10 @@  instantiate_body (tree pattern, tree args, tree d, bool nested_p)
 
       if (DECL_OMP_DECLARE_REDUCTION_P (code_pattern))
 	cp_check_omp_declare_reduction (d);
+
+      if (LAMBDA_FUNCTION_P (d))
+	LAMBDA_EXPR_THIS_CAPTURE (CLASSTYPE_LAMBDA_EXPR (DECL_CONTEXT (d)))
+	  = saved_this_capture;
     }
 
   /* We're not deferring instantiation any more.  */
diff --git a/gcc/testsuite/g++.dg/cpp1z/constexpr-if-lambda7.C b/gcc/testsuite/g++.dg/cpp1z/constexpr-if-lambda7.C
new file mode 100644
index 00000000000..8304c8f22e3
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1z/constexpr-if-lambda7.C
@@ -0,0 +1,24 @@ 
+// PR c++/116756
+// { dg-do compile { target c++17 } }
+
+template<int N> struct cst { static constexpr int value = N; };
+
+struct Store {
+  void openDF() {
+    auto lambda = [this](auto& self, auto I) {
+      if constexpr (I.value == 0) {
+        auto next = [&self] { self(self, cst<1>{}); };
+        openSeries(next);
+      } else {
+        openSeries(0);
+      }
+    };
+    lambda(lambda, cst<0>{});
+  }
+  template<class T> void openSeries(T) { }
+};
+
+int main() {
+  Store store;
+  store.openDF();
+}