diff mbox series

correct -Wmismatched-new-delete (PR 98160, 98166)

Message ID 359da2e7-2207-0879-93b6-10bc019dc8d0@gmail.com
State New
Headers show
Series correct -Wmismatched-new-delete (PR 98160, 98166) | expand

Commit Message

Martin Sebor Dec. 8, 2020, 8:46 p.m. UTC
PR 98160 reports an ICE in pretty printer code called from the newly
added -Wmismatched-new-delete.  The ICE is just a simple oversight,
but much more interesting is the warning issued for the test case.
It highlights an issue I didn't consider in the initial implementation:
that inlining one of a pair of allocation/deallocation functions but
not the other might lead to false positives when the inlined function
calls another allocator that the deallocator isn't associated with.

In addition, tests for the changes exposed the overly simplistic
nature of the detection of calls to mismatched forms of operator
new and delete which fails to consider member operators, also
resulting in false positives.

Finally, in a comment on the initial implementation Jonathan notes
that the -Wmismatched-new-delete warning should trigger not only
in user code but also in libstdc++ functions inlined into user code.
I thought I had done that but as it turns out, the "standard code
sequence" I put in place isn't sufficient to make this work.

The attached changes avoid the false positives a) by ignoring (with
a warning) the new form of the malloc attribute on inline functions,
and disabling the inlining of others by implicitly adding attribute
noinline to their declaration, and b) by making more robust
the detection of mismatched operators new and delete.  Furthermore,
the patch also arranges for the warning to trigger even for inlined
calls to functions defined in system headers.

To make review a little (marginally) easier the change are two files:
1) gcc-98166-1.diff: introduces valid_new_delete_pair_p and
tree_inlined_location.
2) gcc-98166-2.diff: adjusts the atrribute/warning implementation .

Tested on x86_64-linux.

Martin

Comments

Martin Sebor Dec. 8, 2020, 9:29 p.m. UTC | #1
On 12/8/20 1:46 PM, Martin Sebor wrote:
> PR 98160 reports an ICE in pretty printer code called from the newly
> added -Wmismatched-new-delete.  The ICE is just a simple oversight,
> but much more interesting is the warning issued for the test case.
> It highlights an issue I didn't consider in the initial implementation:
> that inlining one of a pair of allocation/deallocation functions but
> not the other might lead to false positives when the inlined function
> calls another allocator that the deallocator isn't associated with.
> 
> In addition, tests for the changes exposed the overly simplistic
> nature of the detection of calls to mismatched forms of operator
> new and delete which fails to consider member operators, also
> resulting in false positives.
> 
> Finally, in a comment on the initial implementation Jonathan notes
> that the -Wmismatched-new-delete warning should trigger not only
> in user code but also in libstdc++ functions inlined into user code.
> I thought I had done that but as it turns out, the "standard code
> sequence" I put in place isn't sufficient to make this work.

I forgot to mention one other issue: the initial implementation is
also susceptible to false positives for calls to __builtin_free (and
__builtin_realloc) when the library function (i.e., free or realloc)
was associated with an allocator.  The patch also avoids those by
making the built-in handling more robust.  Since Glibc headers are
not allowed to declare symbols from other headers (e.g., <stdio.h>
is not allowed to declare free()), referring to the __builtin_xxx
forms of the functions might be the only way to associate, say,
tempnam with free.  I'm hoping to add this for the next Glibc
release.

> The attached changes avoid the false positives a) by ignoring (with
> a warning) the new form of the malloc attribute on inline functions,
> and disabling the inlining of others by implicitly adding attribute
> noinline to their declaration, and b) by making more robust
> the detection of mismatched operators new and delete.  Furthermore,
> the patch also arranges for the warning to trigger even for inlined
> calls to functions defined in system headers.
> 
> To make review a little (marginally) easier the change are two files:
> 1) gcc-98166-1.diff: introduces valid_new_delete_pair_p and
> tree_inlined_location.
> 2) gcc-98166-2.diff: adjusts the atrribute/warning implementation .
> 
> Tested on x86_64-linux.
> 
> Martin
Jeff Law Dec. 13, 2020, 5:23 p.m. UTC | #2
On 12/8/20 1:46 PM, Martin Sebor wrote:
> PR 98160 reports an ICE in pretty printer code called from the newly
> added -Wmismatched-new-delete.  The ICE is just a simple oversight,
> but much more interesting is the warning issued for the test case.
> It highlights an issue I didn't consider in the initial implementation:
> that inlining one of a pair of allocation/deallocation functions but
> not the other might lead to false positives when the inlined function
> calls another allocator that the deallocator isn't associated with.
>
> In addition, tests for the changes exposed the overly simplistic
> nature of the detection of calls to mismatched forms of operator
> new and delete which fails to consider member operators, also
> resulting in false positives.
>
> Finally, in a comment on the initial implementation Jonathan notes
> that the -Wmismatched-new-delete warning should trigger not only
> in user code but also in libstdc++ functions inlined into user code.
> I thought I had done that but as it turns out, the "standard code
> sequence" I put in place isn't sufficient to make this work.
>
> The attached changes avoid the false positives a) by ignoring (with
> a warning) the new form of the malloc attribute on inline functions,
> and disabling the inlining of others by implicitly adding attribute
> noinline to their declaration, and b) by making more robust
> the detection of mismatched operators new and delete.  Furthermore,
> the patch also arranges for the warning to trigger even for inlined
> calls to functions defined in system headers.
>
> To make review a little (marginally) easier the change are two files:
> 1) gcc-98166-1.diff: introduces valid_new_delete_pair_p and
> tree_inlined_location.
> 2) gcc-98166-2.diff: adjusts the atrribute/warning implementation .
>
> Tested on x86_64-linux.
>
> Martin
>
> gcc-98166-1.diff
>
> Introduce an overload of valid_new_delete_pair_p and tree_inlined_location.
>
> gcc/ChangeLog:
>
> 	* tree-ssa-dce.c (valid_new_delete_pair_p): Factor code out into
> 	valid_new_delete_pair_p in tree.c.
> 	* tree.c (tree_inlined_location): Define new function.
> 	(valid_new_delete_pair_p): Define.
> 	* tree.h (tree_inlined_location): Declare.
> 	(valid_new_delete_pair_p): Declare.
OK


>
> gcc-98166-2.diff
>
> Correct/improve maybe_emit_free_warning (PR middle-end/98166, PR c++/57111, PR middle-end/98160).
>
> Resolves:
> PR middle-end/98166 - bogus -Wmismatched-dealloc on user-defined allocator and inlining
> PR c++/57111 - 57111 - Generalize -Wfree-nonheap-object to delete
> PR middle-end/98160 - ICE in default_tree_printer at gcc/tree-diagnostic.c:270
>
> gcc/ChangeLog:
>
> 	PR middle-end/98166
> 	PR c++/57111
> 	PR middle-end/98160
> 	* builtins.c (call_dealloc_p): Remove unused function.
> 	(new_delete_mismatch_p): Call valid_new_delete_pair_p and rework.
> 	(matching_alloc_calls_p): Handle built-in deallocation functions.
> 	(warn_dealloc_offset): Corrct the handling of user-defined operators
> 	delete.
> 	(maybe_emit_free_warning): Avoid assuming expression is a decl.
> 	Simplify.
> 	* doc/extend.texi (attribute malloc): Update.
>
> gcc/c-family/ChangeLog:
>
> 	PR middle-end/98166
> 	PR c++/57111
> 	PR middle-end/98160
> 	* c-attribs.c (handle_malloc_attribute): Implicitly add attribute
> 	noinline to functions not declared inline and warn on those.
>
> libstdc++-v3/ChangeLog:
> 	* testsuite/ext/vstring/requirements/exception/basic.cc: Suppress
> 	a false positive warning.
> 	* testsuite/ext/vstring/requirements/exception/propagation_consistent.cc:
> 	  Same.
>
> gcc/testsuite/ChangeLog:
>
> 	PR middle-end/98166
> 	PR c++/57111
> 	PR middle-end/98160
> 	* g++.dg/warn/Wmismatched-dealloc-2.C: Adjust test of expected warning.
> 	* g++.dg/warn/Wmismatched-new-delete.C: Same.
> 	* gcc.dg/Wmismatched-dealloc.c: Same.
> 	* c-c++-common/Wfree-nonheap-object-2.c: New test.
> 	* c-c++-common/Wfree-nonheap-object-3.c: New test.
> 	* c-c++-common/Wfree-nonheap-object.c: New test.
> 	* c-c++-common/Wmismatched-dealloc.c: New test.
> 	* g++.dg/warn/Wfree-nonheap-object-3.C: New test.
> 	* g++.dg/warn/Wfree-nonheap-object-4.C: New test.
> 	* g++.dg/warn/Wmismatched-new-delete-2.C: New test.
OK, but there's a follow-up as noted below:


>
>  
> diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C
> index 7ecc99a325c..3aea02fa63d 100644
> --- a/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C
> +++ b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C
Please fix the non-unique testnames in this file.  This can be done as a
separate follow-up patch as they're pre-existing.

diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C
b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C
> index ed1090be5c5..fc07149995d 100644
> --- a/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C
> +++ b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C
And in this file.

Jeff
Martin Sebor Dec. 14, 2020, 9:05 p.m. UTC | #3
On 12/13/20 10:23 AM, Jeff Law wrote:
> 
> 
> On 12/8/20 1:46 PM, Martin Sebor wrote:
>> PR 98160 reports an ICE in pretty printer code called from the newly
>> added -Wmismatched-new-delete.  The ICE is just a simple oversight,
>> but much more interesting is the warning issued for the test case.
>> It highlights an issue I didn't consider in the initial implementation:
>> that inlining one of a pair of allocation/deallocation functions but
>> not the other might lead to false positives when the inlined function
>> calls another allocator that the deallocator isn't associated with.
>>
>> In addition, tests for the changes exposed the overly simplistic
>> nature of the detection of calls to mismatched forms of operator
>> new and delete which fails to consider member operators, also
>> resulting in false positives.
>>
>> Finally, in a comment on the initial implementation Jonathan notes
>> that the -Wmismatched-new-delete warning should trigger not only
>> in user code but also in libstdc++ functions inlined into user code.
>> I thought I had done that but as it turns out, the "standard code
>> sequence" I put in place isn't sufficient to make this work.
>>
>> The attached changes avoid the false positives a) by ignoring (with
>> a warning) the new form of the malloc attribute on inline functions,
>> and disabling the inlining of others by implicitly adding attribute
>> noinline to their declaration, and b) by making more robust
>> the detection of mismatched operators new and delete.  Furthermore,
>> the patch also arranges for the warning to trigger even for inlined
>> calls to functions defined in system headers.
>>
>> To make review a little (marginally) easier the change are two files:
>> 1) gcc-98166-1.diff: introduces valid_new_delete_pair_p and
>> tree_inlined_location.
>> 2) gcc-98166-2.diff: adjusts the atrribute/warning implementation .
>>
>> Tested on x86_64-linux.
>>
>> Martin
>>
>> gcc-98166-1.diff
>>
>> Introduce an overload of valid_new_delete_pair_p and tree_inlined_location.
>>
>> gcc/ChangeLog:
>>
>> 	* tree-ssa-dce.c (valid_new_delete_pair_p): Factor code out into
>> 	valid_new_delete_pair_p in tree.c.
>> 	* tree.c (tree_inlined_location): Define new function.
>> 	(valid_new_delete_pair_p): Define.
>> 	* tree.h (tree_inlined_location): Declare.
>> 	(valid_new_delete_pair_p): Declare.
> OK
> 
> 
>>
>> gcc-98166-2.diff
>>
>> Correct/improve maybe_emit_free_warning (PR middle-end/98166, PR c++/57111, PR middle-end/98160).
>>
>> Resolves:
>> PR middle-end/98166 - bogus -Wmismatched-dealloc on user-defined allocator and inlining
>> PR c++/57111 - 57111 - Generalize -Wfree-nonheap-object to delete
>> PR middle-end/98160 - ICE in default_tree_printer at gcc/tree-diagnostic.c:270
>>
>> gcc/ChangeLog:
>>
>> 	PR middle-end/98166
>> 	PR c++/57111
>> 	PR middle-end/98160
>> 	* builtins.c (call_dealloc_p): Remove unused function.
>> 	(new_delete_mismatch_p): Call valid_new_delete_pair_p and rework.
>> 	(matching_alloc_calls_p): Handle built-in deallocation functions.
>> 	(warn_dealloc_offset): Corrct the handling of user-defined operators
>> 	delete.
>> 	(maybe_emit_free_warning): Avoid assuming expression is a decl.
>> 	Simplify.
>> 	* doc/extend.texi (attribute malloc): Update.
>>
>> gcc/c-family/ChangeLog:
>>
>> 	PR middle-end/98166
>> 	PR c++/57111
>> 	PR middle-end/98160
>> 	* c-attribs.c (handle_malloc_attribute): Implicitly add attribute
>> 	noinline to functions not declared inline and warn on those.
>>
>> libstdc++-v3/ChangeLog:
>> 	* testsuite/ext/vstring/requirements/exception/basic.cc: Suppress
>> 	a false positive warning.
>> 	* testsuite/ext/vstring/requirements/exception/propagation_consistent.cc:
>> 	  Same.
>>
>> gcc/testsuite/ChangeLog:
>>
>> 	PR middle-end/98166
>> 	PR c++/57111
>> 	PR middle-end/98160
>> 	* g++.dg/warn/Wmismatched-dealloc-2.C: Adjust test of expected warning.
>> 	* g++.dg/warn/Wmismatched-new-delete.C: Same.
>> 	* gcc.dg/Wmismatched-dealloc.c: Same.
>> 	* c-c++-common/Wfree-nonheap-object-2.c: New test.
>> 	* c-c++-common/Wfree-nonheap-object-3.c: New test.
>> 	* c-c++-common/Wfree-nonheap-object.c: New test.
>> 	* c-c++-common/Wmismatched-dealloc.c: New test.
>> 	* g++.dg/warn/Wfree-nonheap-object-3.C: New test.
>> 	* g++.dg/warn/Wfree-nonheap-object-4.C: New test.
>> 	* g++.dg/warn/Wmismatched-new-delete-2.C: New test.
> OK, but there's a follow-up as noted below:
> 
> 
>>
>>   
>> diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C
>> index 7ecc99a325c..3aea02fa63d 100644
>> --- a/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C
>> +++ b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C
> Please fix the non-unique testnames in this file.  This can be done as a
> separate follow-up patch as they're pre-existing.
> 
> diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C
> b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C
>> index ed1090be5c5..fc07149995d 100644
>> --- a/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C
>> +++ b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C
> And in this file.

I made an additional tweak to better handle Glibc headers and
committed r11-6028.  I don't see any non-unique names in either
of the tests (or any others in the patch).

Martin
Jeff Law Dec. 14, 2020, 11:08 p.m. UTC | #4
On 12/14/20 2:05 PM, Martin Sebor wrote:
> On 12/13/20 10:23 AM, Jeff Law wrote:
>>
>>
>> On 12/8/20 1:46 PM, Martin Sebor wrote:
>>> PR 98160 reports an ICE in pretty printer code called from the newly
>>> added -Wmismatched-new-delete.  The ICE is just a simple oversight,
>>> but much more interesting is the warning issued for the test case.
>>> It highlights an issue I didn't consider in the initial implementation:
>>> that inlining one of a pair of allocation/deallocation functions but
>>> not the other might lead to false positives when the inlined function
>>> calls another allocator that the deallocator isn't associated with.
>>>
>>> In addition, tests for the changes exposed the overly simplistic
>>> nature of the detection of calls to mismatched forms of operator
>>> new and delete which fails to consider member operators, also
>>> resulting in false positives.
>>>
>>> Finally, in a comment on the initial implementation Jonathan notes
>>> that the -Wmismatched-new-delete warning should trigger not only
>>> in user code but also in libstdc++ functions inlined into user code.
>>> I thought I had done that but as it turns out, the "standard code
>>> sequence" I put in place isn't sufficient to make this work.
>>>
>>> The attached changes avoid the false positives a) by ignoring (with
>>> a warning) the new form of the malloc attribute on inline functions,
>>> and disabling the inlining of others by implicitly adding attribute
>>> noinline to their declaration, and b) by making more robust
>>> the detection of mismatched operators new and delete.  Furthermore,
>>> the patch also arranges for the warning to trigger even for inlined
>>> calls to functions defined in system headers.
>>>
>>> To make review a little (marginally) easier the change are two files:
>>> 1) gcc-98166-1.diff: introduces valid_new_delete_pair_p and
>>> tree_inlined_location.
>>> 2) gcc-98166-2.diff: adjusts the atrribute/warning implementation .
>>>
>>> Tested on x86_64-linux.
>>>
>>> Martin
>>>
>>> gcc-98166-1.diff
>>>
>>> Introduce an overload of valid_new_delete_pair_p and
>>> tree_inlined_location.
>>>
>>> gcc/ChangeLog:
>>>
>>>     * tree-ssa-dce.c (valid_new_delete_pair_p): Factor code out into
>>>     valid_new_delete_pair_p in tree.c.
>>>     * tree.c (tree_inlined_location): Define new function.
>>>     (valid_new_delete_pair_p): Define.
>>>     * tree.h (tree_inlined_location): Declare.
>>>     (valid_new_delete_pair_p): Declare.
>> OK
>>
>>
>>>
>>> gcc-98166-2.diff
>>>
>>> Correct/improve maybe_emit_free_warning (PR middle-end/98166, PR
>>> c++/57111, PR middle-end/98160).
>>>
>>> Resolves:
>>> PR middle-end/98166 - bogus -Wmismatched-dealloc on user-defined
>>> allocator and inlining
>>> PR c++/57111 - 57111 - Generalize -Wfree-nonheap-object to delete
>>> PR middle-end/98160 - ICE in default_tree_printer at
>>> gcc/tree-diagnostic.c:270
>>>
>>> gcc/ChangeLog:
>>>
>>>     PR middle-end/98166
>>>     PR c++/57111
>>>     PR middle-end/98160
>>>     * builtins.c (call_dealloc_p): Remove unused function.
>>>     (new_delete_mismatch_p): Call valid_new_delete_pair_p and rework.
>>>     (matching_alloc_calls_p): Handle built-in deallocation functions.
>>>     (warn_dealloc_offset): Corrct the handling of user-defined
>>> operators
>>>     delete.
>>>     (maybe_emit_free_warning): Avoid assuming expression is a decl.
>>>     Simplify.
>>>     * doc/extend.texi (attribute malloc): Update.
>>>
>>> gcc/c-family/ChangeLog:
>>>
>>>     PR middle-end/98166
>>>     PR c++/57111
>>>     PR middle-end/98160
>>>     * c-attribs.c (handle_malloc_attribute): Implicitly add attribute
>>>     noinline to functions not declared inline and warn on those.
>>>
>>> libstdc++-v3/ChangeLog:
>>>     * testsuite/ext/vstring/requirements/exception/basic.cc: Suppress
>>>     a false positive warning.
>>>     *
>>> testsuite/ext/vstring/requirements/exception/propagation_consistent.cc:
>>>       Same.
>>>
>>> gcc/testsuite/ChangeLog:
>>>
>>>     PR middle-end/98166
>>>     PR c++/57111
>>>     PR middle-end/98160
>>>     * g++.dg/warn/Wmismatched-dealloc-2.C: Adjust test of expected
>>> warning.
>>>     * g++.dg/warn/Wmismatched-new-delete.C: Same.
>>>     * gcc.dg/Wmismatched-dealloc.c: Same.
>>>     * c-c++-common/Wfree-nonheap-object-2.c: New test.
>>>     * c-c++-common/Wfree-nonheap-object-3.c: New test.
>>>     * c-c++-common/Wfree-nonheap-object.c: New test.
>>>     * c-c++-common/Wmismatched-dealloc.c: New test.
>>>     * g++.dg/warn/Wfree-nonheap-object-3.C: New test.
>>>     * g++.dg/warn/Wfree-nonheap-object-4.C: New test.
>>>     * g++.dg/warn/Wmismatched-new-delete-2.C: New test.
>> OK, but there's a follow-up as noted below:
>>
>>
>>>
>>>   diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C
>>> b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C
>>> index 7ecc99a325c..3aea02fa63d 100644
>>> --- a/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C
>>> +++ b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C
>> Please fix the non-unique testnames in this file.  This can be done as a
>> separate follow-up patch as they're pre-existing.
>>
>> diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C
>> b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C
>>> index ed1090be5c5..fc07149995d 100644
>>> --- a/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C
>>> +++ b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C
>> And in this file.
>
> I made an additional tweak to better handle Glibc headers and
> committed r11-6028.  I don't see any non-unique names in either
> of the tests (or any others in the patch).
You're right.  My bad.   There are days when I hate unidiff.  It's only
been 20 years of daily use and my brain still handles context diffs better.

jeff
diff mbox series

Patch

Correct/improve maybe_emit_free_warning (PR middle-end/98166, PR c++/57111, PR middle-end/98160).

Resolves:
PR middle-end/98166 - bogus -Wmismatched-dealloc on user-defined allocator and inlining
PR c++/57111 - 57111 - Generalize -Wfree-nonheap-object to delete
PR middle-end/98160 - ICE in default_tree_printer at gcc/tree-diagnostic.c:270

gcc/ChangeLog:

	PR middle-end/98166
	PR c++/57111
	PR middle-end/98160
	* builtins.c (call_dealloc_p): Remove unused function.
	(new_delete_mismatch_p): Call valid_new_delete_pair_p and rework.
	(matching_alloc_calls_p): Handle built-in deallocation functions.
	(warn_dealloc_offset): Corrct the handling of user-defined operators
	delete.
	(maybe_emit_free_warning): Avoid assuming expression is a decl.
	Simplify.
	* doc/extend.texi (attribute malloc): Update.

gcc/c-family/ChangeLog:

	PR middle-end/98166
	PR c++/57111
	PR middle-end/98160
	* c-attribs.c (handle_malloc_attribute): Implicitly add attribute
	noinline to functions not declared inline and warn on those.

libstdc++-v3/ChangeLog:
	* testsuite/ext/vstring/requirements/exception/basic.cc: Suppress
	a false positive warning.
	* testsuite/ext/vstring/requirements/exception/propagation_consistent.cc:
	  Same.

gcc/testsuite/ChangeLog:

	PR middle-end/98166
	PR c++/57111
	PR middle-end/98160
	* g++.dg/warn/Wmismatched-dealloc-2.C: Adjust test of expected warning.
	* g++.dg/warn/Wmismatched-new-delete.C: Same.
	* gcc.dg/Wmismatched-dealloc.c: Same.
	* c-c++-common/Wfree-nonheap-object-2.c: New test.
	* c-c++-common/Wfree-nonheap-object-3.c: New test.
	* c-c++-common/Wfree-nonheap-object.c: New test.
	* c-c++-common/Wmismatched-dealloc.c: New test.
	* g++.dg/warn/Wfree-nonheap-object-3.C: New test.
	* g++.dg/warn/Wfree-nonheap-object-4.C: New test.
	* g++.dg/warn/Wmismatched-new-delete-2.C: New test.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index faa5030853b..434815b7410 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -13065,12 +13051,119 @@  call_dealloc_argno (tree exp)
   return UINT_MAX;
 }
 
-/* Return true if STMT is a call to a deallocation function.  */
+/* Return true if DELETE_DECL is an operator delete that's not suitable
+   to call with a pointer returned fron NEW_DECL.  */
 
-static inline bool
-call_dealloc_p (tree exp)
+static bool
+new_delete_mismatch_p (tree new_decl, tree delete_decl)
 {
-  return call_dealloc_argno (exp) != UINT_MAX;
+  tree new_name = DECL_ASSEMBLER_NAME (new_decl);
+  tree delete_name = DECL_ASSEMBLER_NAME (delete_decl);
+
+  /* valid_new_delete_pair_p() returns a conservative result.  A true
+     result is reliable but a false result doesn't necessarily mean
+     the operators don't match.  */
+  if (valid_new_delete_pair_p (new_name, delete_name))
+    return false;
+
+  const char *new_str = IDENTIFIER_POINTER (new_name);
+  const char *del_str = IDENTIFIER_POINTER (delete_name);
+
+  if (*new_str != '_')
+    return *new_str != *del_str;
+
+  ++del_str;
+  if (*++new_str != 'Z')
+    return *new_str != *del_str;
+
+  ++del_str;
+  if (*++new_str == 'n')
+    return *del_str != 'd';
+
+  if (*new_str != 'N')
+    return *del_str != 'N';
+
+  /* Handle user-defined member operators below.  */
+  ++new_str;
+  ++del_str;
+
+  do
+    {
+      /* Determine if both operators are members of the same type.
+	 If not, they don't match.  */
+      char *new_end, *del_end;
+      unsigned long nlen = strtoul (new_str, &new_end, 10);
+      unsigned long dlen = strtoul (del_str, &del_end, 10);
+      if (nlen != dlen)
+	return true;
+
+      /* Skip past the name length.   */
+      new_str = new_end;
+      del_str = del_end;
+
+      /* Skip past the names making sure each has the expected length
+	 (it would suggest some sort of a corruption if they didn't).  */
+      while (nlen--)
+	if (!*++new_end)
+	  return true;
+
+      for (nlen = dlen; nlen--; )
+	if (!*++del_end)
+	  return true;
+
+      /* The names have the expected length.  Compare them.  */
+      if (memcmp (new_str, del_str, dlen))
+	return true;
+
+      new_str = new_end;
+      del_str = del_end;
+
+      if (*new_str == 'I')
+	{
+	  /* Template instantiation.  */
+	  do
+	    {
+	      ++new_str;
+	      ++del_str;
+
+	      if (*new_str == 'n')
+		break;
+	      if (*new_str != *del_str)
+		return true;
+	    }
+	  while (*new_str);
+	}
+
+      if (*new_str == 'n')
+	{
+	  if (*del_str != 'd')
+	    return true;
+
+	  ++del_str;
+	  if (*++new_str == 'w' && *del_str != 'l')
+	    return true;
+	  if (*new_str == 'a' && *del_str != 'a')
+	    return true;
+	  ++new_str;
+	  ++del_str;
+	  break;
+	}
+    } while (true);
+
+  if (*new_str != 'E')
+    return *del_str != *new_str;
+
+  ++new_str;
+  ++del_str;
+  if (*new_str != 'j' && *new_str != 'm' && *new_str != 'y')
+    return true;
+  if (*del_str != 'P' || *++del_str != 'v')
+    return true;
+
+  /* Ignore any remaining arguments.  Since both operators are members
+     of the same class, mismatches in those should be detectable and
+     diagnosed by the front end.  */
+  return false;
 }
 
 /* ALLOC_DECL and DEALLOC_DECL are pair of allocation and deallocation
@@ -13083,15 +13176,9 @@  matching_alloc_calls_p (tree alloc_decl, tree dealloc_decl)
   if (DECL_IS_OPERATOR_NEW_P (alloc_decl))
     {
       if (DECL_IS_OPERATOR_DELETE_P (dealloc_decl))
-	{
-	  /* Return true iff both functions are of the same array or
-	     singleton form and false otherwise.  */
-	  tree alloc_id = DECL_NAME (alloc_decl);
-	  tree dealloc_id = DECL_NAME (dealloc_decl);
-	  const char *alloc_fname = IDENTIFIER_POINTER (alloc_id);
-	  const char *dealloc_fname = IDENTIFIER_POINTER (dealloc_id);
-	  return !strchr (alloc_fname, '[') == !strchr (dealloc_fname, '[');
-	}
+	/* Return true iff both functions are of the same array or
+	   singleton form and false otherwise.  */
+	return !new_delete_mismatch_p (alloc_decl, dealloc_decl);
 
       /* Return false for deallocation functions that are known not
 	 to match.  */
@@ -13128,13 +13215,35 @@  matching_alloc_calls_p (tree alloc_decl, tree dealloc_decl)
 	}
     }
 
-  /* If DEALLOC_DECL has internal "*dealloc" attribute scan the list of
-     its associated allocation functions for ALLOC_DECL.  If it's found
-     they are a matching pair, otherwise they're not.  */
-  tree attrs = DECL_ATTRIBUTES (dealloc_decl);
-  if (!attrs)
-    return false;
+  if (fndecl_built_in_p (dealloc_decl, BUILT_IN_NORMAL))
+    {
+      tree alloc_attrs = DECL_ATTRIBUTES (alloc_decl);
+      built_in_function dealloc_code = DECL_FUNCTION_CODE (dealloc_decl);
+
+      for (tree alloc_funs = alloc_attrs;
+	   (alloc_funs = lookup_attribute ("malloc", alloc_funs));
+	   alloc_funs = TREE_CHAIN (alloc_funs))
+	{
+	  tree args = TREE_VALUE (alloc_funs);
+	  if (!args)
+	    continue;
+
+	  tree fndecl = TREE_VALUE (args);
+	  if (fndecl
+	      && fndecl_built_in_p (fndecl, BUILT_IN_NORMAL)
+	      && dealloc_code == DECL_FUNCTION_CODE (fndecl))
+	    return true;
+	}
+      return false;
+    }
 
+  /* If DEALLOC_DECL has an internal "*dealloc" attribute scan the list
+     of its associated allocation functions for ALLOC_DECL.  In addition,
+     for built-ins, search the list of attributes of both the form
+     referenced by the attribute and the "internal" __builtin_xxx form/
+     If the corresponding ALLOC_DECL is found they're a matching pair,
+     otherwise they're not.  */
+  tree attrs = DECL_ATTRIBUTES (dealloc_decl);
   for (tree funs = attrs;
        (funs = lookup_attribute ("*dealloc", funs));
        funs = TREE_CHAIN (funs))
@@ -13167,15 +13276,36 @@  matching_alloc_calls_p (gimple *alloc, tree dealloc_decl)
   return matching_alloc_calls_p (alloc_decl, dealloc_decl);
 }
 
-/* Diagnose a call to FNDECL to deallocate a pointer referenced by
-   AREF that includes a nonzero offset.  Such a pointer cannot refer
-   to the beginning of an allocated object.  A negative offset may
-   refer to it only if the target pointer is unknown.  */
+/* Diagnose a call EXP to deallocate a pointer referenced by AREF if it
+   includes a nonzero offset.  Such a pointer cannot refer to the beginning
+   of an allocated object.  A negative offset may refer to it only if
+   the target pointer is unknown.  */
 
 static bool
-warn_dealloc_offset (location_t loc, tree exp, tree fndecl,
-		     const access_ref &aref)
+warn_dealloc_offset (location_t loc, tree exp, const access_ref &aref)
 {
+  if (aref.deref || aref.offrng[0] <= 0 || aref.offrng[1] <= 0)
+    return false;
+
+  tree dealloc_decl = get_callee_fndecl (exp);
+  if (DECL_IS_OPERATOR_DELETE_P (dealloc_decl)
+      && !DECL_IS_REPLACEABLE_OPERATOR (dealloc_decl))
+    {
+      /* A call to a user-defined operator delete with a pointer plus offset
+	 may be valid if it's returned from an unknown function (i.e., one
+	 that's not operator new).  */
+      if (TREE_CODE (aref.ref) == SSA_NAME)
+	{
+	  gimple *def_stmt = SSA_NAME_DEF_STMT (aref.ref);
+	  if (is_gimple_call (def_stmt))
+	    {
+	      tree alloc_decl = gimple_call_fndecl (def_stmt);
+	      if (!DECL_IS_OPERATOR_NEW_P (alloc_decl))
+		return false;
+	    }
+	}
+    }
+
   char offstr[80];
   offstr[0] = '\0';
   if (wi::fits_shwi_p (aref.offrng[0]))
@@ -13192,7 +13322,7 @@  warn_dealloc_offset (location_t loc, tree exp, tree fndecl,
 
   if (!warning_at (loc, OPT_Wfree_nonheap_object,
 		   "%K%qD called on pointer %qE with nonzero offset%s",
-		   exp, fndecl, aref.ref, offstr))
+		   exp, dealloc_decl, aref.ref, offstr))
     return false;
 
   if (DECL_P (aref.ref))
@@ -13202,9 +13332,16 @@  warn_dealloc_offset (location_t loc, tree exp, tree fndecl,
       gimple *def_stmt = SSA_NAME_DEF_STMT (aref.ref);
       if (is_gimple_call (def_stmt))
 	{
+	  location_t def_loc = gimple_location (def_stmt);
 	  tree alloc_decl = gimple_call_fndecl (def_stmt);
-	  inform (gimple_location (def_stmt),
-		  "returned from a call to %qD", alloc_decl);
+	  if (alloc_decl)
+	    inform (def_loc,
+		    "returned from %qD", alloc_decl);
+	  else if (tree alloc_fntype = gimple_call_fntype (def_stmt))
+	    inform (def_loc,
+		    "returned from %qT", alloc_fntype);
+	  else
+	    inform (def_loc,  "obtained here");
 	}
     }
 
@@ -13240,8 +13377,7 @@  maybe_emit_free_warning (tree exp)
     return;
 
   tree dealloc_decl = get_callee_fndecl (exp);
-  location_t loc = tree_nonartificial_location (exp);
-  loc = expansion_point_location_if_in_system_header (loc);
+  location_t loc = tree_inlined_location (exp);
 
   if (DECL_P (ref) || EXPR_P (ref))
     {
@@ -13251,18 +13387,18 @@  maybe_emit_free_warning (tree exp)
 			 "%K%qD called on unallocated object %qD",
 			 exp, dealloc_decl, ref))
 	{
-	  inform (DECL_SOURCE_LOCATION (ref),
-		  "declared here");
+	  loc = (DECL_P (ref)
+		 ? DECL_SOURCE_LOCATION (ref)
+		 : EXPR_LOCATION (ref));
+	  inform (loc, "declared here");
 	  return;
 	}
 
       /* Diagnose freeing a pointer that includes a positive offset.
 	 Such a pointer cannot refer to the beginning of an allocated
 	 object.  A negative offset may refer to it.  */
-      if (!aref.deref
-	  && aref.sizrng[0] != aref.sizrng[1]
-	  && aref.offrng[0] > 0 && aref.offrng[1] > 0
-	  && warn_dealloc_offset (loc, exp, dealloc_decl, aref))
+      if (aref.sizrng[0] != aref.sizrng[1]
+	  && warn_dealloc_offset (loc, exp, aref))
 	return;
     }
   else if (CONSTANT_CLASS_P (ref))
@@ -13295,9 +13431,7 @@  maybe_emit_free_warning (tree exp)
 	    {
 	      if (matching_alloc_calls_p (def_stmt, dealloc_decl))
 		{
-		  if (!aref.deref
-		      && aref.offrng[0] > 0 && aref.offrng[1] > 0
-		      && warn_dealloc_offset (loc, exp, dealloc_decl, aref))
+		  if (warn_dealloc_offset (loc, exp, aref))
 		    return;
 		}
 	      else
@@ -13320,16 +13454,14 @@  maybe_emit_free_warning (tree exp)
 				 "%K%qD called on pointer to "
 				 "an unallocated object",
 				 exp, dealloc_decl);
-	  else if (!aref.deref
-		   && aref.offrng[0] > 0 && aref.offrng[1] > 0
-		   && warn_dealloc_offset (loc, exp, dealloc_decl, aref))
+	  else if (warn_dealloc_offset (loc, exp, aref))
 	    return;
 
 	  if (warned)
 	    {
 	      tree fndecl = gimple_call_fndecl (def_stmt);
 	      inform (gimple_location (def_stmt),
-		      "returned from a call to %qD", fndecl);
+		      "returned from %qD", fndecl);
 	      return;
 	    }
 	}
@@ -13341,7 +13473,7 @@  maybe_emit_free_warning (tree exp)
 	      && !aref.deref
 	      && aref.sizrng[0] != aref.sizrng[1]
 	      && aref.offrng[0] > 0 && aref.offrng[1] > 0
-	      && warn_dealloc_offset (loc, exp, dealloc_decl, aref))
+	      && warn_dealloc_offset (loc, exp, aref))
 	    return;
 	}
     }
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index f7dad7a91d7..7974a7088f9 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -3174,11 +3174,21 @@  handle_malloc_attribute (tree *node, tree name, tree args,
       return NULL_TREE;
     }
 
-  /* In C++ the argument may be wrapped in a cast to disambiguate one
-     of a number of overloads (such as operator delete).  Strip it.  */
   STRIP_NOPS (dealloc);
   if (TREE_CODE (dealloc) == ADDR_EXPR)
-    dealloc = TREE_OPERAND (dealloc, 0);
+    {
+      /* In C++ the argument may be wrapped in a cast to disambiguate
+	 one of a number of overloads (such as operator delete).  To
+	 make things interesting, the cast looks different between
+	 different C++ versions.  Strip it and install the attribute
+	 with the disambiguated function.  */
+      dealloc = TREE_OPERAND (dealloc, 0);
+
+      *no_add_attrs = true;
+      tree attr = tree_cons (NULL_TREE, dealloc, TREE_CHAIN (args));
+      attr = build_tree_list (name, attr);
+      return decl_attributes (node, attr, 0);
+    }
 
   if (TREE_CODE (dealloc) != FUNCTION_DECL)
     {
@@ -3234,9 +3244,50 @@  handle_malloc_attribute (tree *node, tree name, tree args,
 	}
 
       *no_add_attrs = false;
-      tree attr_free = build_tree_list (NULL_TREE, DECL_NAME (fndecl));
-      attr_free = build_tree_list (get_identifier ("*dealloc"), attr_free);
-      decl_attributes (&dealloc, attr_free, 0);
+
+      tree at_noinline = NULL_TREE;
+      if (!fndecl_built_in_p (fndecl) && !fndecl_built_in_p (dealloc))
+	{
+	  /* When inlining (or optimization) is enabled and the allocator
+	     and deallocator are not built-in functions, ignore
+	     the attribute on functions declared inline since it could
+	     lead to false positives when inlining one or the other
+	     call would wind up calling a mismatched allocator or
+	     deallocator.  */
+	  if ((optimize && DECL_DECLARED_INLINE_P (fndecl))
+	      || lookup_attribute ("always_inline", DECL_ATTRIBUTES (fndecl)))
+	    {
+	      warning (OPT_Wattributes,
+		       "%<%E (%E)%> attribute ignored on functions "
+		       "declared %qs", name, DECL_NAME (dealloc), "inline");
+	      *no_add_attrs = true;
+	      return NULL_TREE;
+	    }
+
+	  if ((optimize && DECL_DECLARED_INLINE_P (dealloc))
+	      || lookup_attribute ("always_inline", DECL_ATTRIBUTES (dealloc)))
+	    {
+	      warning (OPT_Wattributes,
+		       "%<%E (%E)%> attribute ignored with deallocation "
+		       "functions declared %qs",
+		       name, DECL_NAME (dealloc), "inline");
+	      inform (DECL_SOURCE_LOCATION (dealloc),
+		      "deallocation function declared here" );
+	      *no_add_attrs = true;
+	      return NULL_TREE;
+	    }
+
+	  /* Disable inlining for non-standard deallocators to avoid false
+	     positives due to mismatches between the inlined implementation
+	     of one and not the other pair of functions.  */
+	  at_noinline
+	    = tree_cons (get_identifier ("noinline"), NULL_TREE, NULL_TREE);
+	  decl_attributes (&fndecl, at_noinline, 0);
+	}
+
+      tree attrs = build_tree_list (NULL_TREE, DECL_NAME (fndecl));
+      attrs = tree_cons (get_identifier ("*dealloc"), attrs, at_noinline);
+      decl_attributes (&dealloc, attrs, 0);
       return NULL_TREE;
     }
 
diff --git a/gcc/testsuite/c-c++-common/Wfree-nonheap-object-2.c b/gcc/testsuite/c-c++-common/Wfree-nonheap-object-2.c
new file mode 100644
index 00000000000..0aedf1babbc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wfree-nonheap-object-2.c
@@ -0,0 +1,52 @@ 
+/* PR middle-end/98166: bogus -Wmismatched-dealloc on user-defined allocator
+   and inlining
+   Verify that the allocator can be declared inline without a warning when
+   it's associated with a standard deallocator.  Associating an inline
+   deallocator with an allocator would cause false positives when the former
+   calls a deallocation function the allocator isn't associated with, so
+   that triggers a warning on declaration.
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+__attribute__ ((malloc (__builtin_free)))
+inline int*
+alloc_int (int n)
+{
+  return (int*)__builtin_malloc (n + sizeof (int));
+}
+
+void test_nowarn_int (int n)
+{
+  {
+    int *p = alloc_int (n);
+    __builtin_free (p);
+  }
+
+  {
+    int *p = alloc_int (n);
+    __builtin_free (p + 1);   // { dg-warning "\\\[-Wfree-nonheap-object" }
+  }
+}
+
+
+inline void
+dealloc_long (long *p)
+{
+  __builtin_free (p);         // { dg-warning "'__builtin_free|void __builtin_free\\(void\\*\\)' called on pointer 'p|<unknown>' with nonzero offset" }
+}
+
+__attribute__ ((malloc (dealloc_long)))
+long* alloc_long (int);       // { dg-warning "'malloc \\\(dealloc_long\\\)' attribute ignored with deallocation functions declared 'inline'" }
+
+void test_nowarn_long (int n)
+{
+  {
+    long *p = alloc_long (n);
+    dealloc_long (p);
+  }
+
+  {
+    long *p = alloc_long (n);
+    dealloc_long (p + 1);
+  }
+}
diff --git a/gcc/testsuite/c-c++-common/Wfree-nonheap-object-3.c b/gcc/testsuite/c-c++-common/Wfree-nonheap-object-3.c
new file mode 100644
index 00000000000..41a5b50362e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wfree-nonheap-object-3.c
@@ -0,0 +1,70 @@ 
+/* PR middle-end/98166: bogus -Wmismatched-dealloc on user-defined allocator
+   and inlining
+   Verify that without inlining, both the allocator and the deallocator
+   can be declared inline without a warning and that mismatched calls are
+   detected, but that declaring them always_inline does trigger a warning.
+   { dg-do compile }
+   { dg-options "-Wall" } */
+
+__attribute__ ((malloc (__builtin_free)))
+inline int*
+alloc_int (int n)
+{
+  return (int*)__builtin_malloc (n + sizeof (int));
+}
+
+void test_nowarn_int (int n)
+{
+  {
+    int *p = alloc_int (n);
+    __builtin_free (p);
+  }
+
+  {
+    int *p = alloc_int (n);
+    __builtin_free (p + 1);   // { dg-warning "'__builtin_free|void __builtin_free\\(void\\*\\)' called on pointer 'p|<unknown>' with nonzero offset" }
+  }
+}
+
+
+inline void
+dealloc_long (long *p) { __builtin_free (p); }
+
+__attribute__ ((malloc (dealloc_long)))
+long* alloc_long (int);
+
+void test_nowarn_long (int n)
+{
+  {
+    long *p = alloc_long (n);
+    dealloc_long (p);
+  }
+
+  {
+    long *p = alloc_long (n);
+    dealloc_long (p + 1);     // { dg-warning "'dealloc_long' called on pointer 'p|<unknown>' with nonzero offset" }
+  }
+}
+
+
+inline __attribute__ ((always_inline)) void
+dealloc_float (float *p)      // { dg-message "deallocation function declared here" }
+{
+  __builtin_free (p);         // { dg-warning "'__builtin_free|void __builtin_free\\(void\\*\\)' called on pointer 'p|<unknown>' with nonzero offset" }
+}
+
+__attribute__ ((malloc (dealloc_float)))
+float* alloc_float (int);     // { dg-warning "'malloc \\(dealloc_float\\)' attribute ignored with deallocation functions declared 'inline'" }
+
+void test_nowarn_float (int n)
+{
+  {
+    float *p = alloc_float (n);
+    dealloc_float (p);
+  }
+
+  {
+    float *p = alloc_float (n);
+    dealloc_float (p + 2);
+  }
+}
diff --git a/gcc/testsuite/c-c++-common/Wfree-nonheap-object.c b/gcc/testsuite/c-c++-common/Wfree-nonheap-object.c
new file mode 100644
index 00000000000..dfbb296e9a7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wfree-nonheap-object.c
@@ -0,0 +1,50 @@ 
+/* Verify that built-in forms of functions can be used interchangeably
+   with their ordinary (library) forms in attribute malloc.
+   { dg-do compile }
+   { dg-options "-Wall" } */
+
+char* f (void) __attribute__ ((malloc (__builtin_free)));
+
+#if __cplusplus
+extern "C" {
+#endif
+
+void free (void*);
+
+#if __cplusplus
+}
+#endif
+
+char* g (void) __attribute__ ((malloc (free)));
+
+
+void test_nowarm (void)
+{
+  char *p = f ();
+  free (p);
+
+  p = g ();
+  free (p);
+
+  p = f ();
+  __builtin_free (p);
+
+  p = g ();
+  __builtin_free (p);
+}
+
+
+void test_warn (void)
+{
+  char *p = f ();
+  free (p + 1);               // { dg-warning "'free|void free\\(void\\*\\)' called on pointer 'p|<unknown>' with nonzero offset" }
+
+  p = g ();
+  free (p + 2);               // { dg-warning "'free|void free\\(void\\*\\)' called on pointer 'p|<unknown>' with nonzero offset" }
+
+  p = f ();
+  __builtin_free (p + 3);     // { dg-warning "'__builtin_free|void __builtin_free\\(void\\*\\)' called on pointer 'p|<unknown>' with nonzero offset" }
+
+  p = g ();
+  __builtin_free (p + 4);     // { dg-warning "'__builtin_free|void __builtin_free\\(void\\*\\)' called on pointer 'p|<unknown>' with nonzero offset" }
+}
diff --git a/gcc/testsuite/c-c++-common/Wmismatched-dealloc.c b/gcc/testsuite/c-c++-common/Wmismatched-dealloc.c
new file mode 100644
index 00000000000..9315f74973c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wmismatched-dealloc.c
@@ -0,0 +1,67 @@ 
+/* PR middle-end/98166: bogus -Wmismatched-dealloc on user-defined allocator
+   and inlining
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+
+void dealloc_shrt (short *p)
+{
+  /* A positive offset would be diagnosed but a negative one must
+     not be.  */
+  __builtin_free (p - 1);     // { dg-bogus "-Wmismatched-dealloc" }
+}
+
+__attribute__ ((malloc (dealloc_shrt)))
+short* alloc_shrt (int n) /* { return malloc (n) + 1; } */;
+
+void test_nowarn_shrt (int n)
+{
+  short *p = alloc_shrt (n);
+  dealloc_shrt (p);
+}
+
+
+void dealloc_int (int *p) /* { free (p - 1); } */;
+
+__attribute__ ((malloc (dealloc_int)))
+int* alloc_int (int n)
+{
+  return (int*)__builtin_malloc (n) + 1;
+}
+
+void test_nowarn_int (int n)
+{
+  int *p = alloc_int (n);
+  dealloc_int (p);          // { dg-bogus "-Wmismatched-dealloc" }
+}
+
+
+void dealloc_long (long *p) /* { free (p - 2); } */;
+
+__attribute__ ((malloc (dealloc_long)))
+inline long*
+alloc_long (int n) {          // { dg-warning "'malloc \\(\[^\n\r\]*dealloc_long\[^\n\r\]*\\)' attribute ignored on functions declared 'inline'" }
+  return (long*)__builtin_malloc (n) + 2;
+}
+
+void test_nowarn_long (int n)
+{
+  long *p = alloc_long (n);
+  dealloc_long (p);           // { dg-bogus "\\\[-Wmismatched-dealloc" }
+}
+
+
+inline void
+dealloc_float (float *p)      // { dg-message "deallocation function declared here" }
+{
+  __builtin_free (p - 3);
+}
+
+__attribute__ ((malloc (dealloc_float)))
+float* alloc_float (int n);   // { dg-warning "'malloc \\(\[^\n\r\]*dealloc_float\[^\n\r\]*\\)' attribute ignored with deallocation functions declared 'inline'" }
+
+void test_nowarn_float (int n)
+{
+  float *p = alloc_float (n);
+  dealloc_float (p);          // { dg-bogus "\\\[-Wmismatched-dealloc" }
+}
diff --git a/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-3.C b/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-3.C
new file mode 100644
index 00000000000..47f97dcb636
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-3.C
@@ -0,0 +1,38 @@ 
+/* PR c++/57111 - Generalize -Wfree-nonheap-object to delete
+   Verify that even without -Wsystem-headers the warning is issued
+   for pairs of library functions defined in system headers.
+   { dg-do compile { target c++11 } }
+   { dg-options "-O2 -Wall" } */
+
+#include <memory>
+#include <string>
+
+void test_string ()
+{
+  std::string str ("abc");          // { dg-message "declared here" }
+
+  const char *s = str.c_str ();
+  __builtin_printf ("%s\n", s);
+
+  /* Because the delete call is made directly in the function this
+     does not exercise the same thing as test_unique_ptr.  */
+  delete s;                         // { dg-warning "'void operator delete\\(void\\*\[^\\)\]*\\)' called on unallocated object 'str'" }
+}
+
+void test_unique_ptr ()
+{
+  int arr[]= { 1, 2 };              // { dg-message "declared here" }
+
+  std::unique_ptr<int[]> up (arr);
+  __builtin_printf ("%i %i\n", up[0], up[1]);
+
+  /* TO DO: verify that the warning is printed, including its inlining
+     context (the directive below doesn't work):
+     { Xdg-message "In member function.*inlined from 'void test_unique_ptr\\(\\)'.*warning: 'void operator delete \\\[]\\(void\\*\\)' called on unallocated object 'arr'" "" { target *-*-* } 0 }  */
+
+  /* Here, the delete call is made indirectly from std::unique_ptr
+     dtor.  */
+}
+
+/* Prune out the warning from test_unique_ptr().
+   { dg-prune-output "-Wfree-nonheap-object" } */
diff --git a/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-4.C b/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-4.C
new file mode 100644
index 00000000000..943ef0cd1ab
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-4.C
@@ -0,0 +1,26 @@ 
+/* PR middle-end/98160: bogus -Wfree-nonheap-object calling member delete
+   on the result of inline member new plus offset
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+struct MemoryManager { void* allocate (); };
+
+struct XMemory
+{
+  void* operator new (__SIZE_TYPE__, MemoryManager *mgr)
+  {
+    void *p = mgr->allocate ();
+    return (char*)p + sizeof(MemoryManager);
+  }
+
+  void operator delete (void*, MemoryManager*);
+};
+
+struct XMLMutex: XMemory {
+  XMLMutex();
+};
+
+void gValidatorMutex (MemoryManager *mgr)
+{
+  new (mgr) XMLMutex;   // { dg-bogus "\\\[-Wfree-nonheap-object" }
+}
diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C
index 7ecc99a325c..3aea02fa63d 100644
--- a/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C
+++ b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C
@@ -59,13 +59,13 @@  void test_my_new ()
 
   {
     void *p = my_new (1);
-    // { dg-message "returned from a call to 'int\\\* my_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'int\\\* my_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 }
     operator delete[] (p);
     // { dg-warning "'void operator delete \\\[]\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function \\\[-Wmismatched-new-delete" "" { target *-*-* } .-1 }
   }
   {
     void *p = my_new (1);
-    // { dg-message "returned from a call to 'int\\\* my_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'int\\\* my_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 }
     sink (p);
     operator delete[] (p);
     // { dg-warning "'void operator delete \\\[]\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function \\\[-Wmismatched-new-delete" "" { target *-*-* } .-1 }
@@ -89,7 +89,7 @@  void test_my_new ()
 
   {
     void *p = my_new (1);
-    // { dg-message "returned from a call to 'int\\\* my_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'int\\\* my_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 }
     sink (p);
     my_array_delete ("3", p);
     // { dg-warning "'void my_array_delete\\\(const char\\\*, void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 }
@@ -97,7 +97,7 @@  void test_my_new ()
 
   {
     void *p = my_new (1);
-    // { dg-message "returned from a call to 'int\\\* my_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'int\\\* my_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 }
     sink (p);
     free (p);
     // { dg-warning "'void free\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 }
@@ -105,7 +105,7 @@  void test_my_new ()
 
   {
     void *p = my_new (1);
-    // { dg-message "returned from a call to 'int\\\* my_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'int\\\* my_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 }
     sink (p);
     p = realloc (p, 123);
     // { dg-warning "'void\\\* realloc\\\(void\\\*, size_t\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 }
@@ -132,13 +132,13 @@  void test_my_array_new ()
 
   {
     void *p = my_array_new (1);
-    // { dg-message "returned from a call to 'int\\\* my_array_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'int\\\* my_array_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 }
     operator delete (p);
     // { dg-warning "'void operator delete\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function \\\[-Wmismatched-new-delete" "" { target *-*-* } .-1 }
   }
   {
     void *p = my_array_new (1);
-    // { dg-message "returned from a call to 'int\\\* my_array_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'int\\\* my_array_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 }
     sink (p);
     operator delete (p);
     // { dg-warning "'void operator delete\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function \\\[-Wmismatched-new-delete" "" { target *-*-* } .-1 }
@@ -161,7 +161,7 @@  void test_my_array_new ()
   }
   {
     void *p = my_array_new (1);
-    // { dg-message "returned from a call to 'int\\\* my_array_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'int\\\* my_array_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 }
     sink (p);
     my_delete ("3", p);
     // { dg-warning "'void my_delete\\\(const char\\\*, void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 }
@@ -169,7 +169,7 @@  void test_my_array_new ()
 
   {
     void *p = my_array_new (1);
-    // { dg-message "returned from a call to 'int\\\* my_array_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'int\\\* my_array_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 }
     sink (p);
     free (p);
     // { dg-warning "'void free\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 }
@@ -177,7 +177,7 @@  void test_my_array_new ()
 
   {
     void *p = my_array_new (1);
-    // { dg-message "returned from a call to 'int\\\* my_array_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'int\\\* my_array_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 }
     sink (p);
     p = realloc (p, 123);
     // { dg-warning "'void\\\* realloc\\\(void\\\*, size_t\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 }
diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C
index ed1090be5c5..fc07149995d 100644
--- a/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C
+++ b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C
@@ -44,14 +44,14 @@  void warn_new_free (int n)
 {
   {
     void *p = operator new (n);
-    // { dg-message "returned from a call to 'void\\\* operator new\\\(" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'void\\\* operator new\\\(" "note" { target *-*-* } .-1 }
     sink (p);
     free (p);
     // { dg-warning "'void free\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 }
   }
   {
     char *p = new char[n];
-    // { dg-message "returned from a call to 'void\\\* operator new \\\[" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'void\\\* operator new \\\[" "note" { target *-*-* } .-1 }
     sink (p);
     free (p);
     // { dg-warning "'void free\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 }
@@ -66,7 +66,7 @@  void warn_new_realloc (int n)
 {
   {
     void *p = operator new (n);
-    // { dg-message "returned from a call to 'void\\\* operator new\\\(" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'void\\\* operator new\\\(" "note" { target *-*-* } .-1 }
     sink (p);
     p = realloc (p, n * 2);
     // { dg-warning "'void\\\* realloc\\\(\[^)\]+\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 }
@@ -74,7 +74,7 @@  void warn_new_realloc (int n)
   }
   {
     void *p = new char[n];
-    // { dg-message "returned from a call to 'void\\\* operator new \\\[" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'void\\\* operator new \\\[" "note" { target *-*-* } .-1 }
     sink (p);
     p = realloc (p, n * 2);
     // { dg-warning "'void\\\* realloc\\\(\[^)\]+\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 }
@@ -89,7 +89,7 @@  void warn_new_realloc (int n)
 void warn_malloc_op_delete (int n)
 {
   char *p = (char *)malloc (n);
-  // { dg-message "returned from a call to 'void\\\* malloc\\\(" "note" { target *-*-* } .-1 }
+  // { dg-message "returned from 'void\\\* malloc\\\(" "note" { target *-*-* } .-1 }
   sink (p);
   operator delete (p);
   // { dg-warning "'void operator delete\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 }
@@ -97,13 +97,13 @@  void warn_malloc_op_delete (int n)
 
 
 /* Verify a warning for an invocation of either form of the delete
-   expression with a pointer returned from a call to malloc().  */
+   expression with a pointer returned from malloc().  */
 
 void warn_malloc_delete (int n)
 {
   {
     char *p = (char *)malloc (n);
-    // { dg-message "returned from a call to 'void\\\* malloc\\\(" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'void\\\* malloc\\\(" "note" { target *-*-* } .-1 }
     sink (p);
     /* C++98 calls operator delete (void*) but later versions call
        operator delete (void*, size_t).  The difference doesn't matter
@@ -114,7 +114,7 @@  void warn_malloc_delete (int n)
 
   {
     char *p = (char *)malloc (n);
-    // { dg-message "returned from a call to 'void\\\* malloc\\\(" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'void\\\* malloc\\\(" "note" { target *-*-* } .-1 }
     sink (p);
     delete[] p;
     // { dg-warning "'void operator delete \\\[]\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 }
@@ -123,13 +123,13 @@  void warn_malloc_delete (int n)
 
 
 /* Verify a warning for an invocation of either form of the delete
-   expression with a pointer returned from a call to realloc().  */
+   expression with a pointer returned from realloc().  */
 
 void warn_realloc_delete (void *p1, void *p2, int n)
 {
   {
     char *q = (char *)realloc (p1, n);
-    // { dg-message "returned from a call to 'void\\\* realloc\\\(" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'void\\\* realloc\\\(" "note" { target *-*-* } .-1 }
     sink (q);
     /* C++98 calls operator delete (void*) but later versions call
        operator delete (void*, size_t).  The difference doesn't matter
@@ -140,7 +140,7 @@  void warn_realloc_delete (void *p1, void *p2, int n)
 
   {
     char *q = (char *)realloc (p2, n);
-    // { dg-message "returned from a call to 'void\\\* realloc\\\(" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'void\\\* realloc\\\(" "note" { target *-*-* } .-1 }
     sink (q);
     delete[] q;
     // { dg-warning "'void operator delete \\\[]\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 }
@@ -149,13 +149,13 @@  void warn_realloc_delete (void *p1, void *p2, int n)
 
 
 /* Verify a warning for an invocation of either form of the delete
-   expression with a pointer returned from a call to strdup().  */
+   expression with a pointer returned from strdup().  */
 
 void warn_strdup_delete (const char *s1, const char *s2)
 {
   {
     char *q = strdup (s1);
-    // { dg-message "returned from a call to 'char\\\* strdup\\\(" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'char\\\* strdup\\\(" "note" { target *-*-* } .-1 }
     sink (q);
     /* C++98 calls operator delete (void*) but later versions call
        operator delete (void*, size_t).  The difference doesn't matter
@@ -166,7 +166,7 @@  void warn_strdup_delete (const char *s1, const char *s2)
 
   {
     char *q = strdup (s2);
-    // { dg-message "returned from a call to 'char\\\* strdup\\\(" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'char\\\* strdup\\\(" "note" { target *-*-* } .-1 }
     sink (q);
     delete[] q;
     // { dg-warning "'void operator delete \\\[]\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 }
@@ -176,13 +176,13 @@  void warn_strdup_delete (const char *s1, const char *s2)
 
 
 /* Verify a warning for an invocation of either form of the delete
-   expression with a pointer returned from a call to strndup().  */
+   expression with a pointer returned from strndup().  */
 
 void warn_strdup_delete (const char *s1, const char *s2, size_t n)
 {
   {
     char *q = strndup (s1, n);
-    // { dg-message "returned from a call to 'char\\\* strndup\\\(" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'char\\\* strndup\\\(" "note" { target *-*-* } .-1 }
     sink (q);
     /* C++98 calls operator delete (void*) but later versions call
        operator delete (void*, size_t).  The difference doesn't matter
@@ -193,7 +193,7 @@  void warn_strdup_delete (const char *s1, const char *s2, size_t n)
 
   {
     char *q = strndup (s2, n);
-    // { dg-message "returned from a call to 'char\\\* strndup\\\(" "note" { target *-*-* } .-1 }
+    // { dg-message "returned from 'char\\\* strndup\\\(" "note" { target *-*-* } .-1 }
     sink (q);
     delete[] q;
     // { dg-warning "'void operator delete \\\[]\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 }
diff --git a/gcc/testsuite/gcc.dg/Wmismatched-dealloc.c b/gcc/testsuite/gcc.dg/Wmismatched-dealloc.c
index 7c5d6acf4d6..38fd44ebdc8 100644
--- a/gcc/testsuite/gcc.dg/Wmismatched-dealloc.c
+++ b/gcc/testsuite/gcc.dg/Wmismatched-dealloc.c
@@ -68,18 +68,18 @@  void nowarn_fdopen (void)
 void warn_fdopen (void)
 {
   {
-    FILE *q = fdopen (0);     // { dg-message "returned from a call to 'fdopen'" "note" }
+    FILE *q = fdopen (0);     // { dg-message "returned from 'fdopen'" "note" }
     sink (q);
     release (q);              // { dg-warning "'release' called on pointer returned from a mismatched allocation function" }
   }
   {
-    FILE *q = fdopen (0);     // { dg-message "returned from a call to 'fdopen'" "note" }
+    FILE *q = fdopen (0);     // { dg-message "returned from 'fdopen'" "note" }
     sink (q);
     free (q);                 // { dg-warning "'free' called on pointer returned from a mismatched allocation function" }
   }
 
   {
-    FILE *q = fdopen (0);     // { dg-message "returned from a call to 'fdopen'" "note" }
+    FILE *q = fdopen (0);     // { dg-message "returned from 'fdopen'" "note" }
     sink (q);
     q = realloc (q, 7);       // { dg-warning "'realloc' called on pointer returned from a mismatched allocation function" }
     sink (q);
@@ -142,7 +142,7 @@  void test_popen (void)
 
   {
     FILE *p;
-    p = popen ("2", "r");     // { dg-message "returned from a call to 'popen'" "note" }
+    p = popen ("2", "r");     // { dg-message "returned from 'popen'" "note" }
     sink (p);
     fclose (p);               // { dg-warning "'fclose' called on pointer returned from a mismatched allocation function" }
   }
@@ -152,7 +152,7 @@  void test_popen (void)
        close the stream returned from freopen().  */
     FILE *p = popen ("2", "r");
     sink (p);
-    p = freopen ("3", "r", p);  // { dg-message "returned from a call to 'freopen'" "note" }
+    p = freopen ("3", "r", p);  // { dg-message "returned from 'freopen'" "note" }
     sink (p);
     pclose (p);               // { dg-warning "'pclose' called on pointer returned from a mismatched allocation function" }
   }
@@ -176,7 +176,7 @@  void test_tmpfile (void)
   }
 
   {
-    FILE *p = tmpfile ();     // { dg-message "returned from a call to 'tmpfile'" "note" }
+    FILE *p = tmpfile ();     // { dg-message "returned from 'tmpfile'" "note" }
     sink (p);
     pclose (p);               // { dg-warning "'pclose' called on pointer returned from a mismatched allocation function" }
   }
@@ -186,19 +186,19 @@  void test_tmpfile (void)
 void warn_malloc (void)
 {
   {
-    FILE *p = malloc (100);   // { dg-message "returned from a call to 'malloc'" "note" }
+    FILE *p = malloc (100);   // { dg-message "returned from 'malloc'" "note" }
     sink (p);
     fclose (p);               // { dg-warning "'fclose' called on pointer returned from a mismatched allocation function" }
   }
 
   {
-    FILE *p = malloc (100);   // { dg-message "returned from a call to 'malloc'" "note" }
+    FILE *p = malloc (100);   // { dg-message "returned from 'malloc'" "note" }
     sink (p);
     p = freopen ("1", "r", p);// { dg-warning "'freopen' called on pointer returned from a mismatched allocation function" }
   }
 
   {
-    FILE *p = malloc (100);   // { dg-message "returned from a call to 'malloc'" "note" }
+    FILE *p = malloc (100);   // { dg-message "returned from 'malloc'" "note" }
     sink (p);
     pclose (p);               // { dg-warning "'pclose' called on pointer returned from a mismatched allocation function" }
   }
@@ -219,32 +219,32 @@  void test_acquire (void)
   }
 
   {
-    FILE *p = acquire ();     // { dg-message "returned from a call to 'acquire'" "note" }
+    FILE *p = acquire ();     // { dg-message "returned from 'acquire'" "note" }
     sink (p);
     fclose (p);               // { dg-warning "'fclose' called on pointer returned from a mismatched allocation function" }
   }
 
   {
-    FILE *p = acquire ();     // { dg-message "returned from a call to 'acquire'" "note" }
+    FILE *p = acquire ();     // { dg-message "returned from 'acquire'" "note" }
     sink (p);
     pclose (p);               // { dg-warning "'pclose' called on pointer returned from a mismatched allocation function" }
   }
 
   {
-    FILE *p = acquire ();     // { dg-message "returned from a call to 'acquire'" "note" }
+    FILE *p = acquire ();     // { dg-message "returned from 'acquire'" "note" }
     sink (p);
     p = freopen ("1", "r", p);  // { dg-warning "'freopen' called on pointer returned from a mismatched allocation function" }
     sink (p);
   }
 
   {
-    FILE *p = acquire ();     // { dg-message "returned from a call to 'acquire'" "note" }
+    FILE *p = acquire ();     // { dg-message "returned from 'acquire'" "note" }
     sink (p);
     free (p);               // { dg-warning "'free' called on pointer returned from a mismatched allocation function" }
   }
 
   {
-    FILE *p = acquire ();     // { dg-message "returned from a call to 'acquire'" "note" }
+    FILE *p = acquire ();     // { dg-message "returned from 'acquire'" "note" }
     sink (p);
     p = realloc (p, 123);     // { dg-warning "'realloc' called on pointer returned from a mismatched allocation function" }
     sink (p);
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 0c969085d1f..e88beb66dbb 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -3262,7 +3262,11 @@  under the control of options such as @option{-Wmismatched-dealloc}.
 To indicate that an allocation function both satisifies the nonaliasing
 property and has a deallocator associated with it, both the plain form
 of the attribute and the one with the @var{deallocator} argument must
-be used.
+be used.  Since inlining one of a the associated functions but not
+the other could result in apparent mismatches, this form of attribute
+@code{malloc} is not accepted on inline functions declared.  For
+the same reason, using the attribute prevents both the allocation
+and deallocation functions from being expanded inline.
 
 For example, besides stating that the functions return pointers that do
 not alias any others, the following declarations make the @code{fclose}
diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-2.C b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-2.C
new file mode 100644
index 00000000000..d0d53b38b93
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-2.C
@@ -0,0 +1,249 @@ 
+/* Verify that implicit and explicit calls to member operator new and delete
+   are handled correctly.
+   { dg-do compile }
+   { dg-options "-Wmismatched-new-delete" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+namespace std
+{
+#if __cplusplus >= 201703L
+enum class align_val_t: size_t { };
+#else
+enum align_val_t { };
+#endif
+
+struct nothrow_t { };
+const nothrow_t nothrow = { };
+}
+
+void sink (void*, ...);
+
+struct POD
+{
+  void* operator new (size_t);
+  void operator delete (void*);
+
+  void* operator new[] (size_t);
+  void operator delete[] (void*);
+};
+
+POD* nowarn_pod ()
+{
+  POD *p = new POD;
+  delete p;
+  return new POD;
+}
+
+void warn_pod_array_mismatch ()
+{
+  POD *p = new POD;
+  delete[] p;                 // { dg-warning "'static void POD::operator delete \\\[]\\(void\\*\\)' called on pointer returned from a mismatched allocation function" }
+  p = new POD[3];
+  delete p;                   // { dg-warning "'static void POD::operator delete\\(void\\*\\)' called on pointer returned from a mismatched allocation function" }
+}
+
+
+struct X1
+{
+  X1 ();
+
+  void* operator new (size_t);
+  void* operator new (size_t, std::align_val_t);
+  void* operator new (size_t, std::nothrow_t) throw ();
+  void* operator new (size_t, std::align_val_t, std::nothrow_t) throw ();
+
+  void* operator new[] (size_t);
+  void* operator new[] (size_t, std::align_val_t);
+  void* operator new[] (size_t, std::nothrow_t) throw ();
+  void* operator new[] (size_t, std::align_val_t, std::nothrow_t) throw ();
+
+  void operator delete (void*);
+  void operator delete (void*, size_t);
+  void operator delete (void*, std::align_val_t);
+  void operator delete (void*, size_t, std::align_val_t);
+  void operator delete (void*, std::nothrow_t) throw ();
+  void operator delete (void*, std::align_val_t, std::nothrow_t) throw ();
+
+  void operator delete[] (void*);
+  void operator delete[] (void*, size_t);
+  void operator delete[] (void*, std::align_val_t);
+  void operator delete[] (void*, size_t, std::align_val_t);
+  void operator delete[] (void*, std::nothrow_t) throw ();
+  void operator delete[] (void*, std::align_val_t, std::nothrow_t) throw ();
+};
+
+X1* nowarn_x1 ()
+{
+  return new X1;
+}
+
+X1* nowarn_x1_array ()
+{
+  return new X1[2];
+}
+
+X1* nowarn_align_val ()
+{
+  X1 *p = new (std::align_val_t (32)) X1;
+  delete p;
+  return new (std::align_val_t (64)) X1;
+}
+
+X1* nowarn_align_val_array ()
+{
+  X1 *p = new (std::align_val_t (32)) X1[2];
+  delete[] p;
+  return new (std::align_val_t (64)) X1[2];
+}
+
+X1* nowarn_x1_nothrow ()
+{
+  X1 *p = new (std::nothrow) X1;
+  delete p;
+  return new (std::nothrow) X1;
+}
+
+X1* nowarn_x1_nothrow_array ()
+{
+  X1 *p = new (std::nothrow) X1[3];
+  delete[] p;
+  return new (std::nothrow) X1[3];
+}
+
+X1* nowarn_align_val_nothrow ()
+{
+  X1 *p = new (std::align_val_t (32), std::nothrow) X1;
+  delete p;
+  return new (std::align_val_t (64), std::nothrow) X1;
+}
+
+X1* nowarn_align_val_nothrow_array ()
+{
+  X1 *p = new (std::align_val_t (32), std::nothrow) X1[4];
+  delete[] p;
+  return new (std::align_val_t (64), std::nothrow) X1[4];
+}
+
+void warn_x1_array_mismatch ()
+{
+  {
+    X1 *p = new X1;
+    delete[] p;               // { dg-warning "'static void X1::operator delete \\\[]\\(void\\*\\)' called on pointer returned from a mismatched allocation function" }
+  }
+  {
+    X1 *p = new X1[2];
+    delete p;                 // { dg-warning "'static void X1::operator delete\\(void\\*\\)' called on pointer returned from a mismatched allocation function" }
+  }
+  {
+    X1 *p = new (std::align_val_t (32)) X1[2];
+    delete p;                 // { dg-warning "'static void X1::operator delete\\(void\\*\\)' called on pointer returned from a mismatched allocation function" }
+  }
+  {
+    // The following requires optimization (see warn_x1_array_mismatch()).
+    X1 *p = new (std::nothrow) X1[3];
+    delete p;                 // { dg-warning "'static void X1::operator delete\\(void\\*\\)' called on pointer returned from a mismatched allocation function" "pr?????" { xfail *-*-* } }
+  }
+}
+
+#pragma GCC push_options
+#pragma GCC optimize "1"
+
+void warn_x1_nothrow_array_mismatch ()
+{
+  X1 *p = new (std::nothrow) X1[3];
+  delete p;                   // { dg-warning "'static void X1::operator delete\\(void\\*\\)' called on pointer returned from a mismatched allocation function" }
+}
+
+#pragma GCC pop_options
+
+
+struct X2: X1
+{
+  X2 ();
+
+  void* operator new (size_t);
+  void operator delete (void*);
+};
+
+X2* nowarn_x2 ()
+{
+  X2 *p = new X2;
+  sink (p);
+  return new X2;
+}
+
+void warn_x2 ()
+{
+  X1 *p = new X2;             // { dg-message "returned from 'static void\\* X2::operator new\\(size_t\\)'" "note" }
+  sink (p);
+  delete p;                   // { dg-warning "'static void X1::operator delete\\(void\\*\\)' called on pointer returned from a mismatched allocation function" }
+}
+
+namespace N {
+namespace NS {
+namespace NmSpc {
+namespace NameSpace {
+
+namespace dl {   // same name as operator delete
+namespace nw {   // and as operator new
+
+struct X3: X2
+{
+  X3 ();
+
+  void* operator new (size_t);
+  void operator delete (void*);
+};
+
+X3* nowarn_x3 ()
+{
+  X3 *p = new X3;
+  sink (p);
+  return new X3;
+}
+
+void warn_x3 ()
+{
+  X1 *p = new X3;             // { dg-message "returned from 'static void\\* N::NS::NmSpc::NameSpace::dl::nw::X3::operator new\\(size_t\\)'" "note" }
+  sink (p);
+  delete p;                   // { dg-warning "'static void X1::operator delete\\(void\\*\\)' called on pointer returned from a mismatched allocation function" }
+}
+
+template <int N>
+struct X4: X2
+{
+  X4 ();
+
+  void* operator new (size_t);
+  void operator delete (void*);
+};
+
+void* nowarn_x4 ()
+{
+  X4<0> *p = new X4<0>;
+  sink (p);
+  return new X4<1>;
+}
+
+void warn_x4 ()
+{
+  X1 *p = new X4<1>;          // { dg-message "returned from 'static void\\* N::NS::NmSpc::NameSpace::dl::nw::X4<N>::operator new\\(size_t\\) \\\[with int N = 1]'" "note" }
+  sink (p);
+  delete p;                   // { dg-warning "'static void X1::operator delete\\(void\\*\\)' called on pointer returned from a mismatched allocation function" }
+}
+
+void warn_x4_inst_mismatch ()
+{
+  void *p = new X4<2>;        // { dg-message "returned from 'static void\\* N::NS::NmSpc::NameSpace::dl::nw::X4<N>::operator new\\(size_t\\) \\\[with int N = 2]'" "note" }
+  sink (p);
+  X4<3> *q = (X4<3>*)p;
+  delete q;                   // { dg-warning "'static void N::NS::NmSpc::NameSpace::dl::nw::X4<N>::operator delete\\(void\\*\\) \\\[with int N = 3]' called on pointer returned from a mismatched allocation function" }
+}
+
+}   // nw
+}   // dl
+}   // NameSpace
+}   // NmSpc
+}   // NS
+}   // N
diff --git a/libstdc++-v3/testsuite/ext/vstring/requirements/exception/basic.cc b/libstdc++-v3/testsuite/ext/vstring/requirements/exception/basic.cc
index 1f4ba020b3b..5036be50724 100644
--- a/libstdc++-v3/testsuite/ext/vstring/requirements/exception/basic.cc
+++ b/libstdc++-v3/testsuite/ext/vstring/requirements/exception/basic.cc
@@ -48,3 +48,7 @@  int main()
   value();
   return 0;
 }
+
+// The __versa_string destructor triggers a bogus -Wfree-nonheap-object
+// due to pr54202.
+// { dg-prune-output "\\\[-Wfree-nonheap-object" }
diff --git a/libstdc++-v3/testsuite/ext/vstring/requirements/exception/propagation_consistent.cc b/libstdc++-v3/testsuite/ext/vstring/requirements/exception/propagation_consistent.cc
index add7a922cdf..61e9aedb7e6 100644
--- a/libstdc++-v3/testsuite/ext/vstring/requirements/exception/propagation_consistent.cc
+++ b/libstdc++-v3/testsuite/ext/vstring/requirements/exception/propagation_consistent.cc
@@ -48,3 +48,7 @@  int main()
   value();
   return 0;
 }
+
+// The __versa_string destructor triggers a bogus -Wfree-nonheap-object
+// due to pr54202.
+// { dg-prune-output "\\\[-Wfree-nonheap-object" }