diff mbox series

warn-access: ignore template parameters when matching operator new/delete [PR109224]

Message ID 20240802211503.3992610-2-arsen@aarsen.me
State New
Headers show
Series warn-access: ignore template parameters when matching operator new/delete [PR109224] | expand

Commit Message

Arsen Arsenović Aug. 2, 2024, 8:36 p.m. UTC
I'm not 100% clear on what the semantics of the matching here are meant
to be - AFAICT, an operator new/delete pair matches (after falling
through the other cases) if all their components (besides the actual
operator name, of course) match, and the pair of actual operator names
matches if one is a singleton new and the other a singleton delete, or,
similarly, if one is an array new and the other an array delete.  We
also appear to ignore their argument types (or so it seems by
experimentation - I was not able to quite discern what path those take).

Stripping operator template arguments from either side of the pair
should have no impact on this logic, I think.

Tested on x86_64-pc-linux-gnu, no regressions.

OK for trunk?

TIA, have a lovely evening.
---------- >8 ----------
Template parameters on a member operator new cannot affect its member
status nor whether it is a singleton or array operator new, hence, we
can ignore it for purposes of matching.  Similar logic applies to the
placement operator delete.

In the PR (and a lot of idiomatic coroutine code generally), operator
new is templated in order to be able to inspect (some of) the arguments
passed to the coroutine, to make allocation-related decisions.  However,
the coroutine implementation will not call a placement delete form, so
it cannot get templated.  As a result, when demangling, we have an extra
template DEMANGLE_COMPONENT_TEMPLATE around the actual operator new, but
not operator delete.  This terminates new_delete_mismatch_p early.

PR middle-end/109224 - Wmismatched-new-delete false positive with a templated operator new (common with coroutines)

gcc/ChangeLog:

	PR middle-end/109224
	* gimple-ssa-warn-access.cc (new_delete_mismatch_p): Strip
	DEMANGLE_COMPONENT_TEMPLATE from the operator new and operator
	after demangling.

gcc/testsuite/ChangeLog:

	PR middle-end/109224
	* g++.dg/warn/Wmismatched-new-delete-9.C: New test.
---
 gcc/gimple-ssa-warn-access.cc                 | 18 ++++++-
 .../g++.dg/warn/Wmismatched-new-delete-9.C    | 47 +++++++++++++++++++
 gcc/testsuite/g++.dg/warn/pr109224.C          | 25 ++++++++++
 3 files changed, 89 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C
 create mode 100644 gcc/testsuite/g++.dg/warn/pr109224.C

Comments

Jason Merrill Aug. 21, 2024, 8:20 p.m. UTC | #1
On 8/2/24 4:36 PM, Arsen Arsenović wrote:
> I'm not 100% clear on what the semantics of the matching here are meant
> to be - AFAICT, an operator new/delete pair matches (after falling
> through the other cases) if all their components (besides the actual
> operator name, of course) match, and the pair of actual operator names
> matches if one is a singleton new and the other a singleton delete, or,
> similarly, if one is an array new and the other an array delete.  We
> also appear to ignore their argument types (or so it seems by
> experimentation - I was not able to quite discern what path those take).

Ignoring argument types is correct, placement delete is only called for 
exception handling.

> Stripping operator template arguments from either side of the pair
> should have no impact on this logic, I think.

Agreed.

> Tested on x86_64-pc-linux-gnu, no regressions.
> 
> OK for trunk?
> 
> TIA, have a lovely evening.
> ---------- >8 ----------
> Template parameters on a member operator new cannot affect its member
> status nor whether it is a singleton or array operator new, hence, we
> can ignore it for purposes of matching.  Similar logic applies to the
> placement operator delete.
> 
> In the PR (and a lot of idiomatic coroutine code generally), operator
> new is templated in order to be able to inspect (some of) the arguments
> passed to the coroutine, to make allocation-related decisions.  However,
> the coroutine implementation will not call a placement delete form, so
> it cannot get templated.  As a result, when demangling, we have an extra
> template DEMANGLE_COMPONENT_TEMPLATE around the actual operator new, but
> not operator delete.  This terminates new_delete_mismatch_p early.
> 
> PR middle-end/109224 - Wmismatched-new-delete false positive with a templated operator new (common with coroutines)
> 
> gcc/ChangeLog:
> 
> 	PR middle-end/109224
> 	* gimple-ssa-warn-access.cc (new_delete_mismatch_p): Strip
> 	DEMANGLE_COMPONENT_TEMPLATE from the operator new and operator
> 	after demangling.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR middle-end/109224
> 	* g++.dg/warn/Wmismatched-new-delete-9.C: New test.
> ---
>   gcc/gimple-ssa-warn-access.cc                 | 18 ++++++-
>   .../g++.dg/warn/Wmismatched-new-delete-9.C    | 47 +++++++++++++++++++
>   gcc/testsuite/g++.dg/warn/pr109224.C          | 25 ++++++++++
>   3 files changed, 89 insertions(+), 1 deletion(-)
>   create mode 100644 gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C
>   create mode 100644 gcc/testsuite/g++.dg/warn/pr109224.C
> 
> diff --git a/gcc/gimple-ssa-warn-access.cc b/gcc/gimple-ssa-warn-access.cc
> index 61f9f0f3d310..e3fec5fb8e77 100644
> --- a/gcc/gimple-ssa-warn-access.cc
> +++ b/gcc/gimple-ssa-warn-access.cc
> @@ -1762,7 +1762,23 @@ new_delete_mismatch_p (tree new_decl, tree delete_decl)
>     void *np = NULL, *dp = NULL;
>     demangle_component *ndc = cplus_demangle_v3_components (new_str, 0, &np);
>     demangle_component *ddc = cplus_demangle_v3_components (del_str, 0, &dp);
> -  bool mismatch = ndc && ddc && new_delete_mismatch_p (*ndc, *ddc);
> +
> +  /* Sometimes, notably quite often with coroutines, 'operator new' is
> +     templated.  However, template arguments can't change whether a given
> +     new/delete is a singleton or array one, nor what it is a member of, so
> +     the template arguments can be safely ignored for the purposes of checking
> +     for mismatches.   */
> +
> +  auto strip_dc_template = [] (demangle_component* dc)
> +  {
> +    if (dc->type == DEMANGLE_COMPONENT_TEMPLATE)
> +      dc = dc->u.s_binary.left;
> +    return dc;
> +  };
> +
> +  bool mismatch = ndc && ddc
> +    && new_delete_mismatch_p (*strip_dc_template (ndc),
> +			      *strip_dc_template (ddc));

The && should not be left of the =; if the initializer needs to span 
multiple lines, it's usually best to wrap it in ().

>     free (np);
>     free (dp);
>     return mismatch;
> diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C
> new file mode 100644
> index 000000000000..fa511bbfdb4b
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C
> @@ -0,0 +1,47 @@
> +/* { dg-do compile { target c++11 } } */
> +/* { dg-additional-options "-Wmismatched-new-delete" } */
> +/* PR middle-end/109224 */
> +/* Verify that we consider templated operator new matching with its operator
> +   delete.  */
> +
> +#include <new>
> +
> +struct foo
> +{
> +  template<typename... Args>
> +  void* operator new (std::size_t sz, Args&&...);
> +  template<typename... Args>
> +  void* operator new[] (std::size_t sz, Args&&...);
> +
> +  void operator delete (void* x);
> +  void operator delete[] (void* x);
> +
> +  template<typename... Args>
> +  void operator delete (void* x, Args&&...);
> +  template<typename... Args>
> +  void operator delete[] (void* x, Args&&...);
> +};
> +
> +void
> +f ()
> +{
> +  delete (new (123, true) foo);
> +  delete[] (new (123, true) foo[123]);
> +
> +  delete (new (123, true) foo[123]);
> +  // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
> +  // { dg-note "returned from" "" { target *-*-* } {.-2} }
> +  delete[] (new (123, true) foo);
> +  // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
> +  // { dg-note "returned from" "" { target *-*-* } {.-2} }
> +
> +  foo::operator delete (new (123, true) foo, 123, true);
> +  foo::operator delete[] (new (123, true) foo[123], 123, true);

Instead of passing the result of a new-expression directly to operator 
delete, you should also call operator new directly.

> +  foo::operator delete (new (123, true) foo[123], 123, true);
> +  // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
> +  // { dg-note "returned from" "" { target *-*-* } {.-2} }
> +  foo::operator delete[] (new (123, true) foo, 123, true);

Likewise.

Jason
Arsen Arsenović Aug. 21, 2024, 9:32 p.m. UTC | #2
Jason Merrill <jason@redhat.com> writes:

> On 8/2/24 4:36 PM, Arsen Arsenović wrote:
>> I'm not 100% clear on what the semantics of the matching here are meant
>> to be - AFAICT, an operator new/delete pair matches (after falling
>> through the other cases) if all their components (besides the actual
>> operator name, of course) match, and the pair of actual operator names
>> matches if one is a singleton new and the other a singleton delete, or,
>> similarly, if one is an array new and the other an array delete.  We
>> also appear to ignore their argument types (or so it seems by
>> experimentation - I was not able to quite discern what path those take).
>
> Ignoring argument types is correct, placement delete is only called for
> exception handling.

ACK - good to know.

>> Stripping operator template arguments from either side of the pair
>> should have no impact on this logic, I think.
>
> Agreed.
>
>> Tested on x86_64-pc-linux-gnu, no regressions.
>> OK for trunk?
>> TIA, have a lovely evening.
>> ---------- >8 ----------
>> Template parameters on a member operator new cannot affect its member
>> status nor whether it is a singleton or array operator new, hence, we
>> can ignore it for purposes of matching.  Similar logic applies to the
>> placement operator delete.
>> In the PR (and a lot of idiomatic coroutine code generally), operator
>> new is templated in order to be able to inspect (some of) the arguments
>> passed to the coroutine, to make allocation-related decisions.  However,
>> the coroutine implementation will not call a placement delete form, so
>> it cannot get templated.  As a result, when demangling, we have an extra
>> template DEMANGLE_COMPONENT_TEMPLATE around the actual operator new, but
>> not operator delete.  This terminates new_delete_mismatch_p early.
>> PR middle-end/109224 - Wmismatched-new-delete false positive with a templated
>> operator new (common with coroutines)
>> gcc/ChangeLog:
>> 	PR middle-end/109224
>> 	* gimple-ssa-warn-access.cc (new_delete_mismatch_p): Strip
>> 	DEMANGLE_COMPONENT_TEMPLATE from the operator new and operator
>> 	after demangling.
>> gcc/testsuite/ChangeLog:
>> 	PR middle-end/109224
>> 	* g++.dg/warn/Wmismatched-new-delete-9.C: New test.
>> ---
>>   gcc/gimple-ssa-warn-access.cc                 | 18 ++++++-
>>   .../g++.dg/warn/Wmismatched-new-delete-9.C    | 47 +++++++++++++++++++
>>   gcc/testsuite/g++.dg/warn/pr109224.C          | 25 ++++++++++
>>   3 files changed, 89 insertions(+), 1 deletion(-)
>>   create mode 100644 gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C
>>   create mode 100644 gcc/testsuite/g++.dg/warn/pr109224.C
>> diff --git a/gcc/gimple-ssa-warn-access.cc b/gcc/gimple-ssa-warn-access.cc
>> index 61f9f0f3d310..e3fec5fb8e77 100644
>> --- a/gcc/gimple-ssa-warn-access.cc
>> +++ b/gcc/gimple-ssa-warn-access.cc
>> @@ -1762,7 +1762,23 @@ new_delete_mismatch_p (tree new_decl, tree delete_decl)
>>     void *np = NULL, *dp = NULL;
>>     demangle_component *ndc = cplus_demangle_v3_components (new_str, 0, &np);
>>     demangle_component *ddc = cplus_demangle_v3_components (del_str, 0, &dp);
>> -  bool mismatch = ndc && ddc && new_delete_mismatch_p (*ndc, *ddc);
>> +
>> +  /* Sometimes, notably quite often with coroutines, 'operator new' is
>> +     templated.  However, template arguments can't change whether a given
>> +     new/delete is a singleton or array one, nor what it is a member of, so
>> +     the template arguments can be safely ignored for the purposes of checking
>> +     for mismatches.   */
>> +
>> +  auto strip_dc_template = [] (demangle_component* dc)
>> +  {
>> +    if (dc->type == DEMANGLE_COMPONENT_TEMPLATE)
>> +      dc = dc->u.s_binary.left;
>> +    return dc;
>> +  };
>> +
>> +  bool mismatch = ndc && ddc
>> +    && new_delete_mismatch_p (*strip_dc_template (ndc),
>> +			      *strip_dc_template (ddc));
>
> The && should not be left of the =; if the initializer needs to span multiple
> lines, it's usually best to wrap it in ().

Okay - done.

>>     free (np);
>>     free (dp);
>>     return mismatch;
>> diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C
>> new file mode 100644
>> index 000000000000..fa511bbfdb4b
>> --- /dev/null
>> +++ b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C
>> @@ -0,0 +1,47 @@
>> +/* { dg-do compile { target c++11 } } */
>> +/* { dg-additional-options "-Wmismatched-new-delete" } */
>> +/* PR middle-end/109224 */
>> +/* Verify that we consider templated operator new matching with its operator
>> +   delete.  */
>> +
>> +#include <new>
>> +
>> +struct foo
>> +{
>> +  template<typename... Args>
>> +  void* operator new (std::size_t sz, Args&&...);
>> +  template<typename... Args>
>> +  void* operator new[] (std::size_t sz, Args&&...);
>> +
>> +  void operator delete (void* x);
>> +  void operator delete[] (void* x);
>> +
>> +  template<typename... Args>
>> +  void operator delete (void* x, Args&&...);
>> +  template<typename... Args>
>> +  void operator delete[] (void* x, Args&&...);
>> +};
>> +
>> +void
>> +f ()
>> +{
>> +  delete (new (123, true) foo);
>> +  delete[] (new (123, true) foo[123]);
>> +
>> +  delete (new (123, true) foo[123]);
>> +  // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
>> +  // { dg-note "returned from" "" { target *-*-* } {.-2} }
>> +  delete[] (new (123, true) foo);
>> +  // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
>> +  // { dg-note "returned from" "" { target *-*-* } {.-2} }
>> +
>> +  foo::operator delete (new (123, true) foo, 123, true);
>> +  foo::operator delete[] (new (123, true) foo[123], 123, true);
>
> Instead of passing the result of a new-expression directly to operator delete,
> you should also call operator new directly.
>
>> +  foo::operator delete (new (123, true) foo[123], 123, true);
>> +  // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
>> +  // { dg-note "returned from" "" { target *-*-* } {.-2} }
>> +  foo::operator delete[] (new (123, true) foo, 123, true);
>
> Likewise.

Ah, good point - adjusted.

Here are the changes to v1 I've made as well as the full patch below
them.  Does this look OK?

--8<---------------cut here---------------start------------->8---
modified   gcc/gimple-ssa-warn-access.cc
@@ -1776,9 +1776,9 @@ new_delete_mismatch_p (tree new_decl, tree delete_decl)
     return dc;
   };
 
-  bool mismatch = ndc && ddc
-    && new_delete_mismatch_p (*strip_dc_template (ndc),
-			      *strip_dc_template (ddc));
+  bool mismatch = (ndc && ddc
+		   && new_delete_mismatch_p (*strip_dc_template (ndc),
+					     *strip_dc_template (ddc)));
   free (np);
   free (dp);
   return mismatch;
modified   gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C
@@ -35,13 +35,13 @@ f ()
   // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
   // { dg-note "returned from" "" { target *-*-* } {.-2} }
 
-  foo::operator delete (new (123, true) foo, 123, true);
-  foo::operator delete[] (new (123, true) foo[123], 123, true);
+  foo::operator delete (foo::operator new (1, 123, true), 123, true);
+  foo::operator delete[] (foo::operator new[] (123, 123, true), 123, true);
 
-  foo::operator delete (new (123, true) foo[123], 123, true);
+  foo::operator delete (foo::operator new[] (123, 123, true), 123, true);
   // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
   // { dg-note "returned from" "" { target *-*-* } {.-2} }
-  foo::operator delete[] (new (123, true) foo, 123, true);
+  foo::operator delete[] (foo::operator new (1, 123, true), 123, true);
   // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
   // { dg-note "returned from" "" { target *-*-* } {.-2} }
 }
--8<---------------cut here---------------end--------------->8---

---------- >8 ----------
Template parameters on a member operator new cannot affect its member
status nor whether it is a singleton or array operator new, hence, we
can ignore it for purposes of matching.  Similar logic applies to the
placement operator delete.

In the PR (and a lot of idiomatic coroutine code generally), operator
new is templated in order to be able to inspect (some of) the arguments
passed to the coroutine, to make allocation-related decisions.  However,
the coroutine implementation will not call a placement delete form, so
it cannot get templated.  As a result, when demangling, we have an extra
template DEMANGLE_COMPONENT_TEMPLATE around the actual operator new, but
not operator delete.  This terminates new_delete_mismatch_p early.

	PR middle-end/109224 - Wmismatched-new-delete false positive with a templated operator new (common with coroutines)

gcc/ChangeLog:

	* gimple-ssa-warn-access.cc (new_delete_mismatch_p): Strip
	DEMANGLE_COMPONENT_TEMPLATE from the operator new and operator
	after demangling.

gcc/testsuite/ChangeLog:

	* g++.dg/warn/Wmismatched-new-delete-9.C: New test.
---
 gcc/gimple-ssa-warn-access.cc                 | 18 ++++++-
 .../g++.dg/warn/Wmismatched-new-delete-9.C    | 47 +++++++++++++++++++
 gcc/testsuite/g++.dg/warn/pr109224.C          | 25 ++++++++++
 3 files changed, 89 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C
 create mode 100644 gcc/testsuite/g++.dg/warn/pr109224.C

diff --git a/gcc/gimple-ssa-warn-access.cc b/gcc/gimple-ssa-warn-access.cc
index 61f9f0f3d310..fcce63ee332d 100644
--- a/gcc/gimple-ssa-warn-access.cc
+++ b/gcc/gimple-ssa-warn-access.cc
@@ -1762,7 +1762,23 @@ new_delete_mismatch_p (tree new_decl, tree delete_decl)
   void *np = NULL, *dp = NULL;
   demangle_component *ndc = cplus_demangle_v3_components (new_str, 0, &np);
   demangle_component *ddc = cplus_demangle_v3_components (del_str, 0, &dp);
-  bool mismatch = ndc && ddc && new_delete_mismatch_p (*ndc, *ddc);
+
+  /* Sometimes, notably quite often with coroutines, 'operator new' is
+     templated.  However, template arguments can't change whether a given
+     new/delete is a singleton or array one, nor what it is a member of, so
+     the template arguments can be safely ignored for the purposes of checking
+     for mismatches.   */
+
+  auto strip_dc_template = [] (demangle_component* dc)
+  {
+    if (dc->type == DEMANGLE_COMPONENT_TEMPLATE)
+      dc = dc->u.s_binary.left;
+    return dc;
+  };
+
+  bool mismatch = (ndc && ddc
+		   && new_delete_mismatch_p (*strip_dc_template (ndc),
+					     *strip_dc_template (ddc)));
   free (np);
   free (dp);
   return mismatch;
diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C
new file mode 100644
index 000000000000..d431f4049e87
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C
@@ -0,0 +1,47 @@
+/* { dg-do compile { target c++11 } } */
+/* { dg-additional-options "-Wmismatched-new-delete" } */
+/* PR middle-end/109224 */
+/* Verify that we consider templated operator new matching with its operator
+   delete.  */
+
+#include <new>
+
+struct foo
+{
+  template<typename... Args>
+  void* operator new (std::size_t sz, Args&&...);
+  template<typename... Args>
+  void* operator new[] (std::size_t sz, Args&&...);
+
+  void operator delete (void* x);
+  void operator delete[] (void* x);
+
+  template<typename... Args>
+  void operator delete (void* x, Args&&...);
+  template<typename... Args>
+  void operator delete[] (void* x, Args&&...);
+};
+
+void
+f ()
+{
+  delete (new (123, true) foo);
+  delete[] (new (123, true) foo[123]);
+
+  delete (new (123, true) foo[123]);
+  // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
+  // { dg-note "returned from" "" { target *-*-* } {.-2} }
+  delete[] (new (123, true) foo);
+  // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
+  // { dg-note "returned from" "" { target *-*-* } {.-2} }
+
+  foo::operator delete (foo::operator new (1, 123, true), 123, true);
+  foo::operator delete[] (foo::operator new[] (123, 123, true), 123, true);
+
+  foo::operator delete (foo::operator new[] (123, 123, true), 123, true);
+  // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
+  // { dg-note "returned from" "" { target *-*-* } {.-2} }
+  foo::operator delete[] (foo::operator new (1, 123, true), 123, true);
+  // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
+  // { dg-note "returned from" "" { target *-*-* } {.-2} }
+}
diff --git a/gcc/testsuite/g++.dg/warn/pr109224.C b/gcc/testsuite/g++.dg/warn/pr109224.C
new file mode 100644
index 000000000000..4b6102226ffc
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/pr109224.C
@@ -0,0 +1,25 @@
+// { dg-do compile { target c++20 } }
+#include <coroutine>
+
+struct Task {
+    struct promise_type {
+        std::suspend_never initial_suspend() { return {}; }
+        std::suspend_never final_suspend() noexcept { return {}; }
+        void unhandled_exception() { throw; }
+        Task get_return_object() { return {}; }
+        void return_void() {}
+
+        template<class I>
+        void* operator new(std::size_t sz, I);
+
+        void operator delete(void* ptr, std::size_t);
+    };
+};
+
+Task f(int) {
+    co_return;
+}
+
+int main() {
+    f(42);
+}
Arsen Arsenović Aug. 28, 2024, 9 p.m. UTC | #3
Hi,

Arsen Arsenović <arsen@aarsen.me> writes:

>> The && should not be left of the =; if the initializer needs to span multiple
>> lines, it's usually best to wrap it in ().
>
> Okay - done.

[...]

>>> +  foo::operator delete (new (123, true) foo, 123, true);
>>> +  foo::operator delete[] (new (123, true) foo[123], 123, true);
>>
>> Instead of passing the result of a new-expression directly to operator delete,
>> you should also call operator new directly.
>>
>>> +  foo::operator delete (new (123, true) foo[123], 123, true);
>>> +  // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
>>> +  // { dg-note "returned from" "" { target *-*-* } {.-2} }
>>> +  foo::operator delete[] (new (123, true) foo, 123, true);
>>
>> Likewise.
>
> Ah, good point - adjusted.
>
> Here are the changes to v1 I've made as well as the full patch below
> them.  Does this look OK?
>
> --8<---------------cut here---------------start------------->8---
> modified   gcc/gimple-ssa-warn-access.cc
> @@ -1776,9 +1776,9 @@ new_delete_mismatch_p (tree new_decl, tree delete_decl)
>      return dc;
>    };
>  
> -  bool mismatch = ndc && ddc
> -    && new_delete_mismatch_p (*strip_dc_template (ndc),
> -			      *strip_dc_template (ddc));
> +  bool mismatch = (ndc && ddc
> +		   && new_delete_mismatch_p (*strip_dc_template (ndc),
> +					     *strip_dc_template (ddc)));
>    free (np);
>    free (dp);
>    return mismatch;
> modified   gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C
> @@ -35,13 +35,13 @@ f ()
>    // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
>    // { dg-note "returned from" "" { target *-*-* } {.-2} }
>  
> -  foo::operator delete (new (123, true) foo, 123, true);
> -  foo::operator delete[] (new (123, true) foo[123], 123, true);
> +  foo::operator delete (foo::operator new (1, 123, true), 123, true);
> +  foo::operator delete[] (foo::operator new[] (123, 123, true), 123, true);
>  
> -  foo::operator delete (new (123, true) foo[123], 123, true);
> +  foo::operator delete (foo::operator new[] (123, 123, true), 123, true);
>    // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
>    // { dg-note "returned from" "" { target *-*-* } {.-2} }
> -  foo::operator delete[] (new (123, true) foo, 123, true);
> +  foo::operator delete[] (foo::operator new (1, 123, true), 123, true);
>    // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
>    // { dg-note "returned from" "" { target *-*-* } {.-2} }
>  }
> --8<---------------cut here---------------end--------------->8---

Gentle ping on this adjustment to the patch (full reproduction of the
adjusted patch is below).

TIA, have a lovely evening.

> ---------- >8 ----------
> Template parameters on a member operator new cannot affect its member
> status nor whether it is a singleton or array operator new, hence, we
> can ignore it for purposes of matching.  Similar logic applies to the
> placement operator delete.
>
> In the PR (and a lot of idiomatic coroutine code generally), operator
> new is templated in order to be able to inspect (some of) the arguments
> passed to the coroutine, to make allocation-related decisions.  However,
> the coroutine implementation will not call a placement delete form, so
> it cannot get templated.  As a result, when demangling, we have an extra
> template DEMANGLE_COMPONENT_TEMPLATE around the actual operator new, but
> not operator delete.  This terminates new_delete_mismatch_p early.
>
> 	PR middle-end/109224 - Wmismatched-new-delete false positive with a templated operator new (common with coroutines)
>
> gcc/ChangeLog:
>
> 	* gimple-ssa-warn-access.cc (new_delete_mismatch_p): Strip
> 	DEMANGLE_COMPONENT_TEMPLATE from the operator new and operator
> 	after demangling.
>
> gcc/testsuite/ChangeLog:
>
> 	* g++.dg/warn/Wmismatched-new-delete-9.C: New test.
> ---
>  gcc/gimple-ssa-warn-access.cc                 | 18 ++++++-
>  .../g++.dg/warn/Wmismatched-new-delete-9.C    | 47 +++++++++++++++++++
>  gcc/testsuite/g++.dg/warn/pr109224.C          | 25 ++++++++++
>  3 files changed, 89 insertions(+), 1 deletion(-)
>  create mode 100644 gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C
>  create mode 100644 gcc/testsuite/g++.dg/warn/pr109224.C
>
> diff --git a/gcc/gimple-ssa-warn-access.cc b/gcc/gimple-ssa-warn-access.cc
> index 61f9f0f3d310..fcce63ee332d 100644
> --- a/gcc/gimple-ssa-warn-access.cc
> +++ b/gcc/gimple-ssa-warn-access.cc
> @@ -1762,7 +1762,23 @@ new_delete_mismatch_p (tree new_decl, tree delete_decl)
>    void *np = NULL, *dp = NULL;
>    demangle_component *ndc = cplus_demangle_v3_components (new_str, 0, &np);
>    demangle_component *ddc = cplus_demangle_v3_components (del_str, 0, &dp);
> -  bool mismatch = ndc && ddc && new_delete_mismatch_p (*ndc, *ddc);
> +
> +  /* Sometimes, notably quite often with coroutines, 'operator new' is
> +     templated.  However, template arguments can't change whether a given
> +     new/delete is a singleton or array one, nor what it is a member of, so
> +     the template arguments can be safely ignored for the purposes of checking
> +     for mismatches.   */
> +
> +  auto strip_dc_template = [] (demangle_component* dc)
> +  {
> +    if (dc->type == DEMANGLE_COMPONENT_TEMPLATE)
> +      dc = dc->u.s_binary.left;
> +    return dc;
> +  };
> +
> +  bool mismatch = (ndc && ddc
> +		   && new_delete_mismatch_p (*strip_dc_template (ndc),
> +					     *strip_dc_template (ddc)));
>    free (np);
>    free (dp);
>    return mismatch;
> diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C
> new file mode 100644
> index 000000000000..d431f4049e87
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C
> @@ -0,0 +1,47 @@
> +/* { dg-do compile { target c++11 } } */
> +/* { dg-additional-options "-Wmismatched-new-delete" } */
> +/* PR middle-end/109224 */
> +/* Verify that we consider templated operator new matching with its operator
> +   delete.  */
> +
> +#include <new>
> +
> +struct foo
> +{
> +  template<typename... Args>
> +  void* operator new (std::size_t sz, Args&&...);
> +  template<typename... Args>
> +  void* operator new[] (std::size_t sz, Args&&...);
> +
> +  void operator delete (void* x);
> +  void operator delete[] (void* x);
> +
> +  template<typename... Args>
> +  void operator delete (void* x, Args&&...);
> +  template<typename... Args>
> +  void operator delete[] (void* x, Args&&...);
> +};
> +
> +void
> +f ()
> +{
> +  delete (new (123, true) foo);
> +  delete[] (new (123, true) foo[123]);
> +
> +  delete (new (123, true) foo[123]);
> +  // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
> +  // { dg-note "returned from" "" { target *-*-* } {.-2} }
> +  delete[] (new (123, true) foo);
> +  // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
> +  // { dg-note "returned from" "" { target *-*-* } {.-2} }
> +
> +  foo::operator delete (foo::operator new (1, 123, true), 123, true);
> +  foo::operator delete[] (foo::operator new[] (123, 123, true), 123, true);
> +
> +  foo::operator delete (foo::operator new[] (123, 123, true), 123, true);
> +  // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
> +  // { dg-note "returned from" "" { target *-*-* } {.-2} }
> +  foo::operator delete[] (foo::operator new (1, 123, true), 123, true);
> +  // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
> +  // { dg-note "returned from" "" { target *-*-* } {.-2} }
> +}
> diff --git a/gcc/testsuite/g++.dg/warn/pr109224.C b/gcc/testsuite/g++.dg/warn/pr109224.C
> new file mode 100644
> index 000000000000..4b6102226ffc
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/warn/pr109224.C
> @@ -0,0 +1,25 @@
> +// { dg-do compile { target c++20 } }
> +#include <coroutine>
> +
> +struct Task {
> +    struct promise_type {
> +        std::suspend_never initial_suspend() { return {}; }
> +        std::suspend_never final_suspend() noexcept { return {}; }
> +        void unhandled_exception() { throw; }
> +        Task get_return_object() { return {}; }
> +        void return_void() {}
> +
> +        template<class I>
> +        void* operator new(std::size_t sz, I);
> +
> +        void operator delete(void* ptr, std::size_t);
> +    };
> +};
> +
> +Task f(int) {
> +    co_return;
> +}
> +
> +int main() {
> +    f(42);
> +}
Arsen Arsenović Sept. 4, 2024, 8:09 p.m. UTC | #4
Evening,

Arsen Arsenović <arsen@aarsen.me> writes:

> [[PGP Signed Part:Good signature from 52C294301EA2C493 Arsen Arsenović <arsen@gcc.gnu.org> (trust ultimate) created at 2024-08-28T23:00:44+0200 using EDDSA]]
> Hi,
>
> Arsen Arsenović <arsen@aarsen.me> writes:
>
>>> The && should not be left of the =; if the initializer needs to span multiple
>>> lines, it's usually best to wrap it in ().
>>
>> Okay - done.
>
> [...]
>
>>>> +  foo::operator delete (new (123, true) foo, 123, true);
>>>> +  foo::operator delete[] (new (123, true) foo[123], 123, true);
>>>
>>> Instead of passing the result of a new-expression directly to operator delete,
>>> you should also call operator new directly.
>>>
>>>> +  foo::operator delete (new (123, true) foo[123], 123, true);
>>>> +  // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
>>>> +  // { dg-note "returned from" "" { target *-*-* } {.-2} }
>>>> +  foo::operator delete[] (new (123, true) foo, 123, true);
>>>
>>> Likewise.
>>
>> Ah, good point - adjusted.
>>
>> Here are the changes to v1 I've made as well as the full patch below
>> them.  Does this look OK?
>>
>> --8<---------------cut here---------------start------------->8---
>> modified   gcc/gimple-ssa-warn-access.cc
>> @@ -1776,9 +1776,9 @@ new_delete_mismatch_p (tree new_decl, tree delete_decl)
>>      return dc;
>>    };
>>  
>> -  bool mismatch = ndc && ddc
>> -    && new_delete_mismatch_p (*strip_dc_template (ndc),
>> -			      *strip_dc_template (ddc));
>> +  bool mismatch = (ndc && ddc
>> +		   && new_delete_mismatch_p (*strip_dc_template (ndc),
>> +					     *strip_dc_template (ddc)));
>>    free (np);
>>    free (dp);
>>    return mismatch;
>> modified   gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C
>> @@ -35,13 +35,13 @@ f ()
>>    // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
>>    // { dg-note "returned from" "" { target *-*-* } {.-2} }
>>  
>> -  foo::operator delete (new (123, true) foo, 123, true);
>> -  foo::operator delete[] (new (123, true) foo[123], 123, true);
>> +  foo::operator delete (foo::operator new (1, 123, true), 123, true);
>> +  foo::operator delete[] (foo::operator new[] (123, 123, true), 123, true);
>>  
>> -  foo::operator delete (new (123, true) foo[123], 123, true);
>> +  foo::operator delete (foo::operator new[] (123, 123, true), 123, true);
>>    // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
>>    // { dg-note "returned from" "" { target *-*-* } {.-2} }
>> -  foo::operator delete[] (new (123, true) foo, 123, true);
>> +  foo::operator delete[] (foo::operator new (1, 123, true), 123, true);
>>    // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
>>    // { dg-note "returned from" "" { target *-*-* } {.-2} }
>>  }
>> --8<---------------cut here---------------end--------------->8---

Gentle ping again.  Full patch:
https://inbox.sourceware.org/gcc-patches/86y14ptvdi.fsf@aarsen.me/

TIA, have a lovely evening.
Arsen Arsenović Sept. 19, 2024, 10:41 p.m. UTC | #5
Arsen Arsenović <arsen@aarsen.me> writes:

> Gentle ping again.  Full patch:
> https://inbox.sourceware.org/gcc-patches/86y14ptvdi.fsf@aarsen.me/

And again.  To clarify, the above is a v2 of sorts (it has the comment
fixed, I just didn't update the subject).

TIA, have a lovely day.
diff mbox series

Patch

diff --git a/gcc/gimple-ssa-warn-access.cc b/gcc/gimple-ssa-warn-access.cc
index 61f9f0f3d310..e3fec5fb8e77 100644
--- a/gcc/gimple-ssa-warn-access.cc
+++ b/gcc/gimple-ssa-warn-access.cc
@@ -1762,7 +1762,23 @@  new_delete_mismatch_p (tree new_decl, tree delete_decl)
   void *np = NULL, *dp = NULL;
   demangle_component *ndc = cplus_demangle_v3_components (new_str, 0, &np);
   demangle_component *ddc = cplus_demangle_v3_components (del_str, 0, &dp);
-  bool mismatch = ndc && ddc && new_delete_mismatch_p (*ndc, *ddc);
+
+  /* Sometimes, notably quite often with coroutines, 'operator new' is
+     templated.  However, template arguments can't change whether a given
+     new/delete is a singleton or array one, nor what it is a member of, so
+     the template arguments can be safely ignored for the purposes of checking
+     for mismatches.   */
+
+  auto strip_dc_template = [] (demangle_component* dc)
+  {
+    if (dc->type == DEMANGLE_COMPONENT_TEMPLATE)
+      dc = dc->u.s_binary.left;
+    return dc;
+  };
+
+  bool mismatch = ndc && ddc
+    && new_delete_mismatch_p (*strip_dc_template (ndc),
+			      *strip_dc_template (ddc));
   free (np);
   free (dp);
   return mismatch;
diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C
new file mode 100644
index 000000000000..fa511bbfdb4b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-9.C
@@ -0,0 +1,47 @@ 
+/* { dg-do compile { target c++11 } } */
+/* { dg-additional-options "-Wmismatched-new-delete" } */
+/* PR middle-end/109224 */
+/* Verify that we consider templated operator new matching with its operator
+   delete.  */
+
+#include <new>
+
+struct foo
+{
+  template<typename... Args>
+  void* operator new (std::size_t sz, Args&&...);
+  template<typename... Args>
+  void* operator new[] (std::size_t sz, Args&&...);
+
+  void operator delete (void* x);
+  void operator delete[] (void* x);
+
+  template<typename... Args>
+  void operator delete (void* x, Args&&...);
+  template<typename... Args>
+  void operator delete[] (void* x, Args&&...);
+};
+
+void
+f ()
+{
+  delete (new (123, true) foo);
+  delete[] (new (123, true) foo[123]);
+
+  delete (new (123, true) foo[123]);
+  // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
+  // { dg-note "returned from" "" { target *-*-* } {.-2} }
+  delete[] (new (123, true) foo);
+  // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
+  // { dg-note "returned from" "" { target *-*-* } {.-2} }
+
+  foo::operator delete (new (123, true) foo, 123, true);
+  foo::operator delete[] (new (123, true) foo[123], 123, true);
+
+  foo::operator delete (new (123, true) foo[123], 123, true);
+  // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
+  // { dg-note "returned from" "" { target *-*-* } {.-2} }
+  foo::operator delete[] (new (123, true) foo, 123, true);
+  // { dg-warning "Wmismatched-new-delete" "" { target *-*-* } {.-1} }
+  // { dg-note "returned from" "" { target *-*-* } {.-2} }
+}
diff --git a/gcc/testsuite/g++.dg/warn/pr109224.C b/gcc/testsuite/g++.dg/warn/pr109224.C
new file mode 100644
index 000000000000..4b6102226ffc
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/pr109224.C
@@ -0,0 +1,25 @@ 
+// { dg-do compile { target c++20 } }
+#include <coroutine>
+
+struct Task {
+    struct promise_type {
+        std::suspend_never initial_suspend() { return {}; }
+        std::suspend_never final_suspend() noexcept { return {}; }
+        void unhandled_exception() { throw; }
+        Task get_return_object() { return {}; }
+        void return_void() {}
+
+        template<class I>
+        void* operator new(std::size_t sz, I);
+
+        void operator delete(void* ptr, std::size_t);
+    };
+};
+
+Task f(int) {
+    co_return;
+}
+
+int main() {
+    f(42);
+}