The nonnull function attribute could previously only be written on the
function itself, using an argument index to identify each pointer
parameter that must not be null:

  void f (char *p, int n) __attribute__ ((nonnull (1)));

This requires the reader to count parameters and keep the index in sync
with the signature.  Allow nonnull to be written directly on the pointer
parameter instead, in both C and C++:

  void f (char * __attribute__ ((nonnull)) p, int n);
  void g ([[gnu::nonnull]] char *p, int n);

The existing nonnull attribute spec cannot be reused for parameters because
it requires type_required=true so that the attribute lands on the function
type and is visible to middle-end consumers that check TYPE_ATTRIBUTES at
call sites, including indirect calls through function pointers where no
decl is available.  With type_required=true the generic attribute handling
redirects any decl to its type before the handler runs, so a PARM_DECL
would be replaced by its pointer type shared across the whole translation
unit and the attribute would incorrectly annotate every use of that
pointer type.  A separate "*nonnull parm" spec with decl_required=true is
therefore used so the marker lands on the PARM_DECL.

A bare nonnull (with no argument index) written on a parameter is
rewritten into an internal "*nonnull parm" marker on the PARM_DECL.  The
marker is collected once the whole parameter list is known and
synthesized back into a function-level nonnull (N), so the existing
-Wnonnull handling diagnoses null arguments unchanged.  Synthesis is
done in the C++ front end's grokdeclarator (cdk_function case) and in the
C front end's build_attr_access_from_parms, so it also covers
prototype-only declarations.  For a non-static member function the
implicit "this" parameter shifts the user-visible positions by one, which
is accounted for during synthesis.

A parameter-level nonnull takes no arguments and the parameter must have
pointer type; nonnull (arg-index) syntax on a parameter, or the attribute
on a non-pointer parameter, is diagnosed.  A pointer-to-function or
(in C++) pointer-to-member-function parameter keeps the historical
meaning of nonnull, namely redirecting the attribute to the pointed-to
function type rather than marking the parameter itself.

gcc/c-family/ChangeLog:

        * c-common.h (c_mark_parm_nonnull): Declare.
        * c-attribs.cc (handle_nonnull_parm_attribute): New function.
        (c_mark_parm_nonnull): New function.
        (c_common_gnu_attributes): Add "*nonnull parm".
        (build_attr_access_from_parms): Synthesize a function-level
        nonnull attribute from per-parameter "*nonnull parm" markers.

gcc/c/ChangeLog:

        * c-decl.cc (grokparm): Rewrite a bare nonnull on a parameter
        into the internal "*nonnull parm" marker.
        (push_parm_decl): Likewise.
        (grokdeclarator): Likewise for a parameter declarator.

gcc/cp/ChangeLog:

        * decl.cc (grokdeclarator): Rewrite a bare nonnull on a parameter
        into the internal "*nonnull parm" marker, and synthesize a
        function-level nonnull from the parameter markers.
        * parser.cc (cp_parser_parameter_declaration_list): Rewrite a
        bare nonnull on a parameter into the internal marker.

gcc/ChangeLog:

        * doc/extend.texi (Common Function Attributes): Document the
        nonnull attribute on function parameters.

gcc/testsuite/ChangeLog:

        * g++.dg/warn/Wnonnull-parm-1.C: New test.
        * g++.dg/warn/Wnonnull-parm-2.C: New test.
        * gcc.dg/Wnonnull-parm-1.c: New test.

Signed-off-by: Andrei Rusanescu <[email protected]>
---
 gcc/c-family/c-attribs.cc                   | 84 ++++++++++++++++++++-
 gcc/c-family/c-common.h                     |  1 +
 gcc/c/c-decl.cc                             | 11 +++
 gcc/cp/decl.cc                              | 62 +++++++++++++--
 gcc/cp/parser.cc                            | 13 +++-
 gcc/doc/extend.texi                         | 23 ++++++
 gcc/testsuite/g++.dg/warn/Wnonnull-parm-1.C | 36 +++++++++
 gcc/testsuite/g++.dg/warn/Wnonnull-parm-2.C | 14 ++++
 gcc/testsuite/gcc.dg/Wnonnull-parm-1.c      | 29 +++++++
 9 files changed, 263 insertions(+), 10 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/warn/Wnonnull-parm-1.C
 create mode 100644 gcc/testsuite/g++.dg/warn/Wnonnull-parm-2.C
 create mode 100644 gcc/testsuite/gcc.dg/Wnonnull-parm-1.c

diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc
index d668ab96630..98d57267ddc 100644
--- a/gcc/c-family/c-attribs.cc
+++ b/gcc/c-family/c-attribs.cc
@@ -139,6 +139,7 @@ static tree handle_vector_size_attribute (tree *, tree, 
tree, int,
 static tree handle_vector_mask_attribute (tree *, tree, tree, int,
                                          bool *) ATTRIBUTE_NONNULL(3);
 static tree handle_nonnull_attribute (tree *, tree, tree, int, bool *);
+static tree handle_nonnull_parm_attribute (tree *, tree, tree, int, bool *);
 static tree handle_nonnull_if_nonzero_attribute (tree *, tree, tree, int,
                                                 bool *);
 static tree handle_nonstring_attribute (tree *, tree, tree, int, bool *);
@@ -511,6 +512,8 @@ const struct attribute_spec c_common_gnu_attributes[] =
                              handle_tls_model_attribute, NULL },
   { "nonnull",                0, -1, false, true, true, false,
                              handle_nonnull_attribute, NULL },
+  { "*nonnull parm",         0, -1, true, false, false, false,
+                             handle_nonnull_parm_attribute, NULL },
   { "nonnull_if_nonzero",     2, 3, false, true, true, false,
                              handle_nonnull_if_nonzero_attribute, NULL },
   { "nonstring",              0, 0, true, false, false, false,
@@ -5105,6 +5108,65 @@ handle_nonnull_attribute (tree *node, tree name,
   return NULL_TREE;
 }
 
+/* Handle the "*nonnull parm" attribute.  */
+
+static tree
+handle_nonnull_parm_attribute (tree *node, tree ARG_UNUSED (name),
+                              tree args, int ARG_UNUSED (flags),
+                              bool *no_add_attrs)
+{
+  tree decl = *node;
+
+  if (TREE_CODE (decl) != PARM_DECL)
+    {
+      warning (OPT_Wattributes, "%<nonnull%> attribute ignored");
+      *no_add_attrs = true;
+    }
+  else if (args)
+    {
+      warning (OPT_Wattributes,
+              "%<nonnull%> attribute on a function parameter takes no "
+              "arguments");
+      *no_add_attrs = true;
+    }
+  else if (!POINTER_TYPE_P (TREE_TYPE (decl)))
+    {
+      warning (OPT_Wattributes, "%<nonnull%> attribute ignored");
+      *no_add_attrs = true;
+    }
+
+  return NULL_TREE;
+}
+
+/* Rewrite any user-written "nonnull" attribute in ATTRS, which is destined
+   for a PARM_DECL of type TYPE, into the internal "*nonnull parm" marker so
+   that it reaches the dedicated handler instead of the generic attribute
+   handler.  On a pointer to a function type the "nonnull" keeps its
+   historical meaning of redirecting to the pointed-to function type, so it
+   is left untouched.  For every other parameter type the dedicated handler
+   decides whether the attribute is valid and, if not, emits a single clear
+   diagnostic.
+
+   Handles both GNU __attribute__ and C++11 encodings.  */
+
+tree
+c_mark_parm_nonnull (tree attrs, tree type)
+{
+  if (type == NULL_TREE
+      || (POINTER_TYPE_P (type) && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (type))))
+    return attrs;
+
+  for (tree *p = &attrs; *p; p = &TREE_CHAIN (*p))
+    {
+      tree a = *p;
+      if (is_attribute_p ("nonnull", get_attribute_name (a)))
+       *p = tree_cons (get_identifier ("*nonnull parm"),
+                       TREE_VALUE (a),
+                       TREE_CHAIN (a));
+    }
+  return attrs;
+}
+
 /* Handle the "nonnull_if_nonzero" attribute.  */
 
 static tree
@@ -6162,8 +6224,28 @@ build_attr_access_from_parms (tree parms, bool 
skip_voidptr)
        }
     }
 
+  /* Collect positions of parameters carrying a per-parameter "*nonnull parm"
+     marker (written by the user as __attribute__ ((nonnull)) or
+     [[gnu::nonnull]] on the parameter itself).  */
+  argpos = 0;
+  for (tree arg = parms; arg; arg = TREE_CHAIN (arg), ++argpos)
+    {
+      if (!DECL_P (arg))
+       continue;
+      if (POINTER_TYPE_P (TREE_TYPE (arg))
+         && lookup_attribute ("*nonnull parm", DECL_ATTRIBUTES (arg)))
+       nnlist = tree_cons (NULL_TREE,
+                           build_int_cst (integer_type_node, argpos + 1),
+                           nnlist);
+    }
+
   if (!spec.length ())
-    return NULL_TREE;
+    {
+      /* There may still be per-parameter nonnull markers.  */
+      if (nnlist != NULL_TREE)
+       return build_tree_list (get_identifier ("nonnull"), nnlist);
+      return NULL_TREE;
+    }
 
   /* If we have nonnull arguments, synthesize an attribute.  */
   if (nnlist != NULL_TREE)
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index bf7bc5eda5b..80d1ee8da7b 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -1704,6 +1704,7 @@ extern tree handle_noreturn_attribute (tree *, tree, 
tree, int, bool *);
 extern tree handle_musttail_attribute (tree *, tree, tree, int, bool *);
 extern bool has_attribute (location_t, tree, tree, tree (*)(tree));
 extern tree build_attr_access_from_parms (tree, bool);
+extern tree c_mark_parm_nonnull (tree, tree);
 extern void set_musttail_on_return (tree, location_t, bool);
 
 /* In c-format.cc.  */
diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
index 42d35a87a17..dd1635307be 100644
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -6405,6 +6405,11 @@ grokparm (const struct c_parm *parm, tree *expr)
   tree decl = grokdeclarator (parm->declarator, parm->specs, PARM, false,
                              NULL, &attrs, expr, NULL, DEPRECATED_NORMAL);
 
+  /* Rewrite any user-written "nonnull" in ATTRS into the internal
+     "*nonnull parm" marker.  The marker is picked up by
+     build_attr_access_from_parms and synthesized into nonnull (N).  */
+  attrs = c_mark_parm_nonnull (attrs, decl ? TREE_TYPE (decl) : NULL_TREE);
+
   decl_attributes (&decl, attrs, 0);
 
   return decl;
@@ -6426,6 +6431,7 @@ push_parm_decl (const struct c_parm *parm, tree *expr)
   if (decl && DECL_P (decl))
     DECL_SOURCE_LOCATION (decl) = parm->loc;
 
+  attrs = c_mark_parm_nonnull (attrs, decl ? TREE_TYPE (decl) : NULL_TREE);
   decl_attributes (&decl, attrs, 0);
 
   decl = pushdecl (decl);
@@ -7296,6 +7302,11 @@ grokdeclarator (const struct c_declarator *declarator,
                  attr_flags |= (int) ATTR_FLAG_ARRAY_NEXT;
              }
            attrs = c_warn_type_attributes (type, attrs);
+           /* Rewrite a bare "nonnull" on a parameter into the internal
+              "*nonnull parm" marker.  It is synthesized into nonnull (N)
+              on the function by build_attr_access_from_parms.  */
+           if (decl_context == PARM)
+             attrs = c_mark_parm_nonnull (attrs, type);
            returned_attrs = decl_attributes (&type,
                                              chainon (returned_attrs, attrs),
                                              attr_flags);
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index a2233107317..9a9a159200f 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -15050,7 +15050,16 @@ grokdeclarator (const cp_declarator *declarator,
 
          attr_flags = 0;
          if (declarator->kind == cdk_id)
-           attr_flags |= (int) ATTR_FLAG_DECL_NEXT;
+           {
+             attr_flags |= (int) ATTR_FLAG_DECL_NEXT;
+             /* Rewrite a bare "nonnull" on a parameter into the internal
+                "*nonnull parm" marker.  A pointer-to-member-function is left
+                alone: there "nonnull" redirects to the pointed-to function
+                type, as for a plain pointer to function.  */
+             if (decl_context == PARM
+                 && !(type && TYPE_PTRMEMFUNC_P (type)))
+               attrs = c_mark_parm_nonnull (attrs, type);
+           }
          if (declarator->kind == cdk_function)
            attr_flags |= (int) ATTR_FLAG_FUNCTION_NEXT;
          if (declarator->kind == cdk_array)
@@ -15594,6 +15603,42 @@ grokdeclarator (const cp_declarator *declarator,
 
            type = cp_build_function_type (type, arg_types);
 
+           /* Synthesize a function-level nonnull (N) from any "*nonnull parm"
+              markers on PARMS.  Done here rather than in store_parm_decls so
+              it also covers prototype-only declarations.  A non-static member
+              function's implicit "this" shifts the positions by one.  */
+           {
+             bool is_nonstatic_member = (decl_context == FIELD
+                                         && !staticp
+                                         && !is_xobj_member_function);
+             tree nnlist = NULL_TREE;
+             unsigned argpos = 0;
+             for (tree p = parms; p; p = DECL_CHAIN (p))
+               {
+                 ++argpos;
+                 if (TREE_CODE (p) == PARM_DECL
+                     && POINTER_TYPE_P (TREE_TYPE (p))
+                     && lookup_attribute ("*nonnull parm",
+                                          DECL_ATTRIBUTES (p)))
+                   {
+                     unsigned pos = argpos + (is_nonstatic_member ? 1 : 0);
+                     nnlist = tree_cons (NULL_TREE,
+                                         build_int_cst (integer_type_node,
+                                                        pos),
+                                         nnlist);
+                   }
+               }
+             if (nnlist)
+               {
+                 tree nn = build_tree_list (get_identifier ("nonnull"),
+                                            nreverse (nnlist));
+                 if (is_nonstatic_member)
+                   returned_attrs = attr_chainon (returned_attrs, nn);
+                 else
+                   cplus_decl_attributes (&type, nn, 0);
+               }
+           }
+
            tree attrs = declarator->std_attributes;
            if (tx_qual)
              {
@@ -15937,18 +15982,23 @@ grokdeclarator (const cp_declarator *declarator,
         a declarator-id appertains to the entity that is declared.  */
       if (declarator->std_attributes != error_mark_node)
        {
+         /* On a parameter, rewrite "nonnull" to the "*nonnull parm" marker
+            before chaining so the cdk_function synthesis picks it up.  */
+         tree std_attrs = declarator->std_attributes;
+         if (decl_context == PARM)
+           std_attrs = c_mark_parm_nonnull (std_attrs, type);
+
          if (flag_reflection
-             && declarator->std_attributes != error_mark_node
-             && lookup_annotation (declarator->std_attributes)
+             && std_attrs != error_mark_node
+             && lookup_annotation (std_attrs)
              && *attrlist != error_mark_node
              && lookup_annotation (*attrlist))
            /* If there are annotations in both lists, ensure
               declarator->std_attributes go after *attrlist.  See
               PR124399.  */
-           *attrlist = chainon (copy_list (*attrlist),
-                                declarator->std_attributes);
+           *attrlist = chainon (copy_list (*attrlist), std_attrs);
          else
-           *attrlist = attr_chainon (declarator->std_attributes, *attrlist);
+           *attrlist = attr_chainon (std_attrs, *attrlist);
        }
       else
        /* We should have already diagnosed the issue (c++/78344).  */
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index b57acc6cba5..1b5d79a33ce 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -28365,9 +28365,16 @@ cp_parser_parameter_declaration_list (cp_parser* 
parser,
        }
 
       if (parameter->decl_specifiers.attributes)
-       cplus_decl_attributes (&decl,
-                              parameter->decl_specifiers.attributes,
-                              0);
+       {
+         /* Rewrite "nonnull" to the "*nonnull parm" marker for synthesis in
+            grokdeclarator.  A pointer-to-member-function is left alone: there
+            "nonnull" redirects to the pointed-to function type.  */
+         tree ptype = TREE_TYPE (decl);
+         tree pattrs = parameter->decl_specifiers.attributes;
+         if (!(ptype && TYPE_PTRMEMFUNC_P (ptype)))
+           pattrs = c_mark_parm_nonnull (pattrs, ptype);
+         cplus_decl_attributes (&decl, pattrs, 0);
+       }
       if (DECL_NAME (decl))
        {
          /* We cannot always pushdecl while parsing tentatively because
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index a49772c5a57..ec8da0b981d 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -3875,6 +3875,29 @@ my_memcpy (void *dest, const void *src, size_t len)
         __attribute__((nonnull));
 @end smallexample
 
+The @code{nonnull} attribute may also be written directly on a pointer
+parameter rather than on the function.  Writing it on parameter @var{N}
+(with no @var{arg-index}) is equivalent to @code{nonnull (@var{N})} on
+the function.  For example, the following two declarations are
+equivalent:
+
+@smallexample
+void f (char * __attribute__ ((nonnull)) p, int n);
+void f (char *p, int n) __attribute__ ((nonnull (1)));
+@end smallexample
+
+The C++11 @code{[[gnu::nonnull]]} spelling is also supported on a
+parameter:
+
+@smallexample
+void g ([[gnu::nonnull]] char *p, int n);
+@end smallexample
+
+A parameter-level @code{nonnull} takes no arguments and the parameter
+must have pointer type; using @code{nonnull (@var{arg-index})} syntax
+on a parameter or applying it to a non-pointer parameter is diagnosed.
+This form is supported in both C and C++.
+
 @atindex @code{nonnull_if_nonzero}
 @cindex functions that have non-null pointer arguments
 @item nonnull_if_nonzero (@var{arg-index}, @var{arg2-index})
diff --git a/gcc/testsuite/g++.dg/warn/Wnonnull-parm-1.C 
b/gcc/testsuite/g++.dg/warn/Wnonnull-parm-1.C
new file mode 100644
index 00000000000..f281a7da051
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wnonnull-parm-1.C
@@ -0,0 +1,36 @@
+// Test the "nonnull" attribute applied directly to a pointer parameter.
+// Writing it on parameter N (with no arg-index) is equivalent to
+// nonnull (N) on the function, so a null argument is diagnosed under
+// -Wnonnull.
+// { dg-do compile { target c++11 } }
+// { dg-options "-Wnonnull" }
+
+void f ([[gnu::nonnull]] int *p, int n);
+
+// The GNU __attribute__ spelling attaches to the parameter too.
+void g (int * __attribute__((nonnull)) p);
+
+// A mix of a marked and an unmarked pointer parameter.
+void h (int *p, [[gnu::nonnull]] int *q);
+
+int gi;
+
+void
+test ()
+{
+  f (nullptr, 0);      // { dg-warning "argument 1 null where non-null 
expected" }
+  f (&gi, 0);
+
+  g (nullptr);         // { dg-warning "argument 1 null where non-null 
expected" }
+  g (&gi);
+
+  h (nullptr, &gi);
+  h (&gi, nullptr);    // { dg-warning "argument 2 null where non-null 
expected" }
+}
+
+// A pointer to member function parameter keeps the historical meaning of
+// "nonnull": it redirects to the pointed-to function type rather than being
+// treated as a per-parameter marker, so no diagnostic about a non-pointer
+// or a wrong argument count is emitted here.
+struct S {};
+void fn (void (S::*f) (int *) __attribute__((nonnull (2))));
diff --git a/gcc/testsuite/g++.dg/warn/Wnonnull-parm-2.C 
b/gcc/testsuite/g++.dg/warn/Wnonnull-parm-2.C
new file mode 100644
index 00000000000..c0ed60c5f86
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wnonnull-parm-2.C
@@ -0,0 +1,14 @@
+// Test diagnostics for invalid uses of "nonnull" on a function parameter.
+// { dg-do compile { target c++11 } }
+
+// nonnull on a parameter takes no arguments.
+void bad_args ([[gnu::nonnull(1)]] int *p);    // { dg-warning ".nonnull. 
attribute on a function parameter takes no arguments" }
+
+// nonnull on a non-pointer parameter is ignored.
+void bad_type ([[gnu::nonnull]] int x);                // { dg-warning 
".nonnull. attribute ignored" }
+
+// A class/struct passed by value is a non-pointer type too, and is ignored
+// the same way.
+struct S { int a; };
+void bad_class ([[gnu::nonnull]] S s);         // { dg-warning ".nonnull. 
attribute ignored" }
+void bad_class_gnu (S s __attribute__((nonnull)));     // { dg-warning 
".nonnull. attribute ignored" }
diff --git a/gcc/testsuite/gcc.dg/Wnonnull-parm-1.c 
b/gcc/testsuite/gcc.dg/Wnonnull-parm-1.c
new file mode 100644
index 00000000000..2e06d3071da
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wnonnull-parm-1.c
@@ -0,0 +1,29 @@
+/* Test the "nonnull" attribute applied directly to a pointer parameter.
+   Writing it on parameter N (with no arg-index) is equivalent to
+   nonnull (N) on the function, so a null argument is diagnosed under
+   -Wnonnull.  */
+/* { dg-do compile } */
+/* { dg-options "-Wnonnull" } */
+
+void f (int * __attribute__((nonnull)) p, int n);
+
+void g (int * __attribute__((nonnull)) p, int * __attribute__((nonnull)) q);
+
+/* A mix of a marked and an unmarked pointer parameter.  */
+void h (int *p, int * __attribute__((nonnull)) q);
+
+static int i;
+
+void
+test (void)
+{
+  f (0, 0);            /* { dg-warning "argument 1 null where non-null 
expected" } */
+  f (&i, 0);
+
+  g (0, &i);           /* { dg-warning "argument 1 null where non-null 
expected" } */
+  g (&i, 0);           /* { dg-warning "argument 2 null where non-null 
expected" } */
+  g (&i, &i);
+
+  h (0, &i);
+  h (&i, 0);           /* { dg-warning "argument 2 null where non-null 
expected" } */
+}
-- 
2.43.0

Reply via email to