c++: nested lambda capturing a capture proxy [PR94376]

Message ID 20211102180653.1146172-1-ppalka@redhat.com
State New
Headers
Series c++: nested lambda capturing a capture proxy [PR94376] |

Commit Message

Patrick Palka Nov. 2, 2021, 6:06 p.m. UTC
  Here when determining the type of the FIELD_DECL for the by-value
capture of 'i' in the inner lambda, we incorrectly give it the
type const int instead of int since the effective initializer is
the proxy for the outer capture, and this proxy is const qualified
since the outer lambda is non-mutable.

This patch fixes this by handling capturing of capture proxies specially
in lambda_capture_field_type, namely we instead consider the type of
their FIELD_DECL which unlike the proxy has the true cv-quals of the
captured entity.

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

	PR c++/94376

gcc/cp/ChangeLog:

	* lambda.c (lambda_capture_field_type): Simplify by handling the
	is_this case first and specially.  When capturing by-value a
	capture proxy, consider the type of the corresponding field
	instead.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp0x/lambda/lambda-nested9.C: New test.
---
 gcc/cp/lambda.c                               | 19 +++++++--
 .../g++.dg/cpp0x/lambda/lambda-nested9.C      | 41 +++++++++++++++++++
 2 files changed, 56 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp0x/lambda/lambda-nested9.C
  

Comments

Jason Merrill Nov. 18, 2021, 10:18 p.m. UTC | #1
On 11/2/21 14:06, Patrick Palka wrote:
> Here when determining the type of the FIELD_DECL for the by-value
> capture of 'i' in the inner lambda, we incorrectly give it the
> type const int instead of int since the effective initializer is
> the proxy for the outer capture, and this proxy is const qualified
> since the outer lambda is non-mutable.
> 
> This patch fixes this by handling capturing of capture proxies specially
> in lambda_capture_field_type, namely we instead consider the type of
> their FIELD_DECL which unlike the proxy has the true cv-quals of the
> captured entity.
> 
> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
> trunk?
> 
> 	PR c++/94376
> 
> gcc/cp/ChangeLog:
> 
> 	* lambda.c (lambda_capture_field_type): Simplify by handling the
> 	is_this case first and specially.  When capturing by-value a
> 	capture proxy, consider the type of the corresponding field
> 	instead.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp0x/lambda/lambda-nested9.C: New test.
> ---
>   gcc/cp/lambda.c                               | 19 +++++++--
>   .../g++.dg/cpp0x/lambda/lambda-nested9.C      | 41 +++++++++++++++++++
>   2 files changed, 56 insertions(+), 4 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp0x/lambda/lambda-nested9.C
> 
> diff --git a/gcc/cp/lambda.c b/gcc/cp/lambda.c
> index 2e9d38bbe83..f16c121dc78 100644
> --- a/gcc/cp/lambda.c
> +++ b/gcc/cp/lambda.c
> @@ -196,7 +196,9 @@ lambda_capture_field_type (tree expr, bool explicit_init_p,
>     tree type;
>     bool is_this = is_this_parameter (tree_strip_nop_conversions (expr));
>   
> -  if (!is_this && explicit_init_p)
> +  if (is_this)
> +    type = TREE_TYPE (expr);
> +  else if (explicit_init_p)
>       {
>         tree auto_node = make_auto ();
>         
> @@ -210,7 +212,7 @@ lambda_capture_field_type (tree expr, bool explicit_init_p,
>         else
>   	type = do_auto_deduction (type, expr, auto_node);
>       }
> -  else if (!is_this && type_dependent_expression_p (expr))
> +  else if (type_dependent_expression_p (expr))
>       {
>         type = cxx_make_type (DECLTYPE_TYPE);
>         DECLTYPE_TYPE_EXPR (type) = expr;
> @@ -220,10 +222,19 @@ lambda_capture_field_type (tree expr, bool explicit_init_p,
>       }
>     else
>       {
> +      if (!by_reference_p && is_capture_proxy (expr))
> +	{
> +	  /* When capturing by-value another capture proxy from an enclosing
> +	     lambda, consider the type of the corresponding field instead,
> +	     as the proxy may be const-qualifed if the enclosing lambda is
> +	     non-mutable (PR94376).  */
> +	  gcc_assert (TREE_CODE (DECL_VALUE_EXPR (expr)) == COMPONENT_REF);
> +	  expr = TREE_OPERAND (DECL_VALUE_EXPR (expr), 1);

Is there a reason not to use DECL_CAPTURED_VARIABLE?

> +	}
> +
>         type = non_reference (unlowered_expr_type (expr));
>   
> -      if (!is_this
> -	  && (by_reference_p || TREE_CODE (type) == FUNCTION_TYPE))
> +      if (by_reference_p || TREE_CODE (type) == FUNCTION_TYPE)
>   	type = build_reference_type (type);
>       }
>   
> diff --git a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-nested9.C b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-nested9.C
> new file mode 100644
> index 00000000000..ff7da3b0ce1
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-nested9.C
> @@ -0,0 +1,41 @@
> +// PR c++/94376
> +// { dg-do compile { target c++11 } }
> +
> +int main() {
> +  // We used to incorrectly reject the first two cases.
> +  int i = 0;
> +  [=] () {
> +    [=] () mutable {
> +      ++i;
> +    };
> +  };
> +
> +#if __cpp_init_captures
> +  [j=0] () {
> +    [=] () mutable {
> +      ++j;
> +    };
> +  };
> +#endif
> +
> +  [=] () {
> +    [&] () mutable {
> +      ++i; // { dg-error "read-only" }
> +    };
> +  };
> +
> +  const int j = 0;
> +  [=] () {
> +    [=] () mutable {
> +      ++j; // { dg-error "read-only" }
> +    };
> +  };
> +
> +#if __cpp_init_captures
> +  [j=0] () {
> +    [&] () mutable {
> +      ++j; // { dg-error "read-only" }
> +    };
> +  };
> +#endif
> +}
>
  
Patrick Palka Nov. 18, 2021, 10:48 p.m. UTC | #2
On Thu, 18 Nov 2021, Jason Merrill wrote:

> On 11/2/21 14:06, Patrick Palka wrote:
> > Here when determining the type of the FIELD_DECL for the by-value
> > capture of 'i' in the inner lambda, we incorrectly give it the
> > type const int instead of int since the effective initializer is
> > the proxy for the outer capture, and this proxy is const qualified
> > since the outer lambda is non-mutable.
> > 
> > This patch fixes this by handling capturing of capture proxies specially
> > in lambda_capture_field_type, namely we instead consider the type of
> > their FIELD_DECL which unlike the proxy has the true cv-quals of the
> > captured entity.
> > 
> > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
> > trunk?
> > 
> > 	PR c++/94376
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	* lambda.c (lambda_capture_field_type): Simplify by handling the
> > 	is_this case first and specially.  When capturing by-value a
> > 	capture proxy, consider the type of the corresponding field
> > 	instead.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> > 	* g++.dg/cpp0x/lambda/lambda-nested9.C: New test.
> > ---
> >   gcc/cp/lambda.c                               | 19 +++++++--
> >   .../g++.dg/cpp0x/lambda/lambda-nested9.C      | 41 +++++++++++++++++++
> >   2 files changed, 56 insertions(+), 4 deletions(-)
> >   create mode 100644 gcc/testsuite/g++.dg/cpp0x/lambda/lambda-nested9.C
> > 
> > diff --git a/gcc/cp/lambda.c b/gcc/cp/lambda.c
> > index 2e9d38bbe83..f16c121dc78 100644
> > --- a/gcc/cp/lambda.c
> > +++ b/gcc/cp/lambda.c
> > @@ -196,7 +196,9 @@ lambda_capture_field_type (tree expr, bool
> > explicit_init_p,
> >     tree type;
> >     bool is_this = is_this_parameter (tree_strip_nop_conversions (expr));
> >   -  if (!is_this && explicit_init_p)
> > +  if (is_this)
> > +    type = TREE_TYPE (expr);
> > +  else if (explicit_init_p)
> >       {
> >         tree auto_node = make_auto ();
> >         @@ -210,7 +212,7 @@ lambda_capture_field_type (tree expr, bool
> > explicit_init_p,
> >         else
> >   	type = do_auto_deduction (type, expr, auto_node);
> >       }
> > -  else if (!is_this && type_dependent_expression_p (expr))
> > +  else if (type_dependent_expression_p (expr))
> >       {
> >         type = cxx_make_type (DECLTYPE_TYPE);
> >         DECLTYPE_TYPE_EXPR (type) = expr;
> > @@ -220,10 +222,19 @@ lambda_capture_field_type (tree expr, bool
> > explicit_init_p,
> >       }
> >     else
> >       {
> > +      if (!by_reference_p && is_capture_proxy (expr))
> > +	{
> > +	  /* When capturing by-value another capture proxy from an enclosing
> > +	     lambda, consider the type of the corresponding field instead,
> > +	     as the proxy may be const-qualifed if the enclosing lambda is
> > +	     non-mutable (PR94376).  */
> > +	  gcc_assert (TREE_CODE (DECL_VALUE_EXPR (expr)) == COMPONENT_REF);
> > +	  expr = TREE_OPERAND (DECL_VALUE_EXPR (expr), 1);
> 
> Is there a reason not to use DECL_CAPTURED_VARIABLE?

IIRC that'd work for normal capture proxies, but DECL_CAPTURED_VARIABLE
is empty for explicitly-initialized captures and their capture proxies
have the same issue.

> 
> > +	}
> > +
> >         type = non_reference (unlowered_expr_type (expr));
> >   -      if (!is_this
> > -	  && (by_reference_p || TREE_CODE (type) == FUNCTION_TYPE))
> > +      if (by_reference_p || TREE_CODE (type) == FUNCTION_TYPE)
> >   	type = build_reference_type (type);
> >       }
> >   diff --git a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-nested9.C
> > b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-nested9.C
> > new file mode 100644
> > index 00000000000..ff7da3b0ce1
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-nested9.C
> > @@ -0,0 +1,41 @@
> > +// PR c++/94376
> > +// { dg-do compile { target c++11 } }
> > +
> > +int main() {
> > +  // We used to incorrectly reject the first two cases.
> > +  int i = 0;
> > +  [=] () {
> > +    [=] () mutable {
> > +      ++i;
> > +    };
> > +  };
> > +
> > +#if __cpp_init_captures
> > +  [j=0] () {
> > +    [=] () mutable {
> > +      ++j;
> > +    };
> > +  };
> > +#endif
> > +
> > +  [=] () {
> > +    [&] () mutable {
> > +      ++i; // { dg-error "read-only" }
> > +    };
> > +  };
> > +
> > +  const int j = 0;
> > +  [=] () {
> > +    [=] () mutable {
> > +      ++j; // { dg-error "read-only" }
> > +    };
> > +  };
> > +
> > +#if __cpp_init_captures
> > +  [j=0] () {
> > +    [&] () mutable {
> > +      ++j; // { dg-error "read-only" }
> > +    };
> > +  };
> > +#endif
> > +}
> > 
> 
>
  
Jason Merrill Nov. 19, 2021, 1:57 a.m. UTC | #3
On 11/18/21 17:48, Patrick Palka wrote:
> On Thu, 18 Nov 2021, Jason Merrill wrote:
> 
>> On 11/2/21 14:06, Patrick Palka wrote:
>>> Here when determining the type of the FIELD_DECL for the by-value
>>> capture of 'i' in the inner lambda, we incorrectly give it the
>>> type const int instead of int since the effective initializer is
>>> the proxy for the outer capture, and this proxy is const qualified
>>> since the outer lambda is non-mutable.
>>>
>>> This patch fixes this by handling capturing of capture proxies specially
>>> in lambda_capture_field_type, namely we instead consider the type of
>>> their FIELD_DECL which unlike the proxy has the true cv-quals of the
>>> captured entity.
>>>
>>> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
>>> trunk?
>>>
>>> 	PR c++/94376
>>>
>>> gcc/cp/ChangeLog:
>>>
>>> 	* lambda.c (lambda_capture_field_type): Simplify by handling the
>>> 	is_this case first and specially.  When capturing by-value a
>>> 	capture proxy, consider the type of the corresponding field
>>> 	instead.
>>>
>>> gcc/testsuite/ChangeLog:
>>>
>>> 	* g++.dg/cpp0x/lambda/lambda-nested9.C: New test.
>>> ---
>>>    gcc/cp/lambda.c                               | 19 +++++++--
>>>    .../g++.dg/cpp0x/lambda/lambda-nested9.C      | 41 +++++++++++++++++++
>>>    2 files changed, 56 insertions(+), 4 deletions(-)
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp0x/lambda/lambda-nested9.C
>>>
>>> diff --git a/gcc/cp/lambda.c b/gcc/cp/lambda.c
>>> index 2e9d38bbe83..f16c121dc78 100644
>>> --- a/gcc/cp/lambda.c
>>> +++ b/gcc/cp/lambda.c
>>> @@ -196,7 +196,9 @@ lambda_capture_field_type (tree expr, bool
>>> explicit_init_p,
>>>      tree type;
>>>      bool is_this = is_this_parameter (tree_strip_nop_conversions (expr));
>>>    -  if (!is_this && explicit_init_p)
>>> +  if (is_this)
>>> +    type = TREE_TYPE (expr);
>>> +  else if (explicit_init_p)
>>>        {
>>>          tree auto_node = make_auto ();
>>>          @@ -210,7 +212,7 @@ lambda_capture_field_type (tree expr, bool
>>> explicit_init_p,
>>>          else
>>>    	type = do_auto_deduction (type, expr, auto_node);
>>>        }
>>> -  else if (!is_this && type_dependent_expression_p (expr))
>>> +  else if (type_dependent_expression_p (expr))
>>>        {
>>>          type = cxx_make_type (DECLTYPE_TYPE);
>>>          DECLTYPE_TYPE_EXPR (type) = expr;
>>> @@ -220,10 +222,19 @@ lambda_capture_field_type (tree expr, bool
>>> explicit_init_p,
>>>        }
>>>      else
>>>        {
>>> +      if (!by_reference_p && is_capture_proxy (expr))
>>> +	{
>>> +	  /* When capturing by-value another capture proxy from an enclosing
>>> +	     lambda, consider the type of the corresponding field instead,
>>> +	     as the proxy may be const-qualifed if the enclosing lambda is
>>> +	     non-mutable (PR94376).  */
>>> +	  gcc_assert (TREE_CODE (DECL_VALUE_EXPR (expr)) == COMPONENT_REF);
>>> +	  expr = TREE_OPERAND (DECL_VALUE_EXPR (expr), 1);
>>
>> Is there a reason not to use DECL_CAPTURED_VARIABLE?
> 
> IIRC that'd work for normal capture proxies, but DECL_CAPTURED_VARIABLE
> is empty for explicitly-initialized captures and their capture proxies
> have the same issue.

Then the patch is OK as is.

>>
>>> +	}
>>> +
>>>          type = non_reference (unlowered_expr_type (expr));
>>>    -      if (!is_this
>>> -	  && (by_reference_p || TREE_CODE (type) == FUNCTION_TYPE))
>>> +      if (by_reference_p || TREE_CODE (type) == FUNCTION_TYPE)
>>>    	type = build_reference_type (type);
>>>        }
>>>    diff --git a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-nested9.C
>>> b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-nested9.C
>>> new file mode 100644
>>> index 00000000000..ff7da3b0ce1
>>> --- /dev/null
>>> +++ b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-nested9.C
>>> @@ -0,0 +1,41 @@
>>> +// PR c++/94376
>>> +// { dg-do compile { target c++11 } }
>>> +
>>> +int main() {
>>> +  // We used to incorrectly reject the first two cases.
>>> +  int i = 0;
>>> +  [=] () {
>>> +    [=] () mutable {
>>> +      ++i;
>>> +    };
>>> +  };
>>> +
>>> +#if __cpp_init_captures
>>> +  [j=0] () {
>>> +    [=] () mutable {
>>> +      ++j;
>>> +    };
>>> +  };
>>> +#endif
>>> +
>>> +  [=] () {
>>> +    [&] () mutable {
>>> +      ++i; // { dg-error "read-only" }
>>> +    };
>>> +  };
>>> +
>>> +  const int j = 0;
>>> +  [=] () {
>>> +    [=] () mutable {
>>> +      ++j; // { dg-error "read-only" }
>>> +    };
>>> +  };
>>> +
>>> +#if __cpp_init_captures
>>> +  [j=0] () {
>>> +    [&] () mutable {
>>> +      ++j; // { dg-error "read-only" }
>>> +    };
>>> +  };
>>> +#endif
>>> +}
>>>
>>
>>
>
  

Patch

diff --git a/gcc/cp/lambda.c b/gcc/cp/lambda.c
index 2e9d38bbe83..f16c121dc78 100644
--- a/gcc/cp/lambda.c
+++ b/gcc/cp/lambda.c
@@ -196,7 +196,9 @@  lambda_capture_field_type (tree expr, bool explicit_init_p,
   tree type;
   bool is_this = is_this_parameter (tree_strip_nop_conversions (expr));
 
-  if (!is_this && explicit_init_p)
+  if (is_this)
+    type = TREE_TYPE (expr);
+  else if (explicit_init_p)
     {
       tree auto_node = make_auto ();
       
@@ -210,7 +212,7 @@  lambda_capture_field_type (tree expr, bool explicit_init_p,
       else
 	type = do_auto_deduction (type, expr, auto_node);
     }
-  else if (!is_this && type_dependent_expression_p (expr))
+  else if (type_dependent_expression_p (expr))
     {
       type = cxx_make_type (DECLTYPE_TYPE);
       DECLTYPE_TYPE_EXPR (type) = expr;
@@ -220,10 +222,19 @@  lambda_capture_field_type (tree expr, bool explicit_init_p,
     }
   else
     {
+      if (!by_reference_p && is_capture_proxy (expr))
+	{
+	  /* When capturing by-value another capture proxy from an enclosing
+	     lambda, consider the type of the corresponding field instead,
+	     as the proxy may be const-qualifed if the enclosing lambda is
+	     non-mutable (PR94376).  */
+	  gcc_assert (TREE_CODE (DECL_VALUE_EXPR (expr)) == COMPONENT_REF);
+	  expr = TREE_OPERAND (DECL_VALUE_EXPR (expr), 1);
+	}
+
       type = non_reference (unlowered_expr_type (expr));
 
-      if (!is_this
-	  && (by_reference_p || TREE_CODE (type) == FUNCTION_TYPE))
+      if (by_reference_p || TREE_CODE (type) == FUNCTION_TYPE)
 	type = build_reference_type (type);
     }
 
diff --git a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-nested9.C b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-nested9.C
new file mode 100644
index 00000000000..ff7da3b0ce1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-nested9.C
@@ -0,0 +1,41 @@ 
+// PR c++/94376
+// { dg-do compile { target c++11 } }
+
+int main() {
+  // We used to incorrectly reject the first two cases.
+  int i = 0;
+  [=] () {
+    [=] () mutable {
+      ++i;
+    };
+  };
+
+#if __cpp_init_captures
+  [j=0] () {
+    [=] () mutable {
+      ++j;
+    };
+  };
+#endif
+
+  [=] () {
+    [&] () mutable {
+      ++i; // { dg-error "read-only" }
+    };
+  };
+
+  const int j = 0;
+  [=] () {
+    [=] () mutable {
+      ++j; // { dg-error "read-only" }
+    };
+  };
+
+#if __cpp_init_captures
+  [j=0] () {
+    [&] () mutable {
+      ++j; // { dg-error "read-only" }
+    };
+  };
+#endif
+}