On 10/5/20 5:12 PM, David Malcolm via Gcc-patches wrote:
This work-in-progress patch generalizes the malloc/free problem-checking
in -fanalyzer so that it can work on arbitrary acquire/release API pairs.

It adds a new __attribute__((deallocated_by(FOO))) that could be used
like this in a library header:

   struct foo;

   extern void foo_release (struct foo *);

   extern struct foo *foo_acquire (void)
     __attribute__ ((deallocated_by(foo_release)));

In theory, the analyzer then "knows" these functions are an
acquire/release pair, and can emit diagnostics for leaks, double-frees,
use-after-frees, mismatching deallocations, etc.

My hope was that this would provide a minimal level of markup that would
support library-checking without requiring lots of further markup.
I attempted to use this to detect a memory leak within a Linux
driver (CVE-2019-19078), by adding the attribute to mark these fns:
   extern struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
   extern void usb_free_urb(struct urb *urb);
where there is a leak of a "urb" on an error-handling path.
Unfortunately I ran into the problem that there are various other fns
that take "struct urb *" and the analyzer conservatively assumes that a
urb passed to them might or might not be freed and thus stops tracking
state for them.

So I don't know how much use this feature would be as-is.
(without either requiring lots of additional attributes for marking
fndecl args as being merely borrowed, or simply assuming that they
are borrowed in the absence of a function body to analyze)

Thoughts?

An attribute like this is close to what PR 94527 asks for, except
it goes beyond pointers and supports allocating resources of any
type.  The latter part seems a bit worrisome to me for some of
the same reason as Linus mentions in comment #8 on the request
(among others).

I've got a prototype solution for the pointer part of the request,
sans the attribute, but should be able to use it with just a few
lines of code.

As for the design of the attribute, my initial thought was to
implement it as an extension to attribute malloc, since I'd always
expect a deallocator to be paired with one (though not the other
way around, as in the case of alloca).

I also wanted the attribute to be suitable for standard allocation
functions, including malloc/realloc, as well as I/O functions like
fopen and freopen.

I wasn't thinking of imposing any particular order on the arguments
to the deallocator (it would rule out supporting freopen).

So with that, I was going to add this syntax to attribute malloc:

  malloc [(deallocator, argno)]

and allow multiple malloc attributes with different deallocators on
the same allocator function, like this:

  __attribute__ ((malloc (free, 1),
                  malloc (realloc, 1)))
    void* malloc (size_t);

None of this exists even as a prototype so there could be flaws in
the idea I'm not thinking of.  From what I can tell, deallocated_by
could be used this way as well although I don't see it discussed in
the documentation.

I don't view adding deallocated_by on top of malloc as a problem
per se, but it does raise the question of what the attribute alone
implies for points-to analysis.  For example:

  __attribute__ ((deallocated_by (my_dealloc)))
  void* my_alloc (int);

  void f (char *p)
  {
    char *q = my_alloc (1);
    int c = p < q;               // is this considered valid?
    my_dealloc (p);
    my_dealloc (q);              // if yes, is this valid?
    return c;
  }

With attribute malloc, my prototype diagnoses the inequality because
the two pointers point to distinct objects.  If the inequality in
the above were to be considered valid with deallocated_by (and without
malloc) it would imply that p and q could point to the same object,
and, in fact, be equal, making the second call to my_dealloc() invalid.

The attribute could be specified in a way to give sensible asnwers
to these questions, but I'm not sure they can be answered from
the documentation diff.  With attribute malloc, on the other hand,
the answers are well established.  That doesn't preclude adding
deallocated_by, but I'd think it should be a superset of malloc.
For instance, it could be required to go along with it when
specified for pointers.  As I mentioned, I have misgivings about
allowing it on other types but I haven't thought about those use
cases long or hard enough to either dispel or validate them.

After thinking about this over lunch I wonder if it might be better
to itroduce two attributes: one for memory allocation and pointers
and another for non-pointer types.

...
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index c779d13f023..ce2a958a1bc 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -127,6 +127,7 @@ static tree handle_cleanup_attribute (tree *, tree, tree, 
int, bool *);
  static tree handle_warn_unused_result_attribute (tree *, tree, tree, int,
                                                 bool *);
  static tree handle_access_attribute (tree *, tree, tree, int, bool *);
+static tree handle_deallocated_by_attribute  (tree *, tree, tree, int, bool *);
static tree handle_sentinel_attribute (tree *, tree, tree, int, bool *);
  static tree handle_type_generic_attribute (tree *, tree, tree, int, bool *);
@@ -491,6 +492,8 @@ const struct attribute_spec c_common_attribute_table[] =
                              handle_noinit_attribute, attr_noinit_exclusions },
    { "access",                     1, 3, false, true, true, false,
                              handle_access_attribute, NULL },
+  { "deallocated_by",              1, 1, true, false, false, false,
+                             handle_deallocated_by_attribute, NULL},
    { NULL,                     0, 0, false, false, false, false, NULL, NULL }
  };
@@ -4663,6 +4666,119 @@ build_attr_access_from_parms (tree parms, bool skip_voidptr)
    return tree_cons (name, attrargs, NULL_TREE);
  }
+/* Return true if DEALLOCATOR_ARG_TYPE is a suitable type for the
+   initial argument of fndecl in __attribute((deallocated_by (fndecl) ))
+   when the attribute is applied to a function returning
+   ALLOCATOR_RETURN_TYPE.  */
+
+static bool
+matching_deallocator_type_p (const_tree deallocator_arg_type,
+                            const_tree allocator_return_type)
+{
+  /* It's OK if the deallocator is "void *" and the allocator returns
+     a pointer.  */
+  if (deallocator_arg_type == ptr_type_node
+      && POINTER_TYPE_P (allocator_return_type))
+    return true;
+
+  /* Check for exact match.  */
+  if (deallocator_arg_type == allocator_return_type)
+    return true;
+
+  /* TODO: do we care about C-style "inheritance" such as in GTK
+     and CPython?  */
+
+  return false;
+}
+
+/* Subroutine of handle_deallocated_by_attribute.
+   Idempotently add a special attribute to the fndecl referenced
+   by __attribute__ ((deallocated_by(FOO))), marking it as such.  */
+
+static void
+maybe_add_deallocator_attribute (tree deallocator_decl)
+{
+  /* This special attribute name has spaces in it, so it can't
+     be added via the normal syntax.  */
+  const char *attr_name_str = "arg of deallocated_by";
+  tree old_attrs = DECL_ATTRIBUTES (deallocator_decl);
+
+  /* Idempotency.  */
+  if (old_attrs)
+    if (lookup_attribute (attr_name_str, old_attrs))
+      return;
+
+  tree attr_name = get_identifier (attr_name_str);
+  tree new_attrs = tree_cons (attr_name, NULL_TREE, old_attrs);
+
+  DECL_ATTRIBUTES (deallocator_decl) = new_attrs;
+}
+
+/* Handle the "deallocated_by" attribute (fndecl).  */
+
+static tree
+handle_deallocated_by_attribute (tree *node, tree name, tree args,
+                                int ARG_UNUSED (flags), bool *no_add_attrs)
+{
+  if (TREE_CODE (*node) != FUNCTION_DECL)
+    {
+      if (warning (OPT_Wattributes, "%qE attribute ignored", name))
+       inform (input_location, "%qE is only usable on function declarations",
+               name);
+      return NULL_TREE;
+    }
+
+  const_tree allocator_fndecl_return_type = TREE_TYPE (TREE_TYPE ((*node)));
+
+  if (VOID_TYPE_P (allocator_fndecl_return_type))
+    {
+      error ("%qE attribute applied to function with %<void%> return type",
+            name);

Elsewhere a similar message says:

  "%qE attribute on function returning %<void%>"

I don't have a preference for one or the other but I would suggest
using the same text for consistency, and to avoid growing the amount
of text that needs to be translated.


+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  tree deallocator_decl = TREE_VALUE (args);
+  if (TREE_CODE (deallocator_decl) != FUNCTION_DECL)
+    {
+      error ("%qE argument not a function", name);
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  /* Check the type of deallocator function.
+     It must have at least one argument; the first argument must have
+     the same type as the return type of fndecl we're applying the
+     attribute to.  */
+  const_tree dealloc_arg_types = TYPE_ARG_TYPES (TREE_TYPE (deallocator_decl));
+  if (dealloc_arg_types == NULL_TREE
+      || VOID_TYPE_P (TREE_VALUE (dealloc_arg_types)))
+    {
+      error ("%qE argument must have at least one argument", name);
+      inform (DECL_SOURCE_LOCATION (deallocator_decl), "declared here");
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+  const tree deallocator_first_arg_type = TREE_VALUE (dealloc_arg_types);
+  if (!matching_deallocator_type_p (deallocator_first_arg_type,
+                                   allocator_fndecl_return_type))
+    {
+      error ("%qE attribute applied to function returning type %qT...",
+            name, allocator_fndecl_return_type);
+      inform (DECL_SOURCE_LOCATION (deallocator_decl),
+             "...whereas %qD accepts type %qT",

FWIW, I don't like being overly nit-picky, but I can't help but notice
that the "... ..." style is fairly rare among GCC messages.  It would
make my OCD better if these messages followed the established style
without the ellipses.


+             deallocator_decl, deallocator_first_arg_type);
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  /* The attribute should also affect the deallocator fndecl.
+     Add a special attribute to mark it as such.  */
+  maybe_add_deallocator_attribute (deallocator_decl);
+
+  return NULL_TREE;
+}
+
  /* Handle a "nothrow" attribute; arguments as in
     struct attribute_spec.handler.  */
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index b9684dc7a06..cc4f26e9ab0 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -2835,6 +2835,95 @@ extern __attribute__ ((alloc_size (1), malloc, nothrow))
  StrongAlias (allocate, alloc);
  @end smallexample
+@item deallocated_by (@var{function})
+@cindex @code{deallocated_by} function attribute
+The @code{deallocated_by} attribute indicates that the function it applies
+to is an allocator function, and that the result of calls to the function
+ought to be deallocated by @var{function}.  This affects how
+@option{-fanalyzer} handles both functions: the function with the attribute
+is treated as an allocator, and @var{function} is treated as a deallocator.
+The attribute can only be applied to functions that return a non-void
+result.  The first argument of the deallocator function must
+have a type matching the result of the allocator function.  Alternatively,
+if both are pointers, the deallocator function can accept a ``void *''.

If it is decided to keep the attribute apply to resources other than
pointers I think it should be called out in the first paragaraph.
I suspect most readers will not expect it (I didn't at first).

+
+For example, in the following
+
+@smallexample
+extern void foo_release (struct foo *);
+extern struct foo *foo_acquire (void)
+  __attribute__ ((deallocated_by(foo_release)));
+@end smallexample
+
+the function ``foo_acquire'' is marked as being an allocator, with the
+resulting ``struct foo *'' needing to be deallocated with a call to
+``foo_release''.
+
+A deallocation function can be shared between more than one allocation
+function - for example, many functions could be marked
+@code{__attribute__ ((deallocated_by(free)))}.

If it's valid for the same allocation function to designate two or
more deallocators I think it should also be called out here, ideally
with an example for readers to understand when that might be useful.

+
+This attribute is used by @option{-fanalyzer} to enable various warnings:
+
+@itemize @bullet
+@item
+The analyzer will emit a @option{-Wanalyzer-mismatching-deallocation}
+diagnostic if there is an execution path in which the result of an
+allocation call is passed to a different deallocator.

Suggest to make clear what "different" means, e.g.,:

  ...call is passed to a<del> different</del> deallocator<ins>
  not associated with the corresponding allocator by means of
  the attribute</ins>.

+
+@item
+The analyzer will emit a @option{-Wanalyzer-double-free}
+diagnostic if there is an execution path in which a value is passed
+more than once to a deallocation call.
+
+@item
+The analyzer will assume that the allocation function could fail and
+return NULL.

I think this should be @code{NULL}.

Martin

 It will emit @option{-Wanalyzer-possible-null-dereference}
+and @option{-Wanalyzer-possible-null-argument} diagnostics if there
+are execution paths in which an unchecked result of an allocation call is
+dereferenced or passed to a function requiring a non-null argument.
+If the allocator always returns non-null, use
+@code{__attribute__ ((returns_nonnull))} to suppress these warnings.
+For example:
+@smallexample
+char *xstrdup (const char *)
+  __attribute__((malloc, returns_nonnull, deallocated_by(free)));
+@end smallexample
+
+@item
+The analyzer will emit a @option{-Wanalyzer-use-after-free}
+diagnostic if there is an execution path in which the memory passed
+by pointer to a deallocation call is used after the deallocation.
+
+@item
+The analyzer will emit a @option{-Wanalyzer-malloc-leak} diagnostic if
+there is an execution path in which the result of an allocation call
+is leaked (without being passed to the deallocation function).
+
+@item
+The analyzer will emit a @option{-Wanalyzer-free-of-non-heap} diagnostic
+if the deallocation function is used on a global or on-stack variable.
+
+@end itemize
+
+The @code{deallocated_by} attribute differs from the @code{malloc}
+attribute in that the former marks an allocation/deallocation pair,
+whereas the latter gives hints about pointer aliasing to the optimizer.
+It may make sense to mark a function declaration with both attributes,
+such as:
+
+@smallexample
+extern void foo_release (struct foo *);
+extern struct foo *foo_acquire (void)
+  __attribute__ ((malloc, deallocated_by(foo_release)));
+@end smallexample
+
+It is assumed that the deallocator can gracefully handle the NULL pointer.
+If this is not the case, the deallocator can be marked with
+@code{__attribute__((nonnull))} so that @option{-fanalyzer} can emit
+a @option{-Wanalyzer-possible-null-argument} diagnostic for code paths
+in which the deallocator is called with NULL.
+
  @item deprecated
  @itemx deprecated (@var{msg})
  @cindex @code{deprecated} function attribute
diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-deallocated_by-1.c 
b/gcc/testsuite/gcc.dg/analyzer/attr-deallocated_by-1.c
new file mode 100644
index 00000000000..61fb0260136
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/attr-deallocated_by-1.c
@@ -0,0 +1,74 @@
+extern void free (void *);
+
+struct foo
+{
+  int m_int;
+};
+
+extern void foo_release (struct foo *);
+extern struct foo *foo_acquire (void)
+  __attribute__ ((malloc, deallocated_by(foo_release)));
+extern void use_foo (const struct foo *)
+  __attribute__((nonnull));
+
+void test_1 (void)
+{
+  struct foo *p = foo_acquire ();
+  foo_release (p);
+}
+
+void test_2 (void)
+{
+  struct foo *p = foo_acquire (); /* { dg-message "this call could return 
NULL" } */
+  p->m_int = 42; /* { dg-warning "dereference of possibly-NULL 'p'" } */
+  foo_release (p);
+}
+
+void test_2a (void)
+{
+  struct foo *p = foo_acquire (); /* { dg-message "this call could return 
NULL" } */
+  use_foo (p); /* { dg-warning "use of possibly-NULL 'p' where non-null 
expected" } */
+  foo_release (p);
+}
+
+void test_3 (void)
+{
+  struct foo *p = foo_acquire (); /* { dg-message "allocated here" } */
+} /* { dg-warning "leak of 'p'" } */
+
+void test_4 (struct foo *p)
+{
+  foo_release (p);
+  foo_release (p); /* { dg-warning "double-'foo_release' of 'p'" } */
+}
+
+void test_4a (void)
+{
+  struct foo *p = foo_acquire ();
+  foo_release (p);
+  foo_release (p); /* { dg-warning "double-'foo_release' of 'p'" } */
+}
+
+void test_5 (void)
+{
+  struct foo *p = foo_acquire (); /* { dg-message "allocated here \\(expects 
deallocation with 'foo_release'\\)" } */
+  free (p); /* { dg-warning "'p' should have been deallocated with 'foo_release' 
but was deallocated with 'free'" } */
+}
+
+void test_6 (struct foo *p)
+{
+  foo_release (p);
+  free (p); // TODO: double-release warning!
+}
+
+void test_7 ()
+{
+  struct foo f;
+  foo_release (&f); /* { dg-warning "not on the heap" } */
+}
+
+int test_8 (struct foo *p)
+{
+  foo_release (p);
+  return p->m_int; /* { dg-warning "use after 'foo_release' of 'p'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-deallocated_by-1a.c 
b/gcc/testsuite/gcc.dg/analyzer/attr-deallocated_by-1a.c
new file mode 100644
index 00000000000..14ea7d3f9d5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/attr-deallocated_by-1a.c
@@ -0,0 +1,69 @@
+/* As attr-deallocated_by-1a.c, but without "malloc" attribute on foo_acquire. 
 */
+
+extern void free (void *);
+
+struct foo
+{
+  int m_int;
+};
+
+extern void foo_release (struct foo *);
+extern struct foo *foo_acquire (void)
+  __attribute__ ((deallocated_by(foo_release)));
+extern void use_foo (const struct foo *)
+  __attribute__((nonnull));
+
+void test_1 (void)
+{
+  struct foo *p = foo_acquire ();
+  foo_release (p);
+}
+
+void test_2 (void)
+{
+  struct foo *p = foo_acquire (); /* { dg-message "this call could return 
NULL" } */
+  p->m_int = 42; /* { dg-warning "dereference of possibly-NULL 'p'" } */
+  foo_release (p);
+}
+
+void test_2a (void)
+{
+  struct foo *p = foo_acquire (); /* { dg-message "this call could return 
NULL" } */
+  use_foo (p); /* { dg-warning "use of possibly-NULL 'p' where non-null 
expected" } */
+  foo_release (p);
+}
+
+void test_3 (void)
+{
+  struct foo *p = foo_acquire (); /* { dg-message "allocated here" } */
+} /* { dg-warning "leak of 'p'" } */
+
+void test_4 (struct foo *p)
+{
+  foo_release (p);
+  foo_release (p); /* { dg-warning "double-'foo_release' of 'p'" } */
+}
+
+void test_5 (void)
+{
+  struct foo *p = foo_acquire (); /* { dg-message "allocated here \\(expects 
deallocation with 'foo_release'\\)" } */
+  free (p); /* { dg-warning "'p' should have been deallocated with 'foo_release' 
but was deallocated with 'free'" } */
+}
+
+void test_6 (struct foo *p)
+{
+  foo_release (p);
+  free (p); // TODO: double-release warning!
+}
+
+void test_7 ()
+{
+  struct foo f;
+  foo_release (&f); /* { dg-warning "not on the heap" } */
+}
+
+int test_8 (struct foo *p)
+{
+  foo_release (p);
+  return p->m_int; /* { dg-warning "use after 'foo_release' of 'p'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-deallocated_by-2.c 
b/gcc/testsuite/gcc.dg/analyzer/attr-deallocated_by-2.c
new file mode 100644
index 00000000000..2a669d99d36
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/attr-deallocated_by-2.c
@@ -0,0 +1,24 @@
+extern void free (void *);
+char *xstrdup (const char *)
+  __attribute__((malloc, returns_nonnull, deallocated_by(free)));
+
+void test_1 (const char *s)
+{
+  char *p = xstrdup (s);
+  free (p);
+}
+
+/* Verify that we don't issue -Wanalyzer-possible-null-dereference
+   when the allocator has __attribute__((returns_nonnull)).  */
+
+char *test_2 (const char *s)
+{
+  char *p = xstrdup (s);
+  p[0] = 'a'; /* { dg-bogus "possibly-NULL" } */
+  return p;
+}
+
+void test_3 (const char *s)
+{
+  char *p = xstrdup (s); /* { dg-message "allocated here" } */
+} /* { dg-warning "leak of 'p'" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-deallocated_by-3.c 
b/gcc/testsuite/gcc.dg/analyzer/attr-deallocated_by-3.c
new file mode 100644
index 00000000000..ef615531e17
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/attr-deallocated_by-3.c
@@ -0,0 +1,31 @@
+/* A non-pointer resource.  */
+
+typedef int handle_t;
+
+extern void handle_release (handle_t h);
+extern handle_t handle_acquire (void)
+  __attribute__ ((deallocated_by(handle_release)));
+extern void unknown_fn (handle_t h);
+
+void test_1 (void)
+{
+  handle_t h = handle_acquire ();
+  handle_release (h);
+}
+
+void test_2 (void)
+{
+  handle_t h = handle_acquire (); /* { dg-message "allocated here" } */
+} /* { dg-warning "leak of 'h'" } */
+
+void test_3 (handle_t h)
+{
+  handle_release (h);
+  handle_release (h); /* { dg-warning "double-'handle_release' of 'h'" } */
+}
+
+void test_4 (void)
+{
+  handle_t h = handle_acquire ();
+  unknown_fn (h);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-deallocated_by-4.c 
b/gcc/testsuite/gcc.dg/analyzer/attr-deallocated_by-4.c
new file mode 100644
index 00000000000..a62f45bd571
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/attr-deallocated_by-4.c
@@ -0,0 +1,21 @@
+/* An example where the deallocator requires non-NULL.  */
+
+struct foo;
+extern void foo_release (struct foo *)
+  __attribute__((nonnull));
+extern struct foo *foo_acquire (void)
+  __attribute__ ((malloc, deallocated_by(foo_release)));
+
+void test_1 (void)
+{
+  struct foo *p = foo_acquire (); /* { dg-message "this call could return 
NULL" } */
+  foo_release (p); /* { dg-warning "use of possibly-NULL 'p' where non-null" } 
*/
+}
+
+void test_2 (void)
+{
+  struct foo *p = foo_acquire ();
+  if (!p)
+    return;
+  foo_release (p);
+}
diff --git 
a/gcc/testsuite/gcc.dg/analyzer/attr-deallocated_by-CVE-2019-19078-usb-leak.c 
b/gcc/testsuite/gcc.dg/analyzer/attr-deallocated_by-CVE-2019-19078-usb-leak.c
new file mode 100644
index 00000000000..a008c50ad50
--- /dev/null
+++ 
b/gcc/testsuite/gcc.dg/analyzer/attr-deallocated_by-CVE-2019-19078-usb-leak.c
@@ -0,0 +1,208 @@
+/* Adapted from linux 5.3.11: drivers/net/wireless/ath/ath10k/usb.c
+   Reduced reproducer for CVE-2019-19078 (leak of struct urb).  */
+
+typedef unsigned char u8;
+typedef unsigned short u16;
+typedef _Bool bool;
+
+#define        ENOMEM          12
+#define        EINVAL          22
+
+/* The original file has this licence header.  */
+
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2007-2011 Atheros Communications Inc.
+ * Copyright (c) 2011-2012,2017 Qualcomm Atheros, Inc.
+ * Copyright (c) 2016-2017 Erik Stromdahl <erik.stromd...@gmail.com>
+ */
+
+/* Adapted from include/linux/compiler_attributes.h.  */
+#define __aligned(x)                    __attribute__((__aligned__(x)))
+#define __printf(a, b)                  __attribute__((__format__(printf, a, 
b)))
+
+/* Possible macro for the new attribute.  */
+#define __deallocated_by(f)      __attribute__((deallocated_by(f)));
+
+/* From include/linux/types.h.  */
+
+typedef unsigned int gfp_t;
+
+/* Not the real value, which is in include/linux/gfp.h.  */
+#define GFP_ATOMIC     32
+
+/* From include/linux/usb.h.  */
+
+struct urb;
+extern void usb_free_urb(struct urb *urb);
+extern struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)
+  __deallocated_by(usb_free_urb);
+/* attribute added as part of testcase */
+
+extern int usb_submit_urb(/*struct urb *urb, */gfp_t mem_flags);
+extern void usb_unanchor_urb(struct urb *urb);
+
+/* From drivers/net/wireless/ath/ath10k/core.h.  */
+
+struct ath10k;
+
+struct ath10k {
+       /* [...many other fields removed...]  */
+
+       /* must be last */
+       u8 drv_priv[0] __aligned(sizeof(void *));
+};
+
+/* From drivers/net/wireless/ath/ath10k/debug.h.  */
+
+enum ath10k_debug_mask {
+       /* [...other values removed...]  */
+       ATH10K_DBG_USB_BULK     = 0x00080000,
+};
+
+extern unsigned int ath10k_debug_mask;
+
+__printf(3, 4) void __ath10k_dbg(struct ath10k *ar,
+                                enum ath10k_debug_mask mask,
+                                const char *fmt, ...);
+
+/* Simplified for now, to avoid pulling in tracepoint code.  */
+static inline
+bool trace_ath10k_log_dbg_enabled(void) { return 0; }
+
+#define ath10k_dbg(ar, dbg_mask, fmt, ...)                     \
+do {                                                           \
+       if ((ath10k_debug_mask & dbg_mask) ||                       \
+           trace_ath10k_log_dbg_enabled())                     \
+               __ath10k_dbg(ar, dbg_mask, fmt, ##__VA_ARGS__); \
+} while (0)
+
+/* From drivers/net/wireless/ath/ath10k/hif.h.  */
+
+struct ath10k_hif_sg_item {
+       /* [...other fields removed...]  */
+       void *transfer_context; /* NULL = tx completion callback not called */
+};
+
+/* From drivers/net/wireless/ath/ath10k/usb.h.  */
+
+/* tx/rx pipes for usb */
+enum ath10k_usb_pipe_id {
+       /* [...other values removed...]  */
+       ATH10K_USB_PIPE_MAX = 8
+};
+
+struct ath10k_usb_pipe {
+       /* [...all fields removed...]  */
+};
+
+/* usb device object */
+struct ath10k_usb {
+       /* [...other fields removed...]  */
+       struct ath10k_usb_pipe pipes[ATH10K_USB_PIPE_MAX];
+};
+
+/* usb urb object */
+struct ath10k_urb_context {
+       /* [...other fields removed...]  */
+       struct ath10k_usb_pipe *pipe;
+       struct sk_buff *skb;
+};
+
+static inline struct ath10k_usb *ath10k_usb_priv(struct ath10k *ar)
+{
+       return (struct ath10k_usb *)ar->drv_priv;
+}
+
+/* The source file.  */
+
+static void ath10k_usb_post_recv_transfers(struct ath10k *ar,
+                                          struct ath10k_usb_pipe *recv_pipe);
+
+struct ath10k_urb_context *
+ath10k_usb_alloc_urb_from_pipe(struct ath10k_usb_pipe *pipe);
+
+void ath10k_usb_free_urb_to_pipe(struct ath10k_usb_pipe *pipe,
+                                struct ath10k_urb_context *urb_context);
+
+/* TODO: this was static and a callback; if made static it's never analyzed
+   (PR analyzer/97258).  */
+
+int ath10k_usb_hif_tx_sg(struct ath10k *ar, u8 pipe_id,
+                        struct ath10k_hif_sg_item *items, int n_items)
+{
+       struct ath10k_usb *ar_usb = ath10k_usb_priv(ar);
+       struct ath10k_usb_pipe *pipe = &ar_usb->pipes[pipe_id];
+       struct ath10k_urb_context *urb_context;
+       struct sk_buff *skb;
+       struct urb *urb;
+       int ret, i;
+
+       for (i = 0; i < n_items; i++) {
+               urb_context = ath10k_usb_alloc_urb_from_pipe(pipe);
+               if (!urb_context) {
+                       ret = -ENOMEM;
+                       goto err;
+               }
+
+               skb = items[i].transfer_context;
+               urb_context->skb = skb;
+
+               urb = usb_alloc_urb(0, GFP_ATOMIC); /* { dg-message "allocated 
here" } */
+               if (!urb) {
+                       ret = -ENOMEM;
+                       goto err_free_urb_to_pipe;
+               }
+
+               /* TODO: these are disabled, otherwise we conservatively
+                  assume that they could free urb.  */
+#if 0
+               usb_fill_bulk_urb(urb,
+                                 ar_usb->udev,
+                                 pipe->usb_pipe_handle,
+                                 skb->data,
+                                 skb->len,
+                                 ath10k_usb_transmit_complete, urb_context);
+               if (!(skb->len % pipe->max_packet_size)) {
+                       /* hit a max packet boundary on this pipe */
+                       urb->transfer_flags |= URB_ZERO_PACKET;
+               }
+
+               usb_anchor_urb(urb, &pipe->urb_submitted);
+#endif
+               /* TODO: initial argument disabled, otherwise we conservatively
+                  assume that it could free urb.  */
+               ret = usb_submit_urb(/*urb, */GFP_ATOMIC);
+               if (ret) { /* TODO: why doesn't it show this condition at 
default verbosity?  */
+                       ath10k_dbg(ar, ATH10K_DBG_USB_BULK,
+                                  "usb bulk transmit failed: %d\n", ret);
+
+                       /* TODO: this is disabled, otherwise we conservatively
+                          assume that it could free urb.  */
+#if 0
+                       usb_unanchor_urb(urb);
+#endif
+
+                       ret = -EINVAL;
+                       /* Leak of urb happens here.  */
+                       goto err_free_urb_to_pipe;
+               }
+
+               /* TODO: the loop confuses the double-free checker (another
+                  instance of PR analyzer/93695).  */
+               usb_free_urb(urb); /* { dg-bogus "double-'usb_free_urb' of 'urb'" 
"" { xfail *-*-* } } */
+       }
+
+       return 0;
+
+err_free_urb_to_pipe:
+       ath10k_usb_free_urb_to_pipe(urb_context->pipe, urb_context);
+err:
+       return ret; /* { dg-warning "leak of 'urb'" } */
+}
+
+/* The original source file ends with:
+MODULE_AUTHOR("Atheros Communications, Inc.");
+MODULE_DESCRIPTION("Driver support for Qualcomm Atheros 802.11ac WLAN USB 
devices");
+MODULE_LICENSE("Dual BSD/GPL");
+*/
diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-deallocated_by-misuses.c 
b/gcc/testsuite/gcc.dg/analyzer/attr-deallocated_by-misuses.c
new file mode 100644
index 00000000000..ddbda0d2161
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/attr-deallocated_by-misuses.c
@@ -0,0 +1,26 @@
+extern void free (void *);
+
+int not_a_fn __attribute__ ((deallocated_by(free))); /* { dg-warning 
"'deallocated_by' attribute ignored" } */
+/* { dg-message "'deallocated_by' is only usable on function declarations" "" 
{ target *-*-* } .-1 } */
+
+void void_return (void) __attribute__ ((deallocated_by(free))); /* { dg-error 
"'deallocated_by' attribute applied to function with 'void' return type" } */
+
+void test_void_return (void)
+{
+  void_return ();
+}
+
+extern void void_args (void); /* { dg-message "declared here" } */
+void *has_deallocated_by_with_void_args (void)
+  __attribute__ ((deallocated_by(void_args))); /* { dg-error "'deallocated_by' 
argument must have at least one argument" } */
+
+extern void no_args (); /* { dg-message "declared here" } */
+void *has_deallocated_by_with_no_args (void)
+  __attribute__ ((deallocated_by(no_args))); /* { dg-error "'deallocated_by' 
argument must have at least one argument" } */
+
+/* Mismatching types.  */
+struct foo {};
+struct bar {};
+extern void takes_foo (struct foo *); /* { dg-message "\\.\\.\\.whereas 'takes_foo' 
accepts type 'struct foo \\*'" } */
+struct bar *wrong_deallocator_type (void)
+  __attribute__ ((deallocated_by(takes_foo))); /* { dg-error "'deallocated_by' 
attribute applied to function returning type 'struct bar \\*'\\.\\.\\." } */


Reply via email to