public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] detect allocation/deallocation mismatches in user-defined functions (PR94527)
@ 2020-11-13 21:45 Martin Sebor
  2020-11-30 22:53 ` Jeff Law
  0 siblings, 1 reply; 4+ messages in thread
From: Martin Sebor @ 2020-11-13 21:45 UTC (permalink / raw)
  To: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 2658 bytes --]

Bug 94527 is request from the kernel developers for an attribute
to indicate that a user-defined function deallocates an object
allocated by an earlier call to an allocation function.  Their
goal is to detect misuses of such functions and the pointers or
objects returned from them.

The recently submitted patches(*) enable the detection of a subset
of such misuses for standard allocation functions like malloc and
free, but those are just a small fraction of allocation/deallocation
functions used in practice, and only rarely used in the kernel
(mostly in utility programs). The attached patch extends attribute
malloc to enable this detection also for user-defined functions.

The design extends attribute malloc to accept one or two optional
arguments: one naming a deallocation function that deallocates
pointers returned from the malloc-like function, and another to
denote the position of the pointer argument in the deallocation
functions parameter list.  Any number of deallocators can be
associated with any number of allocators.  This makes it possible
to annotate, for example, all the POSIX <stdio.h> functions that
open and close FILE streams and detect mismatches between any
pairs that aren't suitable (in addition to calling free on
a FILE* returned from fopen, for instance).

An association with an allocator results in adding an internal
"*dealloc" attribute to the deallocator so that the former can
be quickly looked up based on a call to the latter.

Tested on x86_64-linux + Glibc & Binutils/GDB (no instances
of the new warnings).

Martin

[*] Prerequisite patch
add -Wmismatched-new-delete to middle end (PR 90629)
https://gcc.gnu.org/pipermail/gcc-patches/2020-November/557987.html

PS In pr94527 Jonathan notes that failing to properly match pairs
of calls isn't limited to APIs that return pointers and applies
to other kinds of "handles" including integers (e.g., the POSIX
open/close APIs), and a detection of such mismatches would be
helpful as well.  David submitted a prototype of this for
the analyzer here:
https://gcc.gnu.org/pipermail/gcc-patches/2020-October/555544.html
I chose not to implement nonpointer detection for some of the same
reasons as mentioned in comment #8 on the bug (and also because
there's no support for it in the machinery I use).  I also didn't
use the same attribute as David, in part because I think it's better
to provide separate attributes for pointer APIs and for others
(integers), and in part also because the deallocated_by attribute
design as is cannot accommodate my goal of supporting app standard
functions (including the <stdio.h> freopen which "deallocates"
the third argument).

[-- Attachment #2: gcc-94527.diff --]
[-- Type: text/x-patch, Size: 43045 bytes --]

PR middle-end/94527 - Add an __attribute__ that marks a function as freeing an object

gcc/ChangeLog:

	PR middle-end/94527
	* builtins.c (gimple_call_alloc_p): Handle user-defined functions.
	(fndecl_alloc_p): New helper.
	(call_dealloc_argno): New helper.
	(gimple_call_dealloc_p): Call it.
	(call_dealloc_p): Same.
	(matching_alloc_calls_p): Handle user-defined functions.
	(maybe_emit_free_warning): Same.
	* doc/extend.texi (attribute malloc): Update.
	* doc/invoke.texi (-Wmismatched-dealloc): Document new option.

gcc/c-family/ChangeLog:

	PR middle-end/94527
	* c-attribs.c (handle_dealloc_attribute): New function.
	(handle_malloc_attribute): Handle argument forms of attribute.
	* c.opt (-Wmismatched-dealloc): New option.
	(-Wmismatched-new-delete): Update description.

gcc/testsuite/ChangeLog:

	PR middle-end/94527
	* g++.dg/warn/Wmismatched-dealloc-2.C: New test.
	* g++.dg/warn/Wmismatched-dealloc.C: New test.
	* gcc.dg/Wmismatched-dealloc.c: New test.
	* gcc.dg/attr-malloc.c: New test.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index ebdded69189..aad99da01c2 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -13014,10 +13014,9 @@ find_assignment_location (tree var)
    allocated objects.  Otherwise return true even for all forms of
    alloca (including VLA).  */
 
-bool
-gimple_call_alloc_p (gimple *stmt, bool all_alloc /* = false */)
+static bool
+fndecl_alloc_p (tree fndecl, bool all_alloc /* = false */)
 {
-  tree fndecl = gimple_call_fndecl (stmt);
   if (!fndecl)
     return false;
 
@@ -13025,24 +13024,53 @@ gimple_call_alloc_p (gimple *stmt, bool all_alloc /* = false */)
   if (DECL_IS_OPERATOR_NEW_P (fndecl))
     return true;
 
-  /* TODO: Handle user-defined functions with attribute malloc.  */
-  if (!gimple_call_builtin_p (stmt, BUILT_IN_NORMAL))
+  if (fndecl_built_in_p (fndecl, BUILT_IN_NORMAL))
+    {
+      switch (DECL_FUNCTION_CODE (fndecl))
+	{
+	case BUILT_IN_ALLOCA:
+	case BUILT_IN_ALLOCA_WITH_ALIGN:
+	  return all_alloc;
+	case BUILT_IN_CALLOC:
+	case BUILT_IN_MALLOC:
+	case BUILT_IN_REALLOC:
+	case BUILT_IN_STRDUP:
+	case BUILT_IN_STRNDUP:
+	  return true;
+	default:
+	  break;
+	}
+    }
+
+  /* A function is considered an allocation function if it's declared
+     with attribute malloc with an argument naming its associated
+     deallocation function.  */
+  tree attrs = DECL_ATTRIBUTES (fndecl);
+  if (!attrs)
     return false;
 
-  switch (DECL_FUNCTION_CODE (fndecl))
+  for (tree allocs = attrs;
+       (allocs = lookup_attribute ("malloc", allocs));
+       allocs = TREE_CHAIN (allocs))
     {
-    case BUILT_IN_ALLOCA:
-    case BUILT_IN_ALLOCA_WITH_ALIGN:
-      return all_alloc;
-    case BUILT_IN_CALLOC:
-    case BUILT_IN_MALLOC:
-    case BUILT_IN_REALLOC:
-    case BUILT_IN_STRDUP:
-    case BUILT_IN_STRNDUP:
-      return true;
-    default:
-      return false;
+      tree args = TREE_VALUE (allocs);
+      if (!args)
+	continue;
+
+      if (TREE_VALUE (args))
+	return true;
     }
+
+  return false;
+}
+
+/* Return true if STMT is a call to an allocation function.  A wrapper
+   around fndecl_alloc_p.  */
+
+bool
+gimple_call_alloc_p (gimple *stmt, bool all_alloc /* = false */)
+{
+  return fndecl_alloc_p (gimple_call_fndecl (stmt), all_alloc);
 }
 
 /* Return the zero-based number corresponding to the argument being
@@ -13050,9 +13078,9 @@ gimple_call_alloc_p (gimple *stmt, bool all_alloc /* = false */)
    if it isn't.  */
 
 static unsigned
-gimple_call_dealloc_argno (gimple *stmt)
+call_dealloc_argno (tree exp)
 {
-  tree fndecl = gimple_call_fndecl (stmt);
+  tree fndecl = get_callee_fndecl (exp);
   if (!fndecl)
     return UINT_MAX;
 
@@ -13062,48 +13090,129 @@ gimple_call_dealloc_argno (gimple *stmt)
 
   /* TODO: Handle user-defined functions with attribute malloc?  Handle
      known non-built-ins like fopen?  */
-  if (!gimple_call_builtin_p (stmt, BUILT_IN_NORMAL))
+  if (fndecl_built_in_p (fndecl, BUILT_IN_NORMAL))
+    {
+      switch (DECL_FUNCTION_CODE (fndecl))
+	{
+	case BUILT_IN_FREE:
+	case BUILT_IN_REALLOC:
+	  return 0;
+	default:
+	  break;
+	}
+      return UINT_MAX;
+    }
+
+  tree attrs = DECL_ATTRIBUTES (fndecl);
+  if (!attrs)
     return UINT_MAX;
 
-  switch (DECL_FUNCTION_CODE (fndecl))
+  for (tree atfree = attrs;
+       (atfree = lookup_attribute ("*dealloc", atfree));
+       atfree = TREE_CHAIN (atfree))
     {
-    case BUILT_IN_FREE:
-    case BUILT_IN_REALLOC:
-      return 0;
-    default:
-      return UINT_MAX;
+      tree alloc = TREE_VALUE (atfree);
+      if (!alloc)
+	continue;
+
+      tree pos = TREE_CHAIN (alloc);
+      if (!pos)
+	return 0;
+
+      pos = TREE_VALUE (pos);
+      return TREE_INT_CST_LOW (pos) - 1;
     }
+
+  return UINT_MAX;
 }
 
 /* Return true if STMT is a call to a deallocation function.  */
 
 static inline bool
-gimple_call_dealloc_p (gimple *stmt)
+call_dealloc_p (tree exp)
 {
-  return gimple_call_dealloc_argno (stmt) != UINT_MAX;
+  return call_dealloc_argno (exp) != UINT_MAX;
 }
 
-/* Return true if DEALLOC_DECL is a function suitable to deallocate
-   objects allocated by calls to ALLOC_DECL.  */
+/* ALLOC_DECL and DEALLOC_DECL are pair of allocation and deallocation
+   functions.  Return true if the latter is suitable to deallocate objects
+   allocated by calls to the former.  */
 
 static bool
 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))
+      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 false for deallocation functions that are known not
+	 to match.  */
+      if (fndecl_built_in_p (dealloc_decl, BUILT_IN_FREE)
+	  || fndecl_built_in_p (dealloc_decl, BUILT_IN_REALLOC))
 	return false;
+      /* Otherwise proceed below to check the deallocation function's
+	 "*dealloc" attributes to look for one that mentions this operator
+	 new.  */
+    }
+  else if (fndecl_built_in_p (alloc_decl, BUILT_IN_NORMAL))
+    {
+      switch (DECL_FUNCTION_CODE (alloc_decl))
+	{
+	case BUILT_IN_ALLOCA:
+	case BUILT_IN_ALLOCA_WITH_ALIGN:
+	  return false;
+
+	case BUILT_IN_CALLOC:
+	case BUILT_IN_MALLOC:
+	case BUILT_IN_REALLOC:
+	case BUILT_IN_STRDUP:
+	case BUILT_IN_STRNDUP:
+	  if (DECL_IS_OPERATOR_DELETE_P (dealloc_decl))
+	    return false;
 
-      /* Return true iff both functions are of the same array or
-	 singleton form.  */
-      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, '[');
+	  if (fndecl_built_in_p (dealloc_decl, BUILT_IN_FREE)
+	      || fndecl_built_in_p (dealloc_decl, BUILT_IN_REALLOC))
+	    return true;
+	  break;
+
+	default:
+	  break;
+	}
+    }
+
+  /* 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;
+
+  for (tree funs = attrs;
+       (funs = lookup_attribute ("*dealloc", funs));
+       funs = TREE_CHAIN (funs))
+    {
+      tree args = TREE_VALUE (funs);
+      if (!args)
+	continue;
+
+      tree fname = TREE_VALUE (args);
+      if (!fname)
+	continue;
+
+      if (fname == DECL_NAME (alloc_decl))
+	return true;
     }
 
-  return !DECL_IS_OPERATOR_DELETE_P (dealloc_decl);
+  return false;
 }
 
 /* Return true if DEALLOC_DECL is a function suitable to deallocate
@@ -13175,24 +13284,19 @@ maybe_emit_free_warning (tree exp)
   if (!fndecl)
     return;
 
-  if (!fndecl_built_in_p (fndecl, BUILT_IN_FREE)
-      && !fndecl_built_in_p (fndecl, BUILT_IN_REALLOC)
-      && !DECL_IS_OPERATOR_DELETE_P (fndecl))
+  unsigned argno = call_dealloc_argno (exp);
+  if ((unsigned) call_expr_nargs (exp) <= argno)
     return;
 
-  if (call_expr_nargs (exp) < 1)
-    return;
-
-  tree ptr = CALL_EXPR_ARG (exp, 0);
+  tree ptr = CALL_EXPR_ARG (exp, argno);
   if (integer_zerop (ptr))
     return;
 
   access_ref aref;
-  if (!compute_objsize (ptr, 0, &aref) || !aref.ref)
+  if (!compute_objsize (ptr, 0, &aref))
     return;
 
   tree ref = aref.ref;
-
   if (integer_zerop (ref))
     return;
 
@@ -13258,14 +13362,21 @@ maybe_emit_free_warning (tree exp)
 		    return;
 		}
 	      else
-		warned = warning_at (loc, OPT_Wmismatched_new_delete,
-				     "%K%qD called on pointer returned "
-				     "from a mismatched allocation "
-				     "function", exp, dealloc_decl);
+		{
+		  tree alloc_decl = gimple_call_fndecl (def_stmt);
+		  int opt = (DECL_IS_OPERATOR_NEW_P (alloc_decl)
+			     || DECL_IS_OPERATOR_DELETE_P (dealloc_decl)
+			     ? OPT_Wmismatched_new_delete
+			     : OPT_Wmismatched_dealloc);
+		  warned = warning_at (loc, opt,
+				       "%K%qD called on pointer returned "
+				       "from a mismatched allocation "
+				       "function", exp, dealloc_decl);
+		}
 	    }
 	  else if (gimple_call_builtin_p (def_stmt, BUILT_IN_ALLOCA)
-		   || gimple_call_builtin_p (def_stmt,
-					     BUILT_IN_ALLOCA_WITH_ALIGN))
+	    	   || gimple_call_builtin_p (def_stmt,
+	    				     BUILT_IN_ALLOCA_WITH_ALIGN))
 	    warned = warning_at (loc, OPT_Wfree_nonheap_object,
 				 "%K%qD called on pointer to "
 				 "an unallocated object",
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index b979fbcc0c6..c0d198dca63 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -113,6 +113,7 @@ static tree handle_no_instrument_function_attribute (tree *, tree,
 static tree handle_no_profile_instrument_function_attribute (tree *, tree,
 							     tree, int, bool *);
 static tree handle_malloc_attribute (tree *, tree, tree, int, bool *);
+static tree handle_dealloc_attribute (tree *, tree, tree, int, bool *);
 static tree handle_returns_twice_attribute (tree *, tree, tree, int, bool *);
 static tree handle_no_limit_stack_attribute (tree *, tree, tree, int,
 					     bool *);
@@ -361,7 +362,7 @@ const struct attribute_spec c_common_attribute_table[] =
   { "no_profile_instrument_function",  0, 0, true, false, false, false,
 			      handle_no_profile_instrument_function_attribute,
 			      NULL },
-  { "malloc",                 0, 0, true,  false, false, false,
+  { "malloc",                 0, 2, true,  false, false, false,
 			      handle_malloc_attribute, attr_alloc_exclusions },
   { "returns_twice",          0, 0, true,  false, false, false,
 			      handle_returns_twice_attribute,
@@ -519,6 +520,8 @@ const struct attribute_spec c_common_attribute_table[] =
 			      handle_objc_root_class_attribute, NULL },
   { "objc_nullability",	      1, 1, true, false, false, false,
 			      handle_objc_nullability_attribute, NULL },
+  { "*dealloc",                1, 2, true, false, false, false,
+			      handle_dealloc_attribute, NULL },
   { NULL,                     0, 0, false, false, false, false, NULL, NULL }
 };
 
@@ -3072,20 +3075,179 @@ handle_no_profile_instrument_function_attribute (tree *node, tree name, tree,
   return NULL_TREE;
 }
 
-/* Handle a "malloc" attribute; arguments as in
-   struct attribute_spec.handler.  */
+/* Handle the "malloc" attribute.  */
 
 static tree
-handle_malloc_attribute (tree *node, tree name, tree ARG_UNUSED (args),
+handle_malloc_attribute (tree *node, tree name, tree args,
 			 int ARG_UNUSED (flags), bool *no_add_attrs)
 {
-  if (TREE_CODE (*node) == FUNCTION_DECL
-      && POINTER_TYPE_P (TREE_TYPE (TREE_TYPE (*node))))
-    DECL_IS_MALLOC (*node) = 1;
-  else
+  tree fndecl = *node;
+
+  if (TREE_CODE (*node) != FUNCTION_DECL)
     {
-      warning (OPT_Wattributes, "%qE attribute ignored", name);
+      warning (OPT_Wattributes, "%qE attribute ignored; valid only "
+	       "for functions",
+	       name);
       *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  tree rettype = TREE_TYPE (TREE_TYPE (*node));
+  if (!POINTER_TYPE_P (rettype))
+    {
+      warning (OPT_Wattributes, "%qE attribute ignored on functions "
+	       "returning %qT; valid only for pointer return types",
+	       name, rettype);
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  if (!args)
+    {
+      /* Only the form of the attribute with no arguments declares
+	 a function malloc-like.  */
+      DECL_IS_MALLOC (*node) = 1;
+      return NULL_TREE;
+    }
+
+  tree dealloc = TREE_VALUE (args);
+  if (error_operand_p (dealloc))
+    {
+      /* If the argument is in error it will have already been diagnosed.
+	 Avoid issuing redundant errors here.  */
+      *no_add_attrs = true;
+      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);
+
+  if (TREE_CODE (dealloc) != FUNCTION_DECL)
+    {
+      if (TREE_CODE (dealloc) == OVERLOAD)
+	{
+	  /* Handle specially the common case of specifying one of a number
+	     of overloads, such as operator delete.  */
+	  error ("%qE attribute argument 1 is ambiguous", name);
+	  inform (input_location,
+		  "use a cast to the expected type to disambiguate");
+	  *no_add_attrs = true;
+	  return NULL_TREE;
+	}
+
+      error ("%qE attribute argument 1 does not name a function", name);
+      if (DECL_P (dealloc))
+	inform (DECL_SOURCE_LOCATION (dealloc),
+		"argument references a symbol declared here");
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  /* Mentioning the deallocation function qualifies as its use.  */
+  TREE_USED (dealloc) = 1;
+
+  tree fntype = TREE_TYPE (dealloc);
+  tree argpos = TREE_CHAIN (args) ? TREE_VALUE (TREE_CHAIN (args)) : NULL_TREE;
+  if (!argpos)
+    {
+      tree argtypes = TYPE_ARG_TYPES (fntype);
+      if (!argtypes)
+	{
+	  /* Reject functions without a prototype.  */
+	  error ("%qE attribute argument 1 must take a pointer "
+		 "type as its first argument", name);
+	  inform (DECL_SOURCE_LOCATION (dealloc),
+		  "refernced symbol declared here" );
+	  *no_add_attrs = true;
+	  return NULL_TREE;
+	}
+
+      tree argtype = TREE_VALUE (argtypes);
+      if (TREE_CODE (argtype) != POINTER_TYPE)
+	{
+	  /* Reject functions that don't take a pointer as their first
+	     argument.  */
+	  error ("%qE attribute argument 1 must take a pointer type "
+		 "as its first argument; have %qT", name, argtype);
+	  inform (DECL_SOURCE_LOCATION (dealloc),
+		  "referenced symbol declared here" );
+	  *no_add_attrs = true;
+	  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);
+      return NULL_TREE;
+    }
+
+  /* Validate the positional argument.  */
+  argpos = positional_argument (fntype, name, argpos, POINTER_TYPE);
+  if (!argpos)
+    {
+      *no_add_attrs = true;
+      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);
+  return NULL_TREE;
+}
+
+/* Handle the internal "*dealloc" attribute added for functions declared
+   with the one- and two-argument forms of attribute malloc.  Add it
+   to *NODE unless it's already there with the same arguments.  */
+
+static tree
+handle_dealloc_attribute (tree *node, tree name, tree args, int,
+			  bool *no_add_attrs)
+{
+  tree fndecl = *node;
+
+  tree attrs = DECL_ATTRIBUTES (fndecl);
+  if (!attrs)
+    return NULL_TREE;
+
+  tree arg_fname = TREE_VALUE (args);
+  args = TREE_CHAIN (args);
+  tree arg_pos = args ? TREE_VALUE (args) : NULL_TREE;
+
+  gcc_checking_assert (TREE_CODE (arg_fname) == IDENTIFIER_NODE);
+
+  const char* const namestr = IDENTIFIER_POINTER (name);
+  for (tree at = attrs; (at = lookup_attribute (namestr, at));
+       at = TREE_CHAIN (at))
+    {
+      tree alloc = TREE_VALUE (at);
+      if (!alloc)
+	continue;
+
+      tree pos = TREE_CHAIN (alloc);
+      alloc = TREE_VALUE (alloc);
+      pos = pos ? TREE_VALUE (pos) : NULL_TREE;
+      gcc_checking_assert (TREE_CODE (alloc) == IDENTIFIER_NODE);
+
+      if (alloc == arg_fname
+	  && ((!pos && !arg_pos)
+	      || (pos && arg_pos && 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
+	     being added.  Return without adding another copy.  */
+	  *no_add_attrs = true;
+	  return NULL_TREE;
+	}
     }
 
   return NULL_TREE;
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 0bc81e696db..a1b57043d38 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -785,10 +785,15 @@ Wmisleading-indentation
 C C++ Common Var(warn_misleading_indentation) Warning LangEnabledBy(C C++,Wall)
 Warn when the indentation of the code does not reflect the block structure.
 
+Wmismatched-dealloc
+C ObjC C++ ObjC++ Var(warn_mismatched_alloc) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
+Warn for deallocation calls with arguments returned from mismatched allocation
+functions.
+
 Wmismatched-new-delete
 C++ ObjC++ Var(warn_mismatched_new_delete) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
-Warn for deallocation calls with arguments returned from calls to mismatched
-allocation functions.
+Warn for mismatches between calls to operator new or delete and the corrsponding
+call to the allocation or deallocation function.
 
 Wmismatched-tags
 C++ ObjC++ Var(warn_mismatched_tags) Warning
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index af25f66c8b4..2ed90f0e613 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -3233,20 +3233,63 @@ this reason the attribute is not allowed on types to annotate indirect
 calls.
 
 @item malloc
+@item malloc (@var{deallocator})
+@item malloc (@var{deallocator}, @var{ptr-index})
 @cindex @code{malloc} function attribute
 @cindex functions that behave like malloc
-This tells the compiler that a function is @code{malloc}-like, i.e.,
-that the pointer @var{P} returned by the function cannot alias any
+Attribute @code{malloc} indicates that a function is @code{malloc}-like,
+i.e., that the pointer @var{P} returned by the function cannot alias any
 other pointer valid when the function returns, and moreover no
 pointers to valid objects occur in any storage addressed by @var{P}.
 
-Using this attribute can improve optimization.  Compiler predicts
-that a function with the attribute returns non-null in most cases.
-Functions like
-@code{malloc} and @code{calloc} have this property because they return
-a pointer to uninitialized or zeroed-out storage.  However, functions
-like @code{realloc} do not have this property, as they can return a
-pointer to storage containing pointers.
+Independently, the form of the attribute with one or two arguments
+associates @code{deallocator} as a suitable deallocation function for
+pointers returned from the @code{malloc}-like function.  @var{ptr-index}
+denotes the positional argument to which when the pointer is passed in
+calls to @code{deallocator} has the effect of deallocating it.
+
+Using the attribute with no arguments is designed to improve optimization.
+The compiler predicts that a function with the attribute returns non-null
+in most cases.  Functions like @code{malloc} and @code{calloc} have this
+property because they return a pointer to uninitialized or zeroed-out
+storage.  However, functions like @code{realloc} do not have this property,
+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.
+
+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.
+
+@smallexample
+int   fclose (FILE*);
+FILE* freopen (const char*, const char*, FILE*);
+int   pclose (FILE*);
+
+__attribute__ ((malloc, malloc (fclose), malloc (freopen, 3)))
+  FILE* fdopen (int);
+__attribute__ ((malloc, malloc (fclose), malloc (freopen, 3)))
+  FILE* fopen (const char*, const char*);
+__attribute__ ((malloc, malloc (fclose), malloc (freopen, 3)))
+  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)))
+  FILE* popen (const char*, const char*);
+__attribute__ ((malloc, malloc (fclose), malloc (freopen, 3)))
+  FILE* tmpfile (void);
+@end smallexample
 
 @item no_icf
 @cindex @code{no_icf} function attribute
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 2e997b03447..e32f2f8df9d 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -3834,13 +3834,19 @@ templates.
 @item -Wno-mismatched-new-delete @r{(C++ and Objective-C++ only)}
 @opindex Wmismatched-new-delete
 @opindex Wno-mismatched-new-delete
-Warn for invocations of C++ @code{operator delete} with pointer arguments
+Warn for mismatches between calls to @code{operator new} or @code{operator
+delete} and the corresponding call to the allocation or deallocation function.
+This includes invocations of C++ @code{operator delete} with pointers
 returned from either mismatched forms of @code{operator new}, or from other
 functions that allocate objects for which the @code{operator delete} isn't
-a suitable deallocator.  For example, the @code{delete} expression in
-the function below is diagnosed because it doesn't match the array form
-of the @code{new} expression the pointer argument was returned from.
-Similarly, the call to @code{free} is also diagnosed.
+a suitable deallocator, as well as calls to other deallocation functions
+with pointers returned from @code{operator new} for which the deallocation
+function isn't suitable.
+
+For example, the @code{delete} expression in the function below is diagnosed
+because it doesn't match the array form of the @code{new} expression
+the pointer argument was returned from.  Similarly, the call to @code{free}
+is also diagnosed.
 
 @smallexample
 void f ()
@@ -3853,6 +3859,10 @@ void f ()
 @}
 @end smallexample
 
+The related option @option{-Wmismatched-dealloc} diagnoses mismatches
+involving allocation and deallocation functions other than @code{operator
+new} and @code{operator delete}.
+
 @option{-Wmismatched-new-delete} is enabled by default.
 
 @item -Wmismatched-tags @r{(C++ and Objective-C++ only)}
@@ -6283,6 +6293,41 @@ Ignoring the warning can result in poorly optimized code.
 disable the warning, but this is not recommended and should be done only
 when non-existent profile data is justified.
 
+@item -Wno-mismatched-dealloc
+@opindex Wmismatched-dealloc
+@opindex Wno-mismatched-dealloc
+
+Warn for calls to deallocation functions with pointer arguments returned
+from from allocations functions for which the former isn't a suitable
+deallocator.  A pair of functions can be associated as matching allocators
+and deallocators by use of attribute @code{malloc}.  Unless disabled by
+the @option{-fno-builtin} option the standard functions @code{calloc},
+@code{malloc}, @code{realloc}, and @code{free}, as well as the corresponding
+forms of C++ @code{operator new} and @code{operator delete} are implicitly
+associated as matching allocators and deallocators.  In the following
+example @code{mydealloc} is the deallocator for pointers returned from
+@code{myalloc}.
+
+@smallexample
+void mydealloc (void*);
+
+__attribute__ ((malloc (mydealloc, 1))) void*
+myalloc (size_t);
+
+void f (void)
+@{
+  void *p = myalloc (32);
+  // @dots{}use p@dots{}
+  free (p);   // warning: not a matching deallocator for myalloc
+  mydealloc (p);   // ok
+@}
+@end smallexample
+
+In C++, the related option @option{-Wmismatched-new-delete} diagnoses
+mismatches involving either @code{operator new} or @code{operator delete}.
+
+Option @option{-Wmismatched-dealloc} is enabled by default.
+
 @item -Wmultistatement-macros
 @opindex Wmultistatement-macros
 @opindex Wno-multistatement-macros
diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C
new file mode 100644
index 00000000000..7ecc99a325c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C
@@ -0,0 +1,185 @@
+/* PR middle-end/94527 - Add an attribute that marks a function as freeing
+   an object
+   The detection doesn't require optimization.
+   { dg-do compile }
+   { dg-options "-Wall" } */
+
+#define A(...) __attribute__ ((malloc (__VA_ARGS__)))
+
+typedef __SIZE_TYPE__ size_t;
+
+extern "C" {
+  void free (void *);
+  void* realloc (void *, size_t);
+}
+
+void sink (void *);
+
+void                   mydealloc (int, void*);
+void* A (mydealloc, 2) myalloc (void*);
+
+
+void my_delete (const char*, void*);
+void my_array_delete (const char*, void*);
+
+typedef void OpDelete1 (void*);
+typedef void OpDelete2 (void*, size_t);
+
+A ((OpDelete1*)operator delete, 1)
+#if __cplusplus >= 201402L
+A ((OpDelete2*)operator delete, 1)
+#endif
+A (my_delete, 2)
+int* my_new (size_t);
+
+A ((OpDelete1*)operator delete[], 1)
+#if __cplusplus >= 201402L
+A ((OpDelete2*)operator delete[], 1)
+#endif
+A (my_array_delete, 2)
+int* my_array_new (size_t);
+
+
+void test_my_new ()
+{
+  {
+    void *p = my_new (1);
+    operator delete (p);
+  }
+  {
+    void *p = my_new (1);
+    sink (p);
+    operator delete (p);
+  }
+  {
+    int *p = my_new (1);
+    sink (p);
+    delete p;
+  }
+
+  {
+    void *p = my_new (1);
+    // { dg-message "returned from a call to '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 }
+    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 }
+  }
+  {
+    int *p = my_new (1);
+    sink (p);
+    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);
+    my_delete ("1", p);
+  }
+  {
+    void *p = my_new (1);
+    sink (p);
+    my_delete ("2", p);
+  }
+
+  {
+    void *p = my_new (1);
+    // { dg-message "returned from a call to '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 }
+  }
+
+  {
+    void *p = my_new (1);
+    // { dg-message "returned from a call to '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 }
+  }
+
+  {
+    void *p = my_new (1);
+    // { dg-message "returned from a call to '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 }
+  }
+}
+
+
+void test_my_array_new ()
+{
+  {
+    void *p = my_array_new (1);
+    operator delete[] (p);
+  }
+  {
+    void *p = my_array_new (1);
+    sink (p);
+    operator delete[] (p);
+  }
+  {
+    int *p = my_array_new (1);
+    sink (p);
+    delete[] p;
+  }
+
+  {
+    void *p = my_array_new (1);
+    // { dg-message "returned from a call to '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 }
+    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 }
+  }
+  {
+    int *p = my_array_new (1);
+    sink (p);
+    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);
+    my_array_delete ("1", p);
+  }
+  {
+    void *p = my_array_new (1);
+    sink (p);
+    my_array_delete ("2", p);
+  }
+  {
+    void *p = my_array_new (1);
+    // { dg-message "returned from a call to '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 }
+  }
+
+  {
+    void *p = my_array_new (1);
+    // { dg-message "returned from a call to '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 }
+  }
+
+  {
+    void *p = my_array_new (1);
+    // { dg-message "returned from a call to '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-dealloc.C b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc.C
new file mode 100644
index 00000000000..682db6f02cb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc.C
@@ -0,0 +1,27 @@
+/* PR middle-end/94527 - Add an attribute that marks a function as freeing
+   an object
+   { dg-do compile { target c++11 } }
+   { dg-options "-Wall" } */
+
+#define A(...) __attribute__ ((malloc (__VA_ARGS__)))
+
+typedef __SIZE_TYPE__ size_t;
+
+void                   mydealloc (int, void*);
+void* A (mydealloc, 2) myalloc (void*);
+
+
+void* A (operator delete, 1)
+  bad_new (size_t);                     // { dg-error "attribute argument 1 is ambiguous" }
+void* A (operator delete[], 1)
+  bad_array_new (size_t);               // { dg-error "attribute argument 1 is ambiguous" }
+
+void my_delete (const char*, void*);
+void my_array_delete (const char*, void*);
+
+typedef void OpDelete (void*);
+
+int* A ((OpDelete*)operator delete, 1) A (my_delete, 2)
+  my_new (size_t);
+int* A ((OpDelete*)operator delete[], 1) A (my_array_delete, 2)
+  my_array_new (size_t);
diff --git a/gcc/testsuite/gcc.dg/Wmismatched-dealloc.c b/gcc/testsuite/gcc.dg/Wmismatched-dealloc.c
new file mode 100644
index 00000000000..7c5d6acf4d6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wmismatched-dealloc.c
@@ -0,0 +1,252 @@
+/* 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__)))
+
+typedef struct FILE   FILE;
+typedef __SIZE_TYPE__ size_t;
+
+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);
+
+void sink (FILE*);
+
+
+            void  release (void*);
+A (release) FILE* acquire (void);
+
+void nowarn_fdopen (void)
+{
+  {
+    FILE *q = fdopen (0);
+    if (!q)
+      return;
+
+    fclose (q);
+  }
+
+  {
+    FILE *q = fdopen (0);
+    if (!q)
+      return;
+
+    q = freopen ("1", "r", q);
+    fclose (q);
+  }
+
+  {
+    FILE *q = fdopen (0);
+    if (!q)
+      return;
+
+    sink (q);
+  }
+}
+
+
+void warn_fdopen (void)
+{
+  {
+    FILE *q = fdopen (0);     // { dg-message "returned from a call to '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" }
+    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" }
+    sink (q);
+    q = realloc (q, 7);       // { dg-warning "'realloc' called on pointer returned from a mismatched allocation function" }
+    sink (q);
+  }
+}
+
+
+void nowarn_fopen (void)
+{
+  {
+    FILE *q = fopen ("1", "r");
+    sink (q);
+    fclose (q);
+  }
+
+  {
+    FILE *q = fopen ("2", "r");
+    sink (q);
+    q = freopen ("3", "r", q);
+    sink (q);
+    fclose (q);
+  }
+
+  {
+    FILE *q = fopen ("4", "r");
+    sink (q);
+  }
+}
+
+
+void warn_fopen (void)
+{
+  {
+    FILE *q = fopen ("1", "r");
+    sink (q);
+    release (q);              // { dg-warning "'release' called on pointer returned from a mismatched allocation function" }
+  }
+  {
+    FILE *q = fdopen (0);
+    sink (q);
+    free (q);                 // { dg-warning "'free' called on pointer returned from a mismatched allocation function" }
+  }
+
+  {
+    FILE *q = fdopen (0);
+    sink (q);
+    q = realloc (q, 7);       // { dg-warning "'realloc' called on pointer returned from a mismatched allocation function" }
+    sink (q);
+  }
+}
+
+
+void test_popen (void)
+{
+  {
+    FILE *p = popen ("1", "r");
+    sink (p);
+    pclose (p);
+  }
+
+  {
+    FILE *p;
+    p = popen ("2", "r");     // { dg-message "returned from a call to 'popen'" "note" }
+    sink (p);
+    fclose (p);               // { dg-warning "'fclose' called on pointer returned from a mismatched allocation function" }
+  }
+
+  {
+    /* freopen() can close a stream open by popen() but pclose() can't
+       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" }
+    sink (p);
+    pclose (p);               // { dg-warning "'pclose' called on pointer returned from a mismatched allocation function" }
+  }
+}
+
+
+void test_tmpfile (void)
+{
+  {
+    FILE *p = tmpfile ();
+    sink (p);
+    fclose (p);
+  }
+
+  {
+    FILE *p = tmpfile ();
+    sink (p);
+    p = freopen ("1", "r", p);
+    sink (p);
+    fclose (p);
+  }
+
+  {
+    FILE *p = tmpfile ();     // { dg-message "returned from a call to 'tmpfile'" "note" }
+    sink (p);
+    pclose (p);               // { dg-warning "'pclose' 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" }
+    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" }
+    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" }
+    sink (p);
+    pclose (p);               // { dg-warning "'pclose' called on pointer returned from a mismatched allocation function" }
+  }
+}
+
+
+void test_acquire (void)
+{
+  {
+    FILE *p = acquire ();
+    release (p);
+  }
+
+  {
+    FILE *p = acquire ();
+    sink (p);
+    release (p);
+  }
+
+  {
+    FILE *p = acquire ();     // { dg-message "returned from a call to '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" }
+    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" }
+    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" }
+    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" }
+    sink (p);
+    p = realloc (p, 123);     // { dg-warning "'realloc' called on pointer returned from a mismatched allocation function" }
+    sink (p);
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/attr-malloc.c b/gcc/testsuite/gcc.dg/attr-malloc.c
new file mode 100644
index 00000000000..14f1980ed7f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/attr-malloc.c
@@ -0,0 +1,75 @@
+/* PR middle-end/94527 - Add an attribute that marks a function as freeing
+   an object
+   Verify that attribute malloc with one or two arguments is accepted where
+   intended and rejected where it's invalid.
+   { dg-options "-Wall -ftrack-macro-expansion=0" } */
+
+#define A(...) __attribute__ ((malloc (__VA_ARGS__)))
+
+A (0) void* alloc_zero (int);           // { dg-error "'malloc' attribute argument 1 does not name a function" }
+
+A ("") void* alloc_string (int);        // { dg-error "'malloc' attribute argument 1 does not name a function" }
+
+int var;
+A (var) void* alloc_var (int);          // { dg-error "'malloc' attribute argument 1 does not name a function" }
+
+typedef struct Type { int i; } Type;
+A (Type) void* alloc_type (int);        // { dg-error "expected expression|identifier" }
+
+A (unknown) void* alloc_unknown (int);  // { dg-error "'unknown' undeclared" }
+
+void fv_ ();                            // { dg-message "declared here" }
+A (fv_) void* alloc_fv_ (int);          // { dg-error "'malloc' attribute argument 1 must take a pointer type as its first argument" }
+
+void fvi (int);                         // { dg-message "declared here" }
+A (fvi) void* alloc_fvi (int);          // { dg-error "'malloc' attribute argument 1 must take a pointer type as its first argument; have 'int'" }
+
+void fvv (void);                        // { dg-message "declared here" }
+A (fvv) void* alloc_fvv (int);          // { dg-error "'malloc' attribute argument 1 must take a pointer type as its first argument; have 'void'" }
+
+void fvi_ (int, ...);                   // { dg-message "declared here" }
+A (fvi_) void* alloc_fvi_ (int);        // { dg-error "'malloc' attribute argument 1 must take a pointer type as its first argument; have 'int'" }
+
+void fvi_vp (Type, void*);              // { dg-message "declared here" }
+A (fvi_vp) void* alloc_fvi_vp (int);    // { dg-error "'malloc' attribute argument 1 must take a pointer type as its first argument; have 'Type'" }
+
+
+void fpv (void*);
+A (fpv) void* alloc_fpv (int);
+
+void fpv_i (void*, int);
+A (fpv_i) void* alloc_fpv_i (int);
+
+void fpv_pv (void*, void*);
+A (fpv_i) void* alloc_fpv_pv (int);
+
+
+void gpc (char*);
+void hpi (int*);
+A (fpv) A (gpc) A (hpi) Type* alloc_fpv_gpv (int);
+
+
+/* Verify that the attribute can be applied to <stdio.h> functions.  */
+typedef struct FILE FILE;
+typedef __SIZE_TYPE__ size_t;
+
+int   fclose (FILE*);
+FILE* fdopen (int);
+FILE* fopen (const char*, const char*);
+FILE* freopen (const char*, const char*, FILE*);
+int   pclose (FILE*);
+FILE* popen (const char*, const char*);
+FILE* tmpfile (void);
+
+A (fclose) A (freopen, 3) A (pclose)
+  FILE* fdopen (int);
+A (fclose) A (freopen, 3) A (pclose)
+  FILE* fopen (const char*, const char*);
+A (fclose) A (freopen, 3) A (pclose)
+  FILE* fmemopen(void *, size_t, const char *);
+A (fclose) A (freopen, 3) A (pclose)
+  FILE* freopen (const char*, const char*, FILE*);
+A (fclose) A (freopen, 3) A (pclose)
+  FILE* popen (const char*, const char*);
+A (fclose) A (freopen, 3) A (pclose)
+  FILE* tmpfile (void);

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] detect allocation/deallocation mismatches in user-defined functions (PR94527)
  2020-11-13 21:45 [PATCH] detect allocation/deallocation mismatches in user-defined functions (PR94527) Martin Sebor
@ 2020-11-30 22:53 ` Jeff Law
  2020-12-01 17:14   ` Martin Sebor
  0 siblings, 1 reply; 4+ messages in thread
From: Jeff Law @ 2020-11-30 22:53 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches



On 11/13/20 2:45 PM, Martin Sebor via Gcc-patches wrote:
> Bug 94527 is request from the kernel developers for an attribute
> to indicate that a user-defined function deallocates an object
> allocated by an earlier call to an allocation function.  Their
> goal is to detect misuses of such functions and the pointers or
> objects returned from them.
>
> The recently submitted patches(*) enable the detection of a subset
> of such misuses for standard allocation functions like malloc and
> free, but those are just a small fraction of allocation/deallocation
> functions used in practice, and only rarely used in the kernel
> (mostly in utility programs). The attached patch extends attribute
> malloc to enable this detection also for user-defined functions.
>
> The design extends attribute malloc to accept one or two optional
> arguments: one naming a deallocation function that deallocates
> pointers returned from the malloc-like function, and another to
> denote the position of the pointer argument in the deallocation
> functions parameter list.  Any number of deallocators can be
> associated with any number of allocators.  This makes it possible
> to annotate, for example, all the POSIX <stdio.h> functions that
> open and close FILE streams and detect mismatches between any
> pairs that aren't suitable (in addition to calling free on
> a FILE* returned from fopen, for instance).
>
> An association with an allocator results in adding an internal
> "*dealloc" attribute to the deallocator so that the former can
> be quickly looked up based on a call to the latter.
>
> Tested on x86_64-linux + Glibc & Binutils/GDB (no instances
> of the new warnings).
>
> Martin
>
> [*] Prerequisite patch
> add -Wmismatched-new-delete to middle end (PR 90629)
> https://gcc.gnu.org/pipermail/gcc-patches/2020-November/557987.html
>
> PS In pr94527 Jonathan notes that failing to properly match pairs
> of calls isn't limited to APIs that return pointers and applies
> to other kinds of "handles" including integers (e.g., the POSIX
> open/close APIs), and a detection of such mismatches would be
> helpful as well.  David submitted a prototype of this for
> the analyzer here:
> https://gcc.gnu.org/pipermail/gcc-patches/2020-October/555544.html
> I chose not to implement nonpointer detection for some of the same
> reasons as mentioned in comment #8 on the bug (and also because
> there's no support for it in the machinery I use).  I also didn't
> use the same attribute as David, in part because I think it's better
> to provide separate attributes for pointer APIs and for others
> (integers), and in part also because the deallocated_by attribute
> design as is cannot accommodate my goal of supporting app standard
> functions (including the <stdio.h> freopen which "deallocates"
> the third argument).
>
> gcc-94527.diff
>
> PR middle-end/94527 - Add an __attribute__ that marks a function as freeing an object
>
> gcc/ChangeLog:
>
> 	PR middle-end/94527
> 	* builtins.c (gimple_call_alloc_p): Handle user-defined functions.
> 	(fndecl_alloc_p): New helper.
> 	(call_dealloc_argno): New helper.
> 	(gimple_call_dealloc_p): Call it.
> 	(call_dealloc_p): Same.
> 	(matching_alloc_calls_p): Handle user-defined functions.
> 	(maybe_emit_free_warning): Same.
> 	* doc/extend.texi (attribute malloc): Update.
> 	* doc/invoke.texi (-Wmismatched-dealloc): Document new option.
>
> gcc/c-family/ChangeLog:
>
> 	PR middle-end/94527
> 	* c-attribs.c (handle_dealloc_attribute): New function.
> 	(handle_malloc_attribute): Handle argument forms of attribute.
> 	* c.opt (-Wmismatched-dealloc): New option.
> 	(-Wmismatched-new-delete): Update description.
>
> gcc/testsuite/ChangeLog:
>
> 	PR middle-end/94527
> 	* g++.dg/warn/Wmismatched-dealloc-2.C: New test.
> 	* g++.dg/warn/Wmismatched-dealloc.C: New test.
> 	* gcc.dg/Wmismatched-dealloc.c: New test.
> 	* gcc.dg/attr-malloc.c: New test.
OK once prereq is wrapped up.

I realize the dealloc attribute is currently internal only to make
lookups quick.  Any thoughts on whether or not we might want to make it
a user visible attribute at some point? 


Jeff


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] detect allocation/deallocation mismatches in user-defined functions (PR94527)
  2020-11-30 22:53 ` Jeff Law
@ 2020-12-01 17:14   ` Martin Sebor
  2020-12-01 22:00     ` Jeff Law
  0 siblings, 1 reply; 4+ messages in thread
From: Martin Sebor @ 2020-12-01 17:14 UTC (permalink / raw)
  To: Jeff Law, gcc-patches

On 11/30/20 3:53 PM, Jeff Law wrote:
> 
> 
> On 11/13/20 2:45 PM, Martin Sebor via Gcc-patches wrote:
>> Bug 94527 is request from the kernel developers for an attribute
>> to indicate that a user-defined function deallocates an object
>> allocated by an earlier call to an allocation function.  Their
>> goal is to detect misuses of such functions and the pointers or
>> objects returned from them.
>>
>> The recently submitted patches(*) enable the detection of a subset
>> of such misuses for standard allocation functions like malloc and
>> free, but those are just a small fraction of allocation/deallocation
>> functions used in practice, and only rarely used in the kernel
>> (mostly in utility programs). The attached patch extends attribute
>> malloc to enable this detection also for user-defined functions.
>>
>> The design extends attribute malloc to accept one or two optional
>> arguments: one naming a deallocation function that deallocates
>> pointers returned from the malloc-like function, and another to
>> denote the position of the pointer argument in the deallocation
>> functions parameter list.  Any number of deallocators can be
>> associated with any number of allocators.  This makes it possible
>> to annotate, for example, all the POSIX <stdio.h> functions that
>> open and close FILE streams and detect mismatches between any
>> pairs that aren't suitable (in addition to calling free on
>> a FILE* returned from fopen, for instance).
>>
>> An association with an allocator results in adding an internal
>> "*dealloc" attribute to the deallocator so that the former can
>> be quickly looked up based on a call to the latter.
>>
>> Tested on x86_64-linux + Glibc & Binutils/GDB (no instances
>> of the new warnings).
>>
>> Martin
>>
>> [*] Prerequisite patch
>> add -Wmismatched-new-delete to middle end (PR 90629)
>> https://gcc.gnu.org/pipermail/gcc-patches/2020-November/557987.html
>>
>> PS In pr94527 Jonathan notes that failing to properly match pairs
>> of calls isn't limited to APIs that return pointers and applies
>> to other kinds of "handles" including integers (e.g., the POSIX
>> open/close APIs), and a detection of such mismatches would be
>> helpful as well.  David submitted a prototype of this for
>> the analyzer here:
>> https://gcc.gnu.org/pipermail/gcc-patches/2020-October/555544.html
>> I chose not to implement nonpointer detection for some of the same
>> reasons as mentioned in comment #8 on the bug (and also because
>> there's no support for it in the machinery I use).  I also didn't
>> use the same attribute as David, in part because I think it's better
>> to provide separate attributes for pointer APIs and for others
>> (integers), and in part also because the deallocated_by attribute
>> design as is cannot accommodate my goal of supporting app standard
>> functions (including the <stdio.h> freopen which "deallocates"
>> the third argument).
>>
>> gcc-94527.diff
>>
>> PR middle-end/94527 - Add an __attribute__ that marks a function as freeing an object
>>
>> gcc/ChangeLog:
>>
>> 	PR middle-end/94527
>> 	* builtins.c (gimple_call_alloc_p): Handle user-defined functions.
>> 	(fndecl_alloc_p): New helper.
>> 	(call_dealloc_argno): New helper.
>> 	(gimple_call_dealloc_p): Call it.
>> 	(call_dealloc_p): Same.
>> 	(matching_alloc_calls_p): Handle user-defined functions.
>> 	(maybe_emit_free_warning): Same.
>> 	* doc/extend.texi (attribute malloc): Update.
>> 	* doc/invoke.texi (-Wmismatched-dealloc): Document new option.
>>
>> gcc/c-family/ChangeLog:
>>
>> 	PR middle-end/94527
>> 	* c-attribs.c (handle_dealloc_attribute): New function.
>> 	(handle_malloc_attribute): Handle argument forms of attribute.
>> 	* c.opt (-Wmismatched-dealloc): New option.
>> 	(-Wmismatched-new-delete): Update description.
>>
>> gcc/testsuite/ChangeLog:
>>
>> 	PR middle-end/94527
>> 	* g++.dg/warn/Wmismatched-dealloc-2.C: New test.
>> 	* g++.dg/warn/Wmismatched-dealloc.C: New test.
>> 	* gcc.dg/Wmismatched-dealloc.c: New test.
>> 	* gcc.dg/attr-malloc.c: New test.
> OK once prereq is wrapped up.

Presumably you're referring to this patch:
   https://gcc.gnu.org/pipermail/gcc-patches/2020-November/557987.html
Is there something I need to do to move it forward?  (I'm
assuming you're still reviewing the rest of the patch so
please let me know if you're waiting for me to remove
the objectionable function first.)

> 
> I realize the dealloc attribute is currently internal only to make
> lookups quick.  Any thoughts on whether or not we might want to make it
> a user visible attribute at some point?

I see no problem with exposing both attributes.

Martin

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] detect allocation/deallocation mismatches in user-defined functions (PR94527)
  2020-12-01 17:14   ` Martin Sebor
@ 2020-12-01 22:00     ` Jeff Law
  0 siblings, 0 replies; 4+ messages in thread
From: Jeff Law @ 2020-12-01 22:00 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches



On 12/1/20 10:14 AM, Martin Sebor wrote:
> On 11/30/20 3:53 PM, Jeff Law wrote:
>>
>>
>> On 11/13/20 2:45 PM, Martin Sebor via Gcc-patches wrote:
>>> Bug 94527 is request from the kernel developers for an attribute
>>> to indicate that a user-defined function deallocates an object
>>> allocated by an earlier call to an allocation function.  Their
>>> goal is to detect misuses of such functions and the pointers or
>>> objects returned from them.
>>>
>>> The recently submitted patches(*) enable the detection of a subset
>>> of such misuses for standard allocation functions like malloc and
>>> free, but those are just a small fraction of allocation/deallocation
>>> functions used in practice, and only rarely used in the kernel
>>> (mostly in utility programs). The attached patch extends attribute
>>> malloc to enable this detection also for user-defined functions.
>>>
>>> The design extends attribute malloc to accept one or two optional
>>> arguments: one naming a deallocation function that deallocates
>>> pointers returned from the malloc-like function, and another to
>>> denote the position of the pointer argument in the deallocation
>>> functions parameter list.  Any number of deallocators can be
>>> associated with any number of allocators.  This makes it possible
>>> to annotate, for example, all the POSIX <stdio.h> functions that
>>> open and close FILE streams and detect mismatches between any
>>> pairs that aren't suitable (in addition to calling free on
>>> a FILE* returned from fopen, for instance).
>>>
>>> An association with an allocator results in adding an internal
>>> "*dealloc" attribute to the deallocator so that the former can
>>> be quickly looked up based on a call to the latter.
>>>
>>> Tested on x86_64-linux + Glibc & Binutils/GDB (no instances
>>> of the new warnings).
>>>
>>> Martin
>>>
>>> [*] Prerequisite patch
>>> add -Wmismatched-new-delete to middle end (PR 90629)
>>> https://gcc.gnu.org/pipermail/gcc-patches/2020-November/557987.html
>>>
>>> PS In pr94527 Jonathan notes that failing to properly match pairs
>>> of calls isn't limited to APIs that return pointers and applies
>>> to other kinds of "handles" including integers (e.g., the POSIX
>>> open/close APIs), and a detection of such mismatches would be
>>> helpful as well.  David submitted a prototype of this for
>>> the analyzer here:
>>> https://gcc.gnu.org/pipermail/gcc-patches/2020-October/555544.html
>>> I chose not to implement nonpointer detection for some of the same
>>> reasons as mentioned in comment #8 on the bug (and also because
>>> there's no support for it in the machinery I use).  I also didn't
>>> use the same attribute as David, in part because I think it's better
>>> to provide separate attributes for pointer APIs and for others
>>> (integers), and in part also because the deallocated_by attribute
>>> design as is cannot accommodate my goal of supporting app standard
>>> functions (including the <stdio.h> freopen which "deallocates"
>>> the third argument).
>>>
>>> gcc-94527.diff
>>>
>>> PR middle-end/94527 - Add an __attribute__ that marks a function as
>>> freeing an object
>>>
>>> gcc/ChangeLog:
>>>
>>>     PR middle-end/94527
>>>     * builtins.c (gimple_call_alloc_p): Handle user-defined functions.
>>>     (fndecl_alloc_p): New helper.
>>>     (call_dealloc_argno): New helper.
>>>     (gimple_call_dealloc_p): Call it.
>>>     (call_dealloc_p): Same.
>>>     (matching_alloc_calls_p): Handle user-defined functions.
>>>     (maybe_emit_free_warning): Same.
>>>     * doc/extend.texi (attribute malloc): Update.
>>>     * doc/invoke.texi (-Wmismatched-dealloc): Document new option.
>>>
>>> gcc/c-family/ChangeLog:
>>>
>>>     PR middle-end/94527
>>>     * c-attribs.c (handle_dealloc_attribute): New function.
>>>     (handle_malloc_attribute): Handle argument forms of attribute.
>>>     * c.opt (-Wmismatched-dealloc): New option.
>>>     (-Wmismatched-new-delete): Update description.
>>>
>>> gcc/testsuite/ChangeLog:
>>>
>>>     PR middle-end/94527
>>>     * g++.dg/warn/Wmismatched-dealloc-2.C: New test.
>>>     * g++.dg/warn/Wmismatched-dealloc.C: New test.
>>>     * gcc.dg/Wmismatched-dealloc.c: New test.
>>>     * gcc.dg/attr-malloc.c: New test.
>> OK once prereq is wrapped up.
>
> Presumably you're referring to this patch:
>   https://gcc.gnu.org/pipermail/gcc-patches/2020-November/557987.html
> Is there something I need to do to move it forward?  (I'm
> assuming you're still reviewing the rest of the patch so
> please let me know if you're waiting for me to remove
> the objectionable function first.)
Yes, that's the one.  I'm hoping to get to your reply today...

jeff


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2020-12-01 22:00 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-11-13 21:45 [PATCH] detect allocation/deallocation mismatches in user-defined functions (PR94527) Martin Sebor
2020-11-30 22:53 ` Jeff Law
2020-12-01 17:14   ` Martin Sebor
2020-12-01 22:00     ` Jeff Law

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).