[v3] c++: Reject default arguments for template class friend functions [PR118319]

Message ID 01020194d1c862e4-f51d3dd8-c5b9-4228-ae55-c2af1293427e-000000@eu-west-1.amazonses.com
State New
Headers
Series [v3] c++: Reject default arguments for template class friend functions [PR118319] |

Checks

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

Commit Message

Simon Martin Feb. 4, 2025, 4:25 p.m. UTC
  Hi Jason,

On 4 Feb 2025, at 1:11, Jason Merrill wrote:

> On 1/31/25 11:12 AM, Simon Martin wrote:
>> Hi Jason,
>>
>> On 31 Jan 2025, at 16:29, Jason Merrill wrote:
>>
>>> On 1/31/25 9:52 AM, Simon Martin wrote:
>>>> Hi Jason,
>>>>
>>>> On 9 Jan 2025, at 22:55, Jason Merrill wrote:
>>>>
>>>>> On 1/9/25 8:25 AM, Simon Martin wrote:
>>>>>> We segfault upon the following invalid code
>>>>>>
>>>>>> === cut here ===
>>>>>> template <int> struct S {
>>>>>>       friend void foo (int a = []{}());
>>>>>> };
>>>>>> void foo (int a) {}
>>>>>> int main () {
>>>>>>       S<0> t;
>>>>>>       foo ();
>>>>>> }
>>>>>> === cut here ===
>>>>>>
>>>>>> The problem is that we end up with a LAMBDA_EXPR callee in
>>>>>> set_flags_from_callee, and dereference its NULL_TREE
>>>>>> TREE_TYPE (TREE_TYPE ( )).
>>>>>>
>>>>>> This patch simply sets the default argument to error_mark_node 

>>>>>> for
>>>>>> friend functions that do not meet the requirement in C++17
>>>>>> 11.3.6/4.
>>>>>>
>>>>>> Successfully tested on x86_64-pc-linux-gnu.
>>>>>>
>>>>>> PR c++/118319
>>>>>>
>>>>>> gcc/cp/ChangeLog:
>>>>>>
>>>>>> * decl.cc (grokfndecl): Inspect all friend function parameters,
>>>>>> and set them to error_mark_node if invalid.
>>>>>>
>>>>>> gcc/testsuite/ChangeLog:
>>>>>>
>>>>>> * g++.dg/parse/defarg18.C: New test.
>>>>>>
>>>>>> ---
>>>>>>      
>>>>>> gcc/cp/decl.cc                        | 13 
>>>>>> +++++---
>>>>>>      gcc/testsuite/g++.dg/parse/defarg18.C | 48
>>>>>> +++++++++++++++++++++++++++
>>>>>>      2 files changed, 57 insertions(+), 4 deletions(-)
>>>>>>      create mode 100644 gcc/testsuite/g++.dg/parse/defarg18.C
>>>>>>
>>>>>> diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
>>>>>> index 503ecd9387e..b2761c23d3e 100644
>>>>>> --- a/gcc/cp/decl.cc
>>>>>> +++ b/gcc/cp/decl.cc
>>>>>> @@ -11134,14 +11134,19 @@ grokfndecl (tree ctype,
>>>>>>           expression, that declaration shall be a 
>>>>>> definition..."  */
>>>>>>        if (friendp && !funcdef_flag)
>>>>>>          {
>>>>>> +      bool has_permerrored = false;
>>>>>>            for (tree t = FUNCTION_FIRST_USER_PARMTYPE 
>>>>>> (decl);
>>>>>>         t && t != void_list_node; t = TREE_CHAIN (t))
>>>>>>      if (TREE_PURPOSE (t))
>>>>>>        {
>>>>>> -     permerror (DECL_SOURCE_LOCATION (decl),
>>>>>> -        "friend declaration of %qD specifies default "
>>>>>> -        "arguments and isn%'t a definition", decl);
>>>>>> -     break;
>>>>>> +     if (!has_permerrored)
>>>>>> +       {
>>>>>> + has_permerrored = true;
>>>>>> + permerror (DECL_SOURCE_LOCATION (decl),
>>>>>> +    "friend declaration of %qD specifies default "
>>>>>> +    "arguments and isn%'t a definition", decl);
>>>>>> +       }
>>>>>> +     TREE_PURPOSE (t) = error_mark_node;
>>>>>
>>>>> If we're going to unconditionally change TREE_PURPOSE, then
>>>>> permerror
>>>>> needs to strengthen to error.  But I'd think we could leave the
>>>>> current state in a non-template class, only changing the template
>>>>> case.
>>>> Thanks. It’s true that setting the argument to error_mark_node is
>>>> contradictory with the fact that we accept the code with
>>>> -fpermissive,
>>>> even if only under processing_template_decl, so I checked if
>>>> there’s
>>>> not a better way of approaching this PR.
>>>>
>>>> After a bit of investigation, I think that the real problem is that

>>>> duplicate_decls tries to merge the two declarations, even though 

>>>> they
>>>> don’t meet the constraint about friend functions and default
>>>> arguments.
>>>
>>> I disagree; in this testcase the friend is the (lexically) first
>>> declaration, the problem is that it's a non-defining friend (in a
>>> template) that specifies default args, as addressed by your first
>>> patch.
>> Fair.
>>
>>> I still think my earlier comments are the way forward here: leave 

>>> the
>>> non-template case alone (permerror, don't change TREE_PURPOSE), in a

>>> template give a hard error and change to error_mark_node.
>> Thanks, understood. The reason I looked for another “solution” is
>> that it felt strange to be permissive in non-templates and stricter 
>> in
>> templates. For example, if we do so, we’ll regress the case I added 
>> in
>> defarg19.C in -fpermissive (also available at
>> https://godbolt.org/z/YT3dexGjM).
>>
>> I’m probably splitting hair, and I’m happy to go ahead with your
>> suggestion if you think it’s fine. Otherwise I’ll see if I find 
>> some
>> better fix.
>
> That's fine, it's common to be stricter in templates.
Ok, cool. The attached updated patch does this, and has been 
successfully tested on x86_64-pc-linux-gnu. OK for trunk?

Thanks, Simon
From bfa8949814b15d1c143b0928e8fbb7741bb93ac3 Mon Sep 17 00:00:00 2001
From: Simon Martin <simon@nasilyan.com>
Date: Tue, 4 Feb 2025 11:17:35 +0100
Subject: [PATCH] c++: Reject default arguments for template class friend
 functions [PR118319]

We segfault upon the following invalid code

=== cut here ===
template <int> struct S {
  friend void foo (int a = []{}());
};
void foo (int a) {}
int main () {
  S<0> t;
  foo ();
}
=== cut here ===

The problem is that we end up with a LAMBDA_EXPR callee in
set_flags_from_callee, and dereference its NULL_TREE
TREE_TYPE (TREE_TYPE (..)).

This patch sets the default argument to error_mark_node for template
class friend functions that do not meet the requirement in C++17
11.3.6/4 (the change is restricted to templates per discussion with
Jason).

Successfully tested on x86_64-pc-linux-gnu.

	PR c++/118319

gcc/cp/ChangeLog:

	* decl.cc (grokfndecl): Inspect all friend function parameters.
	Set their default value to error_mark_node if processing a
	template and it's not valid for them to have any.

gcc/testsuite/ChangeLog:

	* g++.dg/parse/defarg18.C: New test.

---
 gcc/cp/decl.cc                        | 14 +++++---
 gcc/testsuite/g++.dg/parse/defarg18.C | 48 +++++++++++++++++++++++++++
 2 files changed, 58 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/parse/defarg18.C

-- 
2.44.0
  

Comments

Jason Merrill Feb. 4, 2025, 8:06 p.m. UTC | #1
On 2/4/25 11:25 AM, Simon Martin wrote:
> Hi Jason,
> 
> On 4 Feb 2025, at 1:11, Jason Merrill wrote:
> 
>> On 1/31/25 11:12 AM, Simon Martin wrote:
>>> Hi Jason,
>>>
>>> On 31 Jan 2025, at 16:29, Jason Merrill wrote:
>>>
>>>> On 1/31/25 9:52 AM, Simon Martin wrote:
>>>>> Hi Jason,
>>>>>
>>>>> On 9 Jan 2025, at 22:55, Jason Merrill wrote:
>>>>>
>>>>>> On 1/9/25 8:25 AM, Simon Martin wrote:
>>>>>>> We segfault upon the following invalid code
>>>>>>>
>>>>>>> === cut here ===
>>>>>>> template <int> struct S {
>>>>>>>        friend void foo (int a = []{}());
>>>>>>> };
>>>>>>> void foo (int a) {}
>>>>>>> int main () {
>>>>>>>        S<0> t;
>>>>>>>        foo ();
>>>>>>> }
>>>>>>> === cut here ===
>>>>>>>
>>>>>>> The problem is that we end up with a LAMBDA_EXPR callee in
>>>>>>> set_flags_from_callee, and dereference its NULL_TREE
>>>>>>> TREE_TYPE (TREE_TYPE ( )).
>>>>>>>
>>>>>>> This patch simply sets the default argument to error_mark_node
> 
>>>>>>> for
>>>>>>> friend functions that do not meet the requirement in C++17
>>>>>>> 11.3.6/4.
>>>>>>>
>>>>>>> Successfully tested on x86_64-pc-linux-gnu.
>>>>>>>
>>>>>>> PR c++/118319
>>>>>>>
>>>>>>> gcc/cp/ChangeLog:
>>>>>>>
>>>>>>> * decl.cc (grokfndecl): Inspect all friend function parameters,
>>>>>>> and set them to error_mark_node if invalid.
>>>>>>>
>>>>>>> gcc/testsuite/ChangeLog:
>>>>>>>
>>>>>>> * g++.dg/parse/defarg18.C: New test.
>>>>>>>
>>>>>>> ---
>>>>>>>       
>>>>>>> gcc/cp/decl.cc                        | 13
>>>>>>> +++++---
>>>>>>>       gcc/testsuite/g++.dg/parse/defarg18.C | 48
>>>>>>> +++++++++++++++++++++++++++
>>>>>>>       2 files changed, 57 insertions(+), 4 deletions(-)
>>>>>>>       create mode 100644 gcc/testsuite/g++.dg/parse/defarg18.C
>>>>>>>
>>>>>>> diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
>>>>>>> index 503ecd9387e..b2761c23d3e 100644
>>>>>>> --- a/gcc/cp/decl.cc
>>>>>>> +++ b/gcc/cp/decl.cc
>>>>>>> @@ -11134,14 +11134,19 @@ grokfndecl (tree ctype,
>>>>>>>            expression, that declaration shall be a
>>>>>>> definition..."  */
>>>>>>>         if (friendp && !funcdef_flag)
>>>>>>>           {
>>>>>>> +      bool has_permerrored = false;
>>>>>>>             for (tree t = FUNCTION_FIRST_USER_PARMTYPE
>>>>>>> (decl);
>>>>>>>          t && t != void_list_node; t = TREE_CHAIN (t))
>>>>>>>       if (TREE_PURPOSE (t))
>>>>>>>         {
>>>>>>> -     permerror (DECL_SOURCE_LOCATION (decl),
>>>>>>> -        "friend declaration of %qD specifies default "
>>>>>>> -        "arguments and isn%'t a definition", decl);
>>>>>>> -     break;
>>>>>>> +     if (!has_permerrored)
>>>>>>> +       {
>>>>>>> + has_permerrored = true;
>>>>>>> + permerror (DECL_SOURCE_LOCATION (decl),
>>>>>>> +    "friend declaration of %qD specifies default "
>>>>>>> +    "arguments and isn%'t a definition", decl);
>>>>>>> +       }
>>>>>>> +     TREE_PURPOSE (t) = error_mark_node;
>>>>>>
>>>>>> If we're going to unconditionally change TREE_PURPOSE, then
>>>>>> permerror
>>>>>> needs to strengthen to error.  But I'd think we could leave the
>>>>>> current state in a non-template class, only changing the template
>>>>>> case.
>>>>> Thanks. It’s true that setting the argument to error_mark_node is
>>>>> contradictory with the fact that we accept the code with
>>>>> -fpermissive,
>>>>> even if only under processing_template_decl, so I checked if
>>>>> there’s
>>>>> not a better way of approaching this PR.
>>>>>
>>>>> After a bit of investigation, I think that the real problem is that
> 
>>>>> duplicate_decls tries to merge the two declarations, even though
> 
>>>>> they
>>>>> don’t meet the constraint about friend functions and default
>>>>> arguments.
>>>>
>>>> I disagree; in this testcase the friend is the (lexically) first
>>>> declaration, the problem is that it's a non-defining friend (in a
>>>> template) that specifies default args, as addressed by your first
>>>> patch.
>>> Fair.
>>>
>>>> I still think my earlier comments are the way forward here: leave
> 
>>>> the
>>>> non-template case alone (permerror, don't change TREE_PURPOSE), in a
> 
>>>> template give a hard error and change to error_mark_node.
>>> Thanks, understood. The reason I looked for another “solution” is
>>> that it felt strange to be permissive in non-templates and stricter
>>> in
>>> templates. For example, if we do so, we’ll regress the case I added
>>> in
>>> defarg19.C in -fpermissive (also available at
>>> https://godbolt.org/z/YT3dexGjM).
>>>
>>> I’m probably splitting hair, and I’m happy to go ahead with your
>>> suggestion if you think it’s fine. Otherwise I’ll see if I find
>>> some
>>> better fix.
>>
>> That's fine, it's common to be stricter in templates.
> Ok, cool. The attached updated patch does this, and has been
> successfully tested on x86_64-pc-linux-gnu. OK for trunk?

> +	    if (!has_permerrored)
> +	      {
> +		has_permerrored = true;
> +		permerror (DECL_SOURCE_LOCATION (decl),

This still needs to be a hard error in a template; you probably want to 
use emit_diagnostic to allow the diagnostic category to be different.

> +			   "friend declaration of %qD specifies default "
> +			   "arguments and isn%'t a definition", decl);
> +	      }
> +	    if (processing_template_decl)
> +	      TREE_PURPOSE (t) = error_mark_node;
  

Patch

diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index b7af33b3231..d9c79e6f620 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -11213,14 +11213,20 @@  grokfndecl (tree ctype,
      expression, that declaration shall be a definition..."  */
   if (friendp && !funcdef_flag)
     {
+      bool has_permerrored = false;
       for (tree t = FUNCTION_FIRST_USER_PARMTYPE (decl);
 	   t && t != void_list_node; t = TREE_CHAIN (t))
 	if (TREE_PURPOSE (t))
 	  {
-	    permerror (DECL_SOURCE_LOCATION (decl),
-		       "friend declaration of %qD specifies default "
-		       "arguments and isn%'t a definition", decl);
-	    break;
+	    if (!has_permerrored)
+	      {
+		has_permerrored = true;
+		permerror (DECL_SOURCE_LOCATION (decl),
+			   "friend declaration of %qD specifies default "
+			   "arguments and isn%'t a definition", decl);
+	      }
+	    if (processing_template_decl)
+	      TREE_PURPOSE (t) = error_mark_node;
 	  }
     }
 
diff --git a/gcc/testsuite/g++.dg/parse/defarg18.C b/gcc/testsuite/g++.dg/parse/defarg18.C
new file mode 100644
index 00000000000..62c8f15f284
--- /dev/null
+++ b/gcc/testsuite/g++.dg/parse/defarg18.C
@@ -0,0 +1,48 @@ 
+// PR c++/118319
+// { dg-do "compile" { target c++11 } }
+
+// Template case, that used to crash.
+
+template <int>
+struct S {
+  friend void foo1 (int a = []{}());  // { dg-error "specifies default|only declaration" }
+  friend void foo3 (int a,	      // { dg-error "specifies default|only declaration" }
+		    int b = []{}(),
+		    int c = []{}());
+};
+
+void foo1 (int a) {}
+void foo3 (int a, int b, int c) {}
+
+void hello (){
+  S<0> t;
+  foo1 ();
+  foo3 (1, 2);
+}
+
+
+// Template case, that already worked.
+
+template <int>
+struct T {
+  friend void bar (int a = []{}()); // { dg-error "specifies default|only declaration" }
+};
+
+void hallo (){
+  T<0> t;
+  bar (); // { dg-error "not declared" }
+}
+
+
+// Non template case, that already worked.
+
+struct NoTemplate {
+  friend void baz (int a = []{}()); // { dg-error "specifies default|could not convert" }
+};
+
+void baz (int a) {} // { dg-error "only declaration" }
+
+void ola (){
+  NoTemplate t;
+  baz (); // { dg-error "void value not ignored" }
+}