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