[gcc r11-6028] Correct/improve maybe_emit_free_warning (PR middle-end/98166, PR c++/57111, PR middle-end/98160).

Martin Sebor msebor@gcc.gnu.org
Mon Dec 14 20:31:06 GMT 2020


https://gcc.gnu.org/g:fe7f75cf16783589eedbab597e6d0b8d35d7e470

commit r11-6028-gfe7f75cf16783589eedbab597e6d0b8d35d7e470
Author: Martin Sebor <msebor@redhat.com>
Date:   Mon Dec 14 13:30:00 2020 -0700

    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 (check_access): Call tree_inlined_location
            fndecl_alloc_p): Handle BUILT_IN_ALIGNED_ALLOC and
            BUILT_IN_GOMP_ALLOC.
            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.
            * 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.
    
    gcc/c-family/ChangeLog:
    
            PR middle-end/98166
            PR c++/57111
            PR middle-end/98160
            * c-attribs.c (maybe_add_noinline): New function.
            (handle_malloc_attribute): Call it.  Use ATTR_FLAG_INTERNAL.
            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-dealloc-2.C: New test.
            * g++.dg/warn/Wmismatched-new-delete-2.C: New test.
            * g++.dg/warn/Wmismatched-new-delete.C: New test.
            * gcc.dg/Wmismatched-dealloc-2.c: New test.
            * gcc.dg/Wmismatched-dealloc-3.c: New test.
            * gcc.dg/Wmismatched-dealloc.c: New test.

Diff:
---
 gcc/builtins.c                                     | 388 +++++++++++++++++----
 gcc/c-family/c-attribs.c                           | 125 +++++--
 gcc/doc/extend.texi                                |  44 +--
 .../c-c++-common/Wfree-nonheap-object-2.c          |  52 +++
 .../c-c++-common/Wfree-nonheap-object-3.c          |  70 ++++
 gcc/testsuite/c-c++-common/Wfree-nonheap-object.c  |  50 +++
 gcc/testsuite/c-c++-common/Wmismatched-dealloc.c   |  67 ++++
 gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-3.C |  38 ++
 gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-4.C |  26 ++
 gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C  |  20 +-
 .../g++.dg/warn/Wmismatched-new-delete-2.C         | 249 +++++++++++++
 gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C |  34 +-
 gcc/testsuite/gcc.dg/Wmismatched-dealloc-2.c       | 141 ++++++++
 gcc/testsuite/gcc.dg/Wmismatched-dealloc-3.c       | 265 ++++++++++++++
 gcc/testsuite/gcc.dg/Wmismatched-dealloc.c         | 163 ++++++---
 gcc/tree-ssa-dce.c                                 |  62 +---
 gcc/tree.c                                         | 103 +++++-
 gcc/tree.h                                         |   2 +
 .../ext/vstring/requirements/exception/basic.cc    |   4 +
 .../exception/propagation_consistent.cc            |   4 +
 20 files changed, 1657 insertions(+), 250 deletions(-)

diff --git a/gcc/builtins.c b/gcc/builtins.c
index faa5030853b..28e44445ab2 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -4738,9 +4738,7 @@ check_access (tree exp, tree dstwrite,
       && TREE_CODE (range[0]) == INTEGER_CST
       && tree_int_cst_lt (maxobjsize, range[0]))
     {
-      location_t loc = tree_nonartificial_location (exp);
-      loc = expansion_point_location_if_in_system_header (loc);
-
+      location_t loc = tree_inlined_location (exp);
       maybe_warn_for_bound (OPT_Wstringop_overflow_, loc, exp, func, range,
 			    NULL_TREE, pad);
       return false;
@@ -4766,9 +4764,7 @@ check_access (tree exp, tree dstwrite,
 	      || (pad && pad->dst.ref && TREE_NO_WARNING (pad->dst.ref)))
 	    return false;
 
-	  location_t loc = tree_nonartificial_location (exp);
-	  loc = expansion_point_location_if_in_system_header (loc);
-
+	  location_t loc = tree_inlined_location (exp);
 	  bool warned = false;
 	  if (dstwrite == slen && at_least_one)
 	    {
@@ -4821,9 +4817,7 @@ check_access (tree exp, tree dstwrite,
 	 PAD is nonnull and BNDRNG is valid.  */
       get_size_range (maxread, range, pad ? pad->src.bndrng : NULL);
 
-      location_t loc = tree_nonartificial_location (exp);
-      loc = expansion_point_location_if_in_system_header (loc);
-
+      location_t loc = tree_inlined_location (exp);
       tree size = dstsize;
       if (pad && pad->mode == access_read_only)
 	size = wide_int_to_tree (sizetype, pad->src.sizrng[1]);
@@ -4882,9 +4876,7 @@ check_access (tree exp, tree dstwrite,
 	  || (pad && pad->src.ref && TREE_NO_WARNING (pad->src.ref)))
 	return false;
 
-      location_t loc = tree_nonartificial_location (exp);
-      loc = expansion_point_location_if_in_system_header (loc);
-
+      location_t loc = tree_inlined_location (exp);
       const bool read
 	= mode == access_read_only || mode == access_read_write;
       const bool maybe = pad && pad->dst.parmarray;
@@ -6381,9 +6373,7 @@ check_strncat_sizes (tree exp, tree objsize)
   if (tree_fits_uhwi_p (maxread) && tree_fits_uhwi_p (objsize)
       && tree_int_cst_equal (objsize, maxread))
     {
-      location_t loc = tree_nonartificial_location (exp);
-      loc = expansion_point_location_if_in_system_header (loc);
-
+      location_t loc = tree_inlined_location (exp);
       warning_at (loc, OPT_Wstringop_overflow_,
 		  "%K%qD specified bound %E equals destination size",
 		  exp, get_callee_fndecl (exp), maxread);
@@ -6456,9 +6446,7 @@ expand_builtin_strncat (tree exp, rtx)
   if (tree_fits_uhwi_p (maxread) && tree_fits_uhwi_p (destsize)
       && tree_int_cst_equal (destsize, maxread))
     {
-      location_t loc = tree_nonartificial_location (exp);
-      loc = expansion_point_location_if_in_system_header (loc);
-
+      location_t loc = tree_inlined_location (exp);
       warning_at (loc, OPT_Wstringop_overflow_,
 		  "%K%qD specified bound %E equals destination size",
 		  exp, get_callee_fndecl (exp), maxread);
@@ -7040,9 +7028,7 @@ expand_builtin_strncmp (tree exp, ATTRIBUTE_UNUSED rtx target,
       || !check_nul_terminated_array (exp, arg2, arg3))
     return NULL_RTX;
 
-  location_t loc = tree_nonartificial_location (exp);
-  loc = expansion_point_location_if_in_system_header (loc);
-
+  location_t loc = tree_inlined_location (exp);
   tree len1 = c_strlen (arg1, 1);
   tree len2 = c_strlen (arg2, 1);
 
@@ -12970,7 +12956,9 @@ fndecl_alloc_p (tree fndecl, bool all_alloc)
 	case BUILT_IN_ALLOCA:
 	case BUILT_IN_ALLOCA_WITH_ALIGN:
 	  return all_alloc;
+	case BUILT_IN_ALIGNED_ALLOC:
 	case BUILT_IN_CALLOC:
+	case BUILT_IN_GOMP_ALLOC:
 	case BUILT_IN_MALLOC:
 	case BUILT_IN_REALLOC:
 	case BUILT_IN_STRDUP:
@@ -13065,12 +13053,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
@@ -13080,18 +13175,17 @@ call_dealloc_p (tree exp)
 static bool
 matching_alloc_calls_p (tree alloc_decl, tree dealloc_decl)
 {
+  /* Set to alloc_kind_t::builtin if ALLOC_DECL is associated with
+     a built-in deallocator.  */
+  enum class alloc_kind_t { none, builtin, user }
+  alloc_dealloc_kind = alloc_kind_t::none;
+
   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.  */
@@ -13110,7 +13204,9 @@ matching_alloc_calls_p (tree alloc_decl, tree dealloc_decl)
 	case BUILT_IN_ALLOCA_WITH_ALIGN:
 	  return false;
 
+	case BUILT_IN_ALIGNED_ALLOC:
 	case BUILT_IN_CALLOC:
+	case BUILT_IN_GOMP_ALLOC:
 	case BUILT_IN_MALLOC:
 	case BUILT_IN_REALLOC:
 	case BUILT_IN_STRDUP:
@@ -13121,6 +13217,8 @@ matching_alloc_calls_p (tree alloc_decl, tree dealloc_decl)
 	  if (fndecl_built_in_p (dealloc_decl, BUILT_IN_FREE)
 	      || fndecl_built_in_p (dealloc_decl, BUILT_IN_REALLOC))
 	    return true;
+
+	  alloc_dealloc_kind = alloc_kind_t::builtin;
 	  break;
 
 	default:
@@ -13128,30 +13226,151 @@ 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;
+  /* Set if DEALLOC_DECL both allocates and deallocates.  */
+  alloc_kind_t realloc_kind = alloc_kind_t::none;
+
+  if (fndecl_built_in_p (dealloc_decl, BUILT_IN_NORMAL))
+    {
+      built_in_function dealloc_code = DECL_FUNCTION_CODE (dealloc_decl);
+      if (dealloc_code == BUILT_IN_REALLOC)
+	realloc_kind = alloc_kind_t::builtin;
+
+      for (tree amats = DECL_ATTRIBUTES (alloc_decl);
+	   (amats = lookup_attribute ("malloc", amats));
+	   amats = TREE_CHAIN (amats))
+	{
+	  tree args = TREE_VALUE (amats);
+	  if (!args)
+	    continue;
+
+	  tree fndecl = TREE_VALUE (args);
+	  if (!fndecl || !DECL_P (fndecl))
+	    continue;
+
+	  if (fndecl_built_in_p (fndecl, BUILT_IN_NORMAL)
+	      && dealloc_code == DECL_FUNCTION_CODE (fndecl))
+	    return true;
+	}
+    }
+
+  const bool alloc_builtin = fndecl_built_in_p (alloc_decl, BUILT_IN_NORMAL);
+  alloc_kind_t realloc_dealloc_kind = alloc_kind_t::none;
 
-  for (tree funs = attrs;
-       (funs = lookup_attribute ("*dealloc", funs));
-       funs = TREE_CHAIN (funs))
+  /* If DEALLOC_DECL has an internal "*dealloc" attribute scan the list
+     of its associated allocation functions for ALLOC_DECL.
+     If the corresponding ALLOC_DECL is found they're a matching pair,
+     otherwise they're not.
+     With DDATS set to the Deallocator's *Dealloc ATtributes...  */
+  for (tree ddats = DECL_ATTRIBUTES (dealloc_decl);
+       (ddats = lookup_attribute ("*dealloc", ddats));
+       ddats = TREE_CHAIN (ddats))
     {
-      tree args = TREE_VALUE (funs);
+      tree args = TREE_VALUE (ddats);
       if (!args)
 	continue;
 
-      tree fname = TREE_VALUE (args);
-      if (!fname)
+      tree alloc = TREE_VALUE (args);
+      if (!alloc)
 	continue;
 
-      if (fname == DECL_NAME (alloc_decl))
+      if (alloc == DECL_NAME (dealloc_decl))
+	realloc_kind = alloc_kind_t::user;
+
+      if (DECL_P (alloc))
+	{
+	  gcc_checking_assert (fndecl_built_in_p (alloc, BUILT_IN_NORMAL));
+
+	  switch (DECL_FUNCTION_CODE (alloc))
+	    {
+	    case BUILT_IN_ALIGNED_ALLOC:
+	    case BUILT_IN_CALLOC:
+	    case BUILT_IN_GOMP_ALLOC:
+	    case BUILT_IN_MALLOC:
+	    case BUILT_IN_REALLOC:
+	    case BUILT_IN_STRDUP:
+	    case BUILT_IN_STRNDUP:
+	      realloc_dealloc_kind = alloc_kind_t::builtin;
+	      break;
+	    default:
+	      break;
+	    }
+
+	  if (!alloc_builtin)
+	    continue;
+
+	  if (DECL_FUNCTION_CODE (alloc) != DECL_FUNCTION_CODE (alloc_decl))
+	    continue;
+
+	  return true;
+	}
+
+      if (alloc == DECL_NAME (alloc_decl))
 	return true;
     }
 
-  return false;
+  if (realloc_kind == alloc_kind_t::none)
+    return false;
+
+  hash_set<tree> common_deallocs;
+  /* Special handling for deallocators.  Iterate over both the allocator's
+     and the reallocator's associated deallocator functions looking for
+     the first one in common.  If one is found, the de/reallocator is
+     a match for the allocator even though the latter isn't directly
+     associated with the former.  This simplifies declarations in system
+     headers.
+     With AMATS set to the Allocator's Malloc ATtributes,
+     and  RMATS set to Reallocator's Malloc ATtributes...  */
+  for (tree amats = DECL_ATTRIBUTES (alloc_decl),
+	 rmats = DECL_ATTRIBUTES (dealloc_decl);
+       (amats = lookup_attribute ("malloc", amats))
+	 || (rmats = lookup_attribute ("malloc", rmats));
+       amats = amats ? TREE_CHAIN (amats) : NULL_TREE,
+	 rmats = rmats ? TREE_CHAIN (rmats) : NULL_TREE)
+    {
+      if (tree args = amats ? TREE_VALUE (amats) : NULL_TREE)
+	if (tree adealloc = TREE_VALUE (args))
+	  {
+	    if (DECL_P (adealloc)
+		&& fndecl_built_in_p (adealloc, BUILT_IN_NORMAL))
+	      {
+		built_in_function fncode = DECL_FUNCTION_CODE (adealloc);
+		if (fncode == BUILT_IN_FREE || fncode == BUILT_IN_REALLOC)
+		  {
+		    if (realloc_kind == alloc_kind_t::builtin)
+		      return true;
+		    alloc_dealloc_kind = alloc_kind_t::builtin;
+		  }
+		continue;
+	      }
+
+	    common_deallocs.add (adealloc);
+	  }
+
+      if (tree args = rmats ? TREE_VALUE (rmats) : NULL_TREE)
+	if (tree ddealloc = TREE_VALUE (args))
+	  {
+	    if (DECL_P (ddealloc)
+		&& fndecl_built_in_p (ddealloc, BUILT_IN_NORMAL))
+	      {
+		built_in_function fncode = DECL_FUNCTION_CODE (ddealloc);
+		if (fncode == BUILT_IN_FREE || fncode == BUILT_IN_REALLOC)
+		  {
+		    if (alloc_dealloc_kind == alloc_kind_t::builtin)
+		      return true;
+		    realloc_dealloc_kind = alloc_kind_t::builtin;
+		  }
+		continue;
+	      }
+
+	    if (common_deallocs.add (ddealloc))
+	      return true;
+	  }
+    }
+
+  /* Succeed only if ALLOC_DECL and the reallocator DEALLOC_DECL share
+     a built-in deallocator.  */
+  return  (alloc_dealloc_kind == alloc_kind_t::builtin
+	   && realloc_dealloc_kind == alloc_kind_t::builtin);
 }
 
 /* Return true if DEALLOC_DECL is a function suitable to deallocate
@@ -13167,15 +13386,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 +13432,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 +13442,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 +13487,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 +13497,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 +13541,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 +13564,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 +13583,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..29e26728300 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -3130,12 +3130,64 @@ handle_no_profile_instrument_function_attribute (tree *node, tree name, tree,
   return NULL_TREE;
 }
 
+/* If ALLOC_DECL and DEALLOC_DECL are a pair of user-defined functions,
+   if they are declared inline issue warnings and return null.  Otherwise
+   create attribute noinline, install it in ALLOC_DECL, and return it.
+   Otherwise return null. */
+
+static tree
+maybe_add_noinline (tree name, tree alloc_decl, tree dealloc_decl,
+		    bool *no_add_attrs)
+{
+  if (fndecl_built_in_p (alloc_decl) || fndecl_built_in_p (dealloc_decl))
+    return NULL_TREE;
+
+  /* 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 (alloc_decl))
+      || lookup_attribute ("always_inline", DECL_ATTRIBUTES (alloc_decl)))
+    {
+      warning (OPT_Wattributes,
+	       "%<%E (%E)%> attribute ignored on functions "
+	       "declared %qs", name, DECL_NAME (dealloc_decl), "inline");
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  if ((optimize && DECL_DECLARED_INLINE_P (dealloc_decl))
+      || lookup_attribute ("always_inline", DECL_ATTRIBUTES (dealloc_decl)))
+    {
+      warning (OPT_Wattributes,
+	       "%<%E (%E)%> attribute ignored with deallocation "
+	       "functions declared %qs",
+	       name, DECL_NAME (dealloc_decl), "inline");
+      inform (DECL_SOURCE_LOCATION (dealloc_decl),
+	      "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.  */
+  tree attr = tree_cons (get_identifier ("noinline"), NULL_TREE, NULL_TREE);
+  decl_attributes (&alloc_decl, attr, 0);
+  return attr;
+}
+
 /* Handle the "malloc" attribute.  */
 
 static tree
-handle_malloc_attribute (tree *node, tree name, tree args,
-			 int ARG_UNUSED (flags), bool *no_add_attrs)
+handle_malloc_attribute (tree *node, tree name, tree args, int flags,
+			 bool *no_add_attrs)
 {
+  if (flags & ATTR_FLAG_INTERNAL)
+    /* Recursive call.  */
+    return NULL_TREE;
+
   tree fndecl = *node;
 
   if (TREE_CODE (*node) != FUNCTION_DECL)
@@ -3174,11 +3226,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)
     {
@@ -3233,10 +3295,21 @@ handle_malloc_attribute (tree *node, tree name, tree args,
 	  return NULL_TREE;
 	}
 
-      *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);
+      /* Disable inlining for non-standard deallocators to avoid false
+	 positives (or warn if either function is explicitly inline).  */
+      tree at_noinline =
+	maybe_add_noinline (name, fndecl, dealloc, no_add_attrs);
+      if (*no_add_attrs)
+	return NULL_TREE;
+
+      /* Add attribute *dealloc to the deallocator function associating
+	 it with this one.  Ideally, the attribute would reference
+	 the DECL of the deallocator but since that changes for each
+	 redeclaration, use DECL_NAME instead.  (DECL_ASSEMBLER_NAME
+	 need not be set set this point and setting it here is too early.  */
+      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;
     }
 
@@ -3248,15 +3321,21 @@ handle_malloc_attribute (tree *node, tree name, tree args,
       return NULL_TREE;
     }
 
+  /* As above, disable inlining for non-standard deallocators to avoid
+     false positives (or warn).  */
+  tree at_noinline =
+    maybe_add_noinline (name, fndecl, dealloc, no_add_attrs);
+  if (*no_add_attrs)
+    return NULL_TREE;
+
   /* It's valid to declare the same function with multiple instances
      of attribute malloc, each naming the same or different deallocator
      functions, and each referencing either the same or a different
      positional argument.  */
-  *no_add_attrs = false;
-  tree attr_free = tree_cons (NULL_TREE, argpos, NULL_TREE);
-  attr_free = tree_cons (NULL_TREE, DECL_NAME (fndecl), attr_free);
-  attr_free = build_tree_list (get_identifier ("*dealloc"), attr_free);
-  decl_attributes (&dealloc, attr_free, 0);
+  tree attrs = tree_cons (NULL_TREE, argpos, NULL_TREE);
+  attrs = tree_cons (NULL_TREE, DECL_NAME (fndecl), attrs);
+  attrs = tree_cons (get_identifier ("*dealloc"), attrs, at_noinline);
+  decl_attributes (&dealloc, attrs, 0);
   return NULL_TREE;
 }
 
@@ -3274,11 +3353,13 @@ handle_dealloc_attribute (tree *node, tree name, tree args, int,
   if (!attrs)
     return NULL_TREE;
 
-  tree arg_fname = TREE_VALUE (args);
+  tree arg = TREE_VALUE (args);
   args = TREE_CHAIN (args);
-  tree arg_pos = args ? TREE_VALUE (args) : NULL_TREE;
+  tree arg_pos = args ? TREE_VALUE (args) : integer_zero_node;
 
-  gcc_checking_assert (TREE_CODE (arg_fname) == IDENTIFIER_NODE);
+  gcc_checking_assert ((DECL_P (arg)
+			&& fndecl_built_in_p (arg, BUILT_IN_NORMAL))
+		       || TREE_CODE (arg) == IDENTIFIER_NODE);
 
   const char* const namestr = IDENTIFIER_POINTER (name);
   for (tree at = attrs; (at = lookup_attribute (namestr, at));
@@ -3290,12 +3371,12 @@ handle_dealloc_attribute (tree *node, tree name, tree args, int,
 
       tree pos = TREE_CHAIN (alloc);
       alloc = TREE_VALUE (alloc);
-      pos = pos ? TREE_VALUE (pos) : NULL_TREE;
-      gcc_checking_assert (TREE_CODE (alloc) == IDENTIFIER_NODE);
+      pos = pos ? TREE_VALUE (pos) : integer_zero_node;
+      gcc_checking_assert ((DECL_P (alloc)
+			    && fndecl_built_in_p (alloc, BUILT_IN_NORMAL))
+			   || TREE_CODE (alloc) == IDENTIFIER_NODE);
 
-      if (alloc == arg_fname
-	  && ((!pos && !arg_pos)
-	      || (pos && arg_pos && tree_int_cst_equal (pos, arg_pos))))
+      if (alloc == arg && tree_int_cst_equal (pos, arg_pos))
 	{
 	  /* The function already has the attribute either without any
 	     arguments or with the same arguments as the attribute that's
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 0c969085d1f..e73464a7f19 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -3257,37 +3257,37 @@ as they may return pointers to storage containing pointers to existing
 objects.
 
 Associating a function with a @var{deallocator} helps detect calls to
-mismatched allocation and deallocation functions and diagnose them
-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.
+mismatched allocation and deallocation functions and diagnose them 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.  The same
+function can be both an allocator and a deallocator.  Since inlining one
+of the associated functions but not the other could result in apparent
+mismatches, this form of attribute @code{malloc} is not accepted on inline
+functions.  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}
-and @code{frepen} functions suitable deallocators for pointers returned
-from all the functions that return them, and the @code{pclose} function
-as the only other suitable deallocator besides @code{freopen} for pointers
-returned from @code{popen}.  The deallocator functions must declared
-before they can be referenced in the attribute.
+not alias any others, the following declarations make @code{fclose}
+a suitable deallocator for pointers returned from all functions except
+@code{popen}, and @code{pclose} as the only suitable deallocator for
+pointers returned from @code{popen}.  The deallocator functions must
+declared before they can be referenced in the attribute.
 
 @smallexample
-int   fclose (FILE*);
-FILE* freopen (const char*, const char*, FILE*);
-int   pclose (FILE*);
+int fclose (FILE*);
+int pclose (FILE*);
 
-__attribute__ ((malloc, malloc (fclose), malloc (freopen, 3)))
+__attribute__ ((malloc, malloc (fclose (1))))
   FILE* fdopen (int);
-__attribute__ ((malloc, malloc (fclose), malloc (freopen, 3)))
+__attribute__ ((malloc, malloc (fclose (1))))
   FILE* fopen (const char*, const char*);
-__attribute__ ((malloc, malloc (fclose), malloc (freopen, 3)))
+__attribute__ ((malloc, malloc (fclose (1))))
   FILE* fmemopen(void *, size_t, const char *);
-__attribute__ ((malloc, malloc (fclose), malloc (freopen, 3)))
-  FILE* freopen (const char*, const char*, FILE*);
-__attribute__ ((malloc, malloc (pclose), malloc (freopen, 3)))
+__attribute__ ((malloc, malloc (pclose (1))))
   FILE* popen (const char*, const char*);
-__attribute__ ((malloc, malloc (fclose), malloc (freopen, 3)))
+__attribute__ ((malloc, malloc (fclose (1))))
   FILE* tmpfile (void);
 @end smallexample
 
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..27af2c2316b
--- /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, 1)))
+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 (int, long *p) /* { free (p - 2); } */;
+
+__attribute__ ((malloc (dealloc_long, 2)))
+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 (0, p);          // { dg-bogus "\\\[-Wmismatched-dealloc" }
+}
+
+
+inline void
+dealloc_float (int, int, float *p)  // { dg-message "deallocation function declared here" }
+{
+  __builtin_free (p - 3);
+}
+
+__attribute__ ((malloc (dealloc_float, 3)))
+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 (0, 1, 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-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/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-2.c b/gcc/testsuite/gcc.dg/Wmismatched-dealloc-2.c
new file mode 100644
index 00000000000..21a5ea7c5da
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wmismatched-dealloc-2.c
@@ -0,0 +1,141 @@
+/* PR middle-end/94527 - Add an attribute that marks a function as freeing
+   an object
+   Verify that attribute malloc with one or two arguments has the expected
+   effect on diagnostics.
+   { dg-options "-Wall -ftrack-macro-expansion=0" } */
+
+#define A(...) __attribute__ ((malloc (__VA_ARGS__), noipa))
+
+typedef __SIZE_TYPE__ size_t;
+typedef struct A A;
+typedef struct B B;
+
+/* A pointer returned by any of the four functions must be deallocated
+   either by dealloc() or by realloc_{A,B}().  */
+A (__builtin_free) A* alloc_A (int);
+A (__builtin_free) B* alloc_B (int);
+A (__builtin_free) A* realloc_A (A *p, int n) { return p; }
+A (__builtin_free) B* realloc_B (B *p, int n) { return p; }
+
+A (realloc_A) A* alloc_A (int);
+A (realloc_B) B* alloc_B (int);
+A (realloc_A) A* realloc_A (A*, int);
+A (realloc_B) B* realloc_B (B*, int);
+
+void dealloc (void*);
+A (dealloc) void* alloc (int);
+
+void sink (void*);
+
+void test_alloc_A (void)
+{
+  {
+    void *p = alloc_A (1);
+    p = realloc_A (p, 2);
+    __builtin_free (p);
+  }
+
+  {
+    void *p = alloc_A (1);
+    /* Verify that calling realloc doesn't trigger a warning even though
+       alloc_A is not directly associated with it.  */
+    p = __builtin_realloc (p, 2);
+    sink (p);
+  }
+
+  {
+    void *p = alloc_A (1);              // { dg-message "returned from 'alloc_A'" }
+    dealloc (p);                        // { dg-warning "'dealloc' called on pointer returned from a mismatched allocation function" }
+  }
+
+  {
+    /* Because alloc_A() and realloc_B() share free() as a deallocator
+       they must also be valid as each other's deallocators.  */
+    void *p = alloc_A (1);
+    p = realloc_B ((B*)p, 2);
+    __builtin_free (p);
+  }
+
+  {
+    void *p = alloc_A (1);
+    p = realloc_A (p, 2);
+    p = __builtin_realloc (p, 3);
+    __builtin_free (p);
+  }
+}
+
+
+void test_realloc_A (void *ptr)
+{
+  {
+    void *p = realloc_A (0, 1);
+    p = realloc_A (p, 2);
+    __builtin_free (p);
+  }
+
+  {
+    void *p = realloc_A (ptr, 2);
+    p = realloc_A (p, 2);
+    __builtin_free (p);
+  }
+
+  {
+    void *p = realloc_A (0, 3);
+    p = __builtin_realloc (p, 2);
+    sink (p);
+  }
+
+  {
+    void *p = realloc_A (0, 4);         // { dg-message "returned from 'realloc_A'" }
+    dealloc (p);                        // { dg-warning "'dealloc' called on pointer returned from a mismatched allocation function" }
+  }
+
+  {
+    /* Because realloc_A() and realloc_B() share free() as a deallocator
+       they must also be valid as each other's deallocators.  */
+    void *p = realloc_A (0, 5);
+    p = realloc_B ((B*)p, 2);
+    __builtin_free (p);
+  }
+
+  {
+    void *p = realloc_A (0, 6);
+    p = realloc_A ((A*)p, 2);
+    p = __builtin_realloc (p, 3);
+    __builtin_free (p);
+  }
+}
+
+
+void test_realloc (void *ptr)
+{
+  extern void free (void*);
+  extern void* realloc (void*, size_t);
+
+  {
+    void *p = realloc (ptr, 1);
+    p = realloc_A (p, 2);
+    __builtin_free (p);
+  }
+
+  {
+    void *p = realloc (ptr, 2);
+    p = realloc_A (p, 2);
+    free (p);
+  }
+
+  {
+    void *p = realloc (ptr, 3);
+    free (p);
+  }
+
+  {
+    void *p = realloc (ptr, 4);
+    __builtin_free (p);
+  }
+
+  {
+    void *p = realloc (ptr, 5);         // { dg-message "returned from 'realloc'" }
+    dealloc (p);                        // { dg-warning "'dealloc' called on pointer returned from a mismatched allocation function" }
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/Wmismatched-dealloc-3.c b/gcc/testsuite/gcc.dg/Wmismatched-dealloc-3.c
new file mode 100644
index 00000000000..5afcea39b5e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wmismatched-dealloc-3.c
@@ -0,0 +1,265 @@
+/* Verify that Glibc <stdlib.h> declarations are handled correctly
+   { dg-do compile }
+   { dg-options "-Wall" } */
+
+#define A(...) __attribute__ ((malloc (__VA_ARGS__), noipa))
+
+typedef __SIZE_TYPE__ size_t;
+
+/* All functions with the same standard deallocator are associated
+   with each other.  */
+void free (void*);
+void* calloc (size_t, size_t);
+void* malloc (size_t);
+void* realloc (void*, size_t);
+
+A (__builtin_free) void* aligned_alloc (size_t, size_t);
+
+/* Like realloc(), reallocarray() is both an allocator and a deallocator.
+   It must be associated with both free() and with itself, but nothing
+   else.  */
+A (__builtin_free) void* reallocarray (void*, size_t, size_t);
+A (reallocarray) void* reallocarray (void*, size_t, size_t);
+
+A (__builtin_free) extern char *canonicalize_file_name (const char*);
+
+
+void dealloc (void*);
+A (dealloc) void* alloc (size_t);
+
+
+void sink (void*);
+void* source (void);
+
+
+void test_builtin_aligned_alloc (void *p)
+{
+  {
+    void *q = __builtin_aligned_alloc (1, 2);
+    sink (q);
+    __builtin_free (q);
+  }
+
+  {
+    void *q = __builtin_aligned_alloc (1, 2);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = __builtin_aligned_alloc (1, 2);
+    q = __builtin_realloc (q, 3);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = __builtin_aligned_alloc (1, 2);
+    q = realloc (q, 3);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q;
+    q = __builtin_aligned_alloc (1, 2); // { dg-message "returned from '__builtin_aligned_alloc'" }
+    sink (q);
+    dealloc (q);                        // { dg-warning "'dealloc' called on pointer returned from a mismatched allocation function" }
+  }
+}
+
+
+void test_aligned_alloc (void *p)
+{
+  {
+    void *q = aligned_alloc (1, 2);
+    sink (q);
+    __builtin_free (q);
+  }
+
+  {
+    void *q = aligned_alloc (1, 2);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = aligned_alloc (1, 2);
+    q = __builtin_realloc (q, 3);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = aligned_alloc (1, 2);
+    q = realloc (q, 3);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = aligned_alloc (1, 2);     // { dg-message "returned from 'aligned_alloc'" }
+    sink (q);
+    dealloc (q);                        // { dg-warning "'dealloc' called on pointer returned from a mismatched allocation function" }
+  }
+}
+
+
+void test_reallocarray (void *p)
+{
+  {
+    void *q = __builtin_aligned_alloc (1, 2);
+    q = reallocarray (q, 2, 3);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = aligned_alloc (1, 2);
+    q = reallocarray (q, 2, 3);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = __builtin_calloc (1, 2);
+    q = reallocarray (q, 2, 3);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = calloc (1, 2);
+    q = reallocarray (q, 2, 3);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = __builtin_malloc (1);
+    q = reallocarray (q, 2, 3);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = malloc (1);
+    q = reallocarray (q, 2, 3);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = __builtin_realloc (p, 1);
+    q = reallocarray (q, 2, 3);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = realloc (p, 1);
+    q = reallocarray (q, 2, 3);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = __builtin_strdup ("abc");
+    q = reallocarray (q, 3, 4);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = __builtin_strndup ("abcd", 3);
+    q = reallocarray (q, 4, 5);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = source ();
+    q = reallocarray (q, 5, 6);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = alloc (1);                // { dg-message "returned from 'alloc'" }
+    q = reallocarray (q, 6, 7);         // { dg-warning "'reallocarray' called on pointer returned from a mismatched allocation function" }
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = reallocarray (p, 7, 8);
+    q = __builtin_realloc (q, 9);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = reallocarray (p, 7, 8);
+    q = realloc (q, 9);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = reallocarray (p, 8, 9);
+    q = reallocarray (q, 3, 4);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = reallocarray (p, 9, 10);
+    q = reallocarray (q, 3, 4);
+    sink (q);
+    dealloc (q);                        // { dg-warning "'dealloc' called on pointer returned from a mismatched allocation function" }
+  }
+}
+
+
+void test_canonicalize_filename (void *p)
+{
+  {
+    void *q = canonicalize_file_name ("a");
+    sink (q);
+    __builtin_free (q);
+  }
+
+  {
+    void *q = canonicalize_file_name ("b");
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = canonicalize_file_name ("c");
+    q = __builtin_realloc (q, 2);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = canonicalize_file_name ("d");
+    q = realloc (q, 3);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q = canonicalize_file_name ("e");
+    q = reallocarray (q, 4, 5);
+    sink (q);
+    free (q);
+  }
+
+  {
+    void *q;
+    q = canonicalize_file_name ("f");   // { dg-message "returned from 'canonicalize_file_name'" }
+    sink (q);
+    dealloc (q);                        // { dg-warning "'dealloc' called on pointer returned from a mismatched allocation function" }
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/Wmismatched-dealloc.c b/gcc/testsuite/gcc.dg/Wmismatched-dealloc.c
index 7c5d6acf4d6..6336efa5594 100644
--- a/gcc/testsuite/gcc.dg/Wmismatched-dealloc.c
+++ b/gcc/testsuite/gcc.dg/Wmismatched-dealloc.c
@@ -13,28 +13,27 @@ void  free (void*);
 void* malloc (size_t);
 void* realloc (void*, size_t);
 
-int   fclose (FILE*);
-FILE* freopen (const char*, const char*, FILE*);
-int   pclose (FILE*);
-
-A (fclose) A (freopen, 3)
-  FILE* fdopen (int);
-A (fclose) A (freopen, 3)
-  FILE* fopen (const char*, const char*);
-A (fclose) A (freopen, 3)
-  FILE* fmemopen(void *, size_t, const char *);
-A (fclose) A (freopen, 3)
-  FILE* freopen (const char*, const char*, FILE*);
-A (pclose) A (freopen, 3)
-  FILE* popen (const char*, const char*);
-A (fclose) A (freopen, 3)
-  FILE* tmpfile (void);
+/* Declare functions with the minimum attributes malloc how they're
+   likely going to be declared in <stdio.h>.  */
+               int   fclose (FILE*);
+A (fclose)     FILE* fdopen (int);
+A (fclose)     FILE* fopen (const char*, const char*);
+A (fclose)     FILE* fmemopen(void *, size_t, const char *);
+A (fclose)     FILE* freopen (const char*, const char*, FILE*);
+A (freopen, 3) FILE* freopen (const char*, const char*, FILE*);
+A (fclose)     FILE* tmpfile (void);
 
-void sink (FILE*);
+A (fclose)     FILE* open_memstream (char**, size_t*);
+A (fclose)     FILE* open_wmemstream (char**, size_t*);
+
+               int   pclose (FILE*);
+A (pclose)     FILE* popen (const char*, const char*);
 
+               void  release (void*);
+A (release)    FILE* acquire (void);
+
+void sink (FILE*);
 
-            void  release (void*);
-A (release) FILE* acquire (void);
 
 void nowarn_fdopen (void)
 {
@@ -68,18 +67,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);
@@ -132,43 +131,104 @@ void warn_fopen (void)
 }
 
 
-void test_popen (void)
+void test_freopen (FILE *p[])
 {
   {
-    FILE *p = popen ("1", "r");
+    FILE *q = freopen ("1", "r", p[0]);
+    sink (q);
+    fclose (q);
+  }
+  {
+    FILE *q = freopen ("2", "r", p[1]);
+    sink (q);
+    q = freopen ("3", "r", q);
+    sink (q);
+    fclose (q);
+  }
+
+  {
+    FILE *q;
+    q = freopen ("3", "r", p[2]); // { dg-message "returned from 'freopen'" }
+    sink (q);
+    q = realloc (q, 7);       // { dg-warning "'realloc' called on pointer returned from a mismatched allocation function" }
+    sink (q);
+  }
+}
+
+
+void test_tmpfile (void)
+{
+  {
+    FILE *p = tmpfile ();
     sink (p);
-    pclose (p);
+    fclose (p);
   }
 
   {
-    FILE *p;
-    p = popen ("2", "r");     // { dg-message "returned from a call to 'popen'" "note" }
+    FILE *p = tmpfile ();
     sink (p);
-    fclose (p);               // { dg-warning "'fclose' called on pointer returned from a mismatched allocation function" }
+    p = freopen ("1", "r", p);
+    sink (p);
+    fclose (p);
   }
 
   {
-    /* freopen() can close a stream open by popen() but pclose() can't
-       close the stream returned from freopen().  */
-    FILE *p = popen ("2", "r");
+    FILE *p = tmpfile ();     // { dg-message "returned from 'tmpfile'" "note" }
     sink (p);
-    p = freopen ("3", "r", p);  // { dg-message "returned from a call to 'freopen'" "note" }
+    pclose (p);               // { dg-warning "'pclose' called on pointer returned from a mismatched allocation function" }
+  }
+}
+
+
+void test_open_memstream (char **bufp, size_t *sizep)
+{
+  {
+    FILE *p = open_memstream (bufp, sizep);
+    sink (p);
+    fclose (p);
+  }
+
+  {
+    FILE *p = open_memstream (bufp, sizep);
+    sink (p);
+    p = freopen ("1", "r", p);
+    sink (p);
+    fclose (p);
+  }
+
+  {
+    FILE *p;
+    p = open_memstream (bufp, sizep);   // { dg-message "returned from 'open_memstream'" "note" }
     sink (p);
     pclose (p);               // { dg-warning "'pclose' called on pointer returned from a mismatched allocation function" }
   }
+
+  {
+    FILE *p;
+    p = open_memstream (bufp, sizep);   // { dg-message "returned from 'open_memstream'" "note" }
+    sink (p);
+    free (p);                 // { dg-warning "'free' called on pointer returned from a mismatched allocation function" }
+  }
+
+  {
+    FILE *p;
+    p = open_memstream (bufp, sizep);   // { dg-message "returned from 'open_memstream'" "note" }
+    sink (p);
+    release (p);              // { dg-warning "'release' called on pointer returned from a mismatched allocation function" }
+  }
 }
 
 
-void test_tmpfile (void)
+void test_open_wmemstream (char **bufp, size_t *sizep)
 {
   {
-    FILE *p = tmpfile ();
+    FILE *p = open_wmemstream (bufp, sizep);
     sink (p);
     fclose (p);
   }
 
   {
-    FILE *p = tmpfile ();
+    FILE *p = open_wmemstream (bufp, sizep);
     sink (p);
     p = freopen ("1", "r", p);
     sink (p);
@@ -176,29 +236,44 @@ void test_tmpfile (void)
   }
 
   {
-    FILE *p = tmpfile ();     // { dg-message "returned from a call to 'tmpfile'" "note" }
+    FILE *p;
+    p = open_wmemstream (bufp, sizep);  // { dg-message "returned from 'open_wmemstream'" "note" }
     sink (p);
     pclose (p);               // { dg-warning "'pclose' called on pointer returned from a mismatched allocation function" }
   }
+
+  {
+    FILE *p;
+    p = open_wmemstream (bufp, sizep);  // { dg-message "returned from 'open_wmemstream'" "note" }
+    sink (p);
+    free (p);                 // { dg-warning "'free' called on pointer returned from a mismatched allocation function" }
+  }
+
+  {
+    FILE *p;
+    p = open_wmemstream (bufp, sizep);  // { dg-message "returned from 'open_wmemstream'" "note" }
+    sink (p);
+    release (p);              // { dg-warning "'release' called on pointer returned from a mismatched allocation function" }
+  }
 }
 
 
 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 +294,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/tree-ssa-dce.c b/gcc/tree-ssa-dce.c
index 9fb156c120d..5ec872967b7 100644
--- a/gcc/tree-ssa-dce.c
+++ b/gcc/tree-ssa-dce.c
@@ -656,67 +656,7 @@ valid_new_delete_pair_p (gimple *new_call, gimple *delete_call)
 {
   tree new_asm = DECL_ASSEMBLER_NAME (gimple_call_fndecl (new_call));
   tree delete_asm = DECL_ASSEMBLER_NAME (gimple_call_fndecl (delete_call));
-  const char *new_name = IDENTIFIER_POINTER (new_asm);
-  const char *delete_name = IDENTIFIER_POINTER (delete_asm);
-  unsigned int new_len = IDENTIFIER_LENGTH (new_asm);
-  unsigned int delete_len = IDENTIFIER_LENGTH (delete_asm);
-
-  if (new_len < 5 || delete_len < 6)
-    return false;
-  if (new_name[0] == '_')
-    ++new_name, --new_len;
-  if (new_name[0] == '_')
-    ++new_name, --new_len;
-  if (delete_name[0] == '_')
-    ++delete_name, --delete_len;
-  if (delete_name[0] == '_')
-    ++delete_name, --delete_len;
-  if (new_len < 4 || delete_len < 5)
-    return false;
-  /* *_len is now just the length after initial underscores.  */
-  if (new_name[0] != 'Z' || new_name[1] != 'n')
-    return false;
-  if (delete_name[0] != 'Z' || delete_name[1] != 'd')
-    return false;
-  /* _Znw must match _Zdl, _Zna must match _Zda.  */
-  if ((new_name[2] != 'w' || delete_name[2] != 'l')
-      && (new_name[2] != 'a' || delete_name[2] != 'a'))
-    return false;
-  /* 'j', 'm' and 'y' correspond to size_t.  */
-  if (new_name[3] != 'j' && new_name[3] != 'm' && new_name[3] != 'y')
-    return false;
-  if (delete_name[3] != 'P' || delete_name[4] != 'v')
-    return false;
-  if (new_len == 4
-      || (new_len == 18 && !memcmp (new_name + 4, "RKSt9nothrow_t", 14)))
-    {
-      /* _ZnXY or _ZnXYRKSt9nothrow_t matches
-	 _ZdXPv, _ZdXPvY and _ZdXPvRKSt9nothrow_t.  */
-      if (delete_len == 5)
-	return true;
-      if (delete_len == 6 && delete_name[5] == new_name[3])
-	return true;
-      if (delete_len == 19 && !memcmp (delete_name + 5, "RKSt9nothrow_t", 14))
-	return true;
-    }
-  else if ((new_len == 19 && !memcmp (new_name + 4, "St11align_val_t", 15))
-	   || (new_len == 33
-	       && !memcmp (new_name + 4, "St11align_val_tRKSt9nothrow_t", 29)))
-    {
-      /* _ZnXYSt11align_val_t or _ZnXYSt11align_val_tRKSt9nothrow_t matches
-	 _ZdXPvSt11align_val_t or _ZdXPvYSt11align_val_t or  or
-	 _ZdXPvSt11align_val_tRKSt9nothrow_t.  */
-      if (delete_len == 20 && !memcmp (delete_name + 5, "St11align_val_t", 15))
-	return true;
-      if (delete_len == 21
-	  && delete_name[5] == new_name[3]
-	  && !memcmp (delete_name + 6, "St11align_val_t", 15))
-	return true;
-      if (delete_len == 34
-	  && !memcmp (delete_name + 5, "St11align_val_tRKSt9nothrow_t", 29))
-	return true;
-    }
-  return false;
+  return valid_new_delete_pair_p (new_asm, delete_asm);
 }
 
 /* Propagate necessity using the operands of necessary statements.
diff --git a/gcc/tree.c b/gcc/tree.c
index 9b2ecb34256..5fd9da3ab96 100644
--- a/gcc/tree.c
+++ b/gcc/tree.c
@@ -12610,8 +12610,40 @@ tree_nonartificial_location (tree exp)
     return EXPR_LOCATION (exp);
 }
 
+/* Return the location into which EXP has been inlined.  Analogous
+   to tree_nonartificial_location() above but not limited to artificial
+   functions declared inline.  If SYSTEM_HEADER is true, return
+   the macro expansion point of the location if it's in a system header */
 
-/* These are the hash table functions for the hash table of OPTIMIZATION_NODEq
+location_t
+tree_inlined_location (tree exp, bool system_header /* = true */)
+{
+  location_t loc = UNKNOWN_LOCATION;
+
+  tree block = TREE_BLOCK (exp);
+
+  while (block && TREE_CODE (block) == BLOCK
+	 && BLOCK_ABSTRACT_ORIGIN (block))
+    {
+      tree ao = BLOCK_ABSTRACT_ORIGIN (block);
+      if (TREE_CODE (ao) == FUNCTION_DECL)
+	loc = BLOCK_SOURCE_LOCATION (block);
+      else if (TREE_CODE (ao) != BLOCK)
+	break;
+
+      block = BLOCK_SUPERCONTEXT (block);
+    }
+
+  if (loc == UNKNOWN_LOCATION)
+    loc = EXPR_LOCATION (exp);
+
+  if (system_header)
+    return expansion_point_location_if_in_system_header (loc);
+
+  return loc;
+}
+
+/* These are the hash table functions for the hash table of OPTIMIZATION_NODE
    nodes.  */
 
 /* Return the hash code X, an OPTIMIZATION_NODE or TARGET_OPTION code.  */
@@ -15386,6 +15418,75 @@ verify_type_context (location_t loc, type_context_kind context,
 	  || targetm.verify_type_context (loc, context, type, silent_p));
 }
 
+/* Return that NEW_ASM and DELETE_ASM name a valid pair of new and
+   delete operators.  */
+
+bool
+valid_new_delete_pair_p (tree new_asm, tree delete_asm)
+{
+  const char *new_name = IDENTIFIER_POINTER (new_asm);
+  const char *delete_name = IDENTIFIER_POINTER (delete_asm);
+  unsigned int new_len = IDENTIFIER_LENGTH (new_asm);
+  unsigned int delete_len = IDENTIFIER_LENGTH (delete_asm);
+
+  if (new_len < 5 || delete_len < 6)
+    return false;
+  if (new_name[0] == '_')
+    ++new_name, --new_len;
+  if (new_name[0] == '_')
+    ++new_name, --new_len;
+  if (delete_name[0] == '_')
+    ++delete_name, --delete_len;
+  if (delete_name[0] == '_')
+    ++delete_name, --delete_len;
+  if (new_len < 4 || delete_len < 5)
+    return false;
+  /* *_len is now just the length after initial underscores.  */
+  if (new_name[0] != 'Z' || new_name[1] != 'n')
+    return false;
+  if (delete_name[0] != 'Z' || delete_name[1] != 'd')
+    return false;
+  /* _Znw must match _Zdl, _Zna must match _Zda.  */
+  if ((new_name[2] != 'w' || delete_name[2] != 'l')
+      && (new_name[2] != 'a' || delete_name[2] != 'a'))
+    return false;
+  /* 'j', 'm' and 'y' correspond to size_t.  */
+  if (new_name[3] != 'j' && new_name[3] != 'm' && new_name[3] != 'y')
+    return false;
+  if (delete_name[3] != 'P' || delete_name[4] != 'v')
+    return false;
+  if (new_len == 4
+      || (new_len == 18 && !memcmp (new_name + 4, "RKSt9nothrow_t", 14)))
+    {
+      /* _ZnXY or _ZnXYRKSt9nothrow_t matches
+	 _ZdXPv, _ZdXPvY and _ZdXPvRKSt9nothrow_t.  */
+      if (delete_len == 5)
+	return true;
+      if (delete_len == 6 && delete_name[5] == new_name[3])
+	return true;
+      if (delete_len == 19 && !memcmp (delete_name + 5, "RKSt9nothrow_t", 14))
+	return true;
+    }
+  else if ((new_len == 19 && !memcmp (new_name + 4, "St11align_val_t", 15))
+	   || (new_len == 33
+	       && !memcmp (new_name + 4, "St11align_val_tRKSt9nothrow_t", 29)))
+    {
+      /* _ZnXYSt11align_val_t or _ZnXYSt11align_val_tRKSt9nothrow_t matches
+	 _ZdXPvSt11align_val_t or _ZdXPvYSt11align_val_t or  or
+	 _ZdXPvSt11align_val_tRKSt9nothrow_t.  */
+      if (delete_len == 20 && !memcmp (delete_name + 5, "St11align_val_t", 15))
+	return true;
+      if (delete_len == 21
+	  && delete_name[5] == new_name[3]
+	  && !memcmp (delete_name + 6, "St11align_val_t", 15))
+	return true;
+      if (delete_len == 34
+	  && !memcmp (delete_name + 5, "St11align_val_tRKSt9nothrow_t", 29))
+	return true;
+    }
+  return false;
+}
+
 #if CHECKING_P
 
 namespace selftest {
diff --git a/gcc/tree.h b/gcc/tree.h
index b44039f61ff..d366ffd8a51 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -5278,6 +5278,7 @@ extern tree tree_block (tree);
 extern void tree_set_block (tree, tree);
 extern location_t *block_nonartificial_location (tree);
 extern location_t tree_nonartificial_location (tree);
+extern location_t tree_inlined_location (tree, bool = true);
 extern tree block_ultimate_origin (const_tree);
 extern tree get_binfo_at_offset (tree, poly_int64, tree);
 extern bool virtual_method_call_p (const_tree, bool = false);
@@ -5355,6 +5356,7 @@ extern bool gimple_canonical_types_compatible_p (const_tree, const_tree,
 extern bool type_with_interoperable_signedness (const_tree);
 extern bitmap get_nonnull_args (const_tree);
 extern int get_range_pos_neg (tree);
+extern bool valid_new_delete_pair_p (tree, tree);
 
 /* Return simplified tree code of type that is used for canonical type
    merging.  */
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" }


More information about the Libstdc++-cvs mailing list