Building on parameter-level nonnull, allow the attribute to be written
directly on a pointer data member of a class or struct in C++:
struct X { [[gnu::nonnull]] int *p; };
When such an aggregate is initialized and the member would be
initialized to a null pointer, -Wnonnull now warns. Both an explicit
null initializer and the implicit null produced by a null default member
initializer are diagnosed:
X a{}; // member implicitly initialized to null
X b{nullptr}; // null pointer used to initialize member
int i;
X c{&i}; // valid
A user-written nonnull on a data member is rewritten into an internal
"*nonnull field" marker that stays on the FIELD_DECL, so it is not
rejected by the generic attribute handling before it can be recorded.
The marker is consulted during brace-enclosed aggregate initialization
in the C++ front end to diagnose null initializers. A member-level
nonnull takes no arguments and the member must have pointer type,
otherwise the attribute is diagnosed. Assigning null to the member
after initialization is not diagnosed, the check is limited to
initialization.
This form is currently supported in C++ only.
gcc/c-family/ChangeLog:
* c-common.h (c_mark_field_nonnull): Declare.
* c-attribs.cc (handle_nonnull_field_attribute): New function.
(mark_nonnull_attr): New function.
(c_mark_parm_nonnull): Use it.
(c_mark_field_nonnull): Use it.
(c_common_gnu_attributes): Add "*nonnull field".
gcc/cp/ChangeLog:
* decl.cc (grokdeclarator): Rewrite a bare nonnull on a data
member into the internal "*nonnull field" marker.
* decl2.cc (grokfield): Likewise.
* typeck2.cc (check_nonnull_field_init): New function.
(process_init_constructor_record): Diagnose a null initializer
for a nonnull data member under -Wnonnull.
gcc/ChangeLog:
* doc/extend.texi (Common Function Attributes): Document the
nonnull attribute on data members.
gcc/testsuite/ChangeLog:
* g++.dg/warn/Wnonnull-field-1.C: New test.
* g++.dg/warn/Wnonnull-field-2.C: New test.
* g++.dg/warn/Wnonnull-field-3.C: New test.
Signed-off-by: Andrei Rusanescu <[email protected]>
---
gcc/c-family/c-attribs.cc | 77 +++++++++++++++++---
gcc/c-family/c-common.h | 1 +
gcc/cp/decl.cc | 7 +-
gcc/cp/decl2.cc | 9 ++-
gcc/cp/typeck2.cc | 50 +++++++++++++
gcc/doc/extend.texi | 24 ++++++
gcc/testsuite/g++.dg/warn/Wnonnull-field-1.C | 63 ++++++++++++++++
gcc/testsuite/g++.dg/warn/Wnonnull-field-2.C | 12 +++
gcc/testsuite/g++.dg/warn/Wnonnull-field-3.C | 20 +++++
9 files changed, 250 insertions(+), 13 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/warn/Wnonnull-field-1.C
create mode 100644 gcc/testsuite/g++.dg/warn/Wnonnull-field-2.C
create mode 100644 gcc/testsuite/g++.dg/warn/Wnonnull-field-3.C
diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc
index 98d57267ddc..ee9d1987ab2 100644
--- a/gcc/c-family/c-attribs.cc
+++ b/gcc/c-family/c-attribs.cc
@@ -140,6 +140,7 @@ 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_field_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 *);
@@ -514,6 +515,8 @@ const struct attribute_spec c_common_gnu_attributes[] =
handle_nonnull_attribute, NULL },
{ "*nonnull parm", 0, -1, true, false, false, false,
handle_nonnull_parm_attribute, NULL },
+ { "*nonnull field", 0, -1, true, false, false, false,
+ handle_nonnull_field_attribute, NULL },
{ "nonnull_if_nonzero", 2, 3, false, true, true, false,
handle_nonnull_if_nonzero_attribute, NULL },
{ "nonstring", 0, 0, true, false, false, false,
@@ -5138,6 +5141,28 @@ handle_nonnull_parm_attribute (tree *node, tree
ARG_UNUSED (name),
return NULL_TREE;
}
+/* Shared helper for c_mark_parm_nonnull and c_mark_field_nonnull: walk
+ ATTRS and replace each "nonnull" entry with MARKER, leaving
+ pointer-to-function types untouched. */
+
+static tree
+mark_nonnull_attr (tree attrs, tree type, const char *marker)
+{
+ 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 (marker),
+ TREE_VALUE (a),
+ TREE_CHAIN (a));
+ }
+ return attrs;
+}
+
/* 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
@@ -5152,19 +5177,51 @@ handle_nonnull_parm_attribute (tree *node, tree
ARG_UNUSED (name),
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;
+ return mark_nonnull_attr (attrs, type, "*nonnull parm");
+}
- for (tree *p = &attrs; *p; p = &TREE_CHAIN (*p))
+/* Handle the "*nonnull field" attribute. This is the internal marker that
+ a user-written "nonnull" attribute on a struct/class data member is
+ rewritten into. It stays on the FIELD_DECL. */
+
+static tree
+handle_nonnull_field_attribute (tree *node, tree ARG_UNUSED (name),
+ tree args, int ARG_UNUSED (flags),
+ bool *no_add_attrs)
+{
+ tree decl = *node;
+
+ if (TREE_CODE (decl) != FIELD_DECL)
{
- 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));
+ warning (OPT_Wattributes, "%<nonnull%> attribute ignored");
+ *no_add_attrs = true;
}
- return attrs;
+ else if (args)
+ {
+ warning (OPT_Wattributes,
+ "%<nonnull%> attribute on a data member takes no arguments");
+ *no_add_attrs = true;
+ }
+ else if (!POINTER_TYPE_P (TREE_TYPE (decl)))
+ {
+ warning (OPT_Wattributes,
+ "%<nonnull%> attribute applied to member %qD of non-pointer "
+ "type", decl);
+ *no_add_attrs = true;
+ }
+
+ return NULL_TREE;
+}
+
+/* Rewrite any user-written "nonnull" attribute in ATTRS, destined for a
+ FIELD_DECL of type TYPE, into the internal "*nonnull field" marker. A
+ pointer to a function type is left alone, so "nonnull" there redirects to
+ the pointed-to function type as before. Handles GNU and C++11 encodings.
*/
+
+tree
+c_mark_field_nonnull (tree attrs, tree type)
+{
+ return mark_nonnull_attr (attrs, type, "*nonnull field");
}
/* Handle the "nonnull_if_nonzero" attribute. */
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 80d1ee8da7b..9ef51d3880b 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -1705,6 +1705,7 @@ 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 tree c_mark_field_nonnull (tree, tree);
extern void set_musttail_on_return (tree, location_t, bool);
/* In c-format.cc. */
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 9a9a159200f..e9c469a6995 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -15052,13 +15052,16 @@ grokdeclarator (const cp_declarator *declarator,
if (declarator->kind == cdk_id)
{
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
+ /* Rewrite a bare "nonnull" on a parameter or data member into
+ the internal 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);
+ else if (decl_context == FIELD
+ && !(type && TYPE_PTRMEMFUNC_P (type)))
+ attrs = c_mark_field_nonnull (attrs, type);
}
if (declarator->kind == cdk_function)
attr_flags |= (int) ATTR_FLAG_FUNCTION_NEXT;
diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
index 6e84b58e0b7..21be6a9a678 100644
--- a/gcc/cp/decl2.cc
+++ b/gcc/cp/decl2.cc
@@ -1413,7 +1413,14 @@ grokfield (const cp_declarator *declarator,
}
if (attrlist)
- cplus_decl_attributes (&value, attrlist, 0);
+ {
+ /* Rewrite a bare "nonnull" on a data member to the "*nonnull field"
+ marker. It stays on the FIELD_DECL and is consulted during aggregate
+ initialization to diagnose a null initializer under -Wnonnull. */
+ if (TREE_CODE (value) == FIELD_DECL)
+ attrlist = c_mark_field_nonnull (attrlist, TREE_TYPE (value));
+ cplus_decl_attributes (&value, attrlist, 0);
+ }
if (init && DIRECT_LIST_INIT_P (init))
flags = LOOKUP_NORMAL;
diff --git a/gcc/cp/typeck2.cc b/gcc/cp/typeck2.cc
index 12c34ba6b3a..5f57a4fcdb3 100644
--- a/gcc/cp/typeck2.cc
+++ b/gcc/cp/typeck2.cc
@@ -34,6 +34,8 @@ along with GCC; see the file COPYING3. If not see
#include "intl.h"
#include "gcc-rich-location.h"
#include "target.h"
+#include "stringpool.h"
+#include "attribs.h"
static tree
process_init_constructor (tree type, tree init, int nested, int flags,
@@ -1833,6 +1835,39 @@ process_init_constructor_array (tree type, tree init,
int nested, int flags,
return picflags;
}
+/* If FIELD is a pointer data member carrying the internal "*nonnull field"
+ marker (from a user-written "nonnull" attribute) and VALUE is a null
+ pointer constant, warn under -Wnonnull that a non-null member is being
+ initialized with null. */
+
+static void
+check_nonnull_field_init (tree field, tree value, tsubst_flags_t complain)
+{
+ if (!(complain & tf_warning)
+ || cp_unevaluated_operand
+ || processing_template_decl
+ || field == NULL_TREE
+ || TREE_CODE (field) != FIELD_DECL
+ || value == NULL_TREE
+ || value == error_mark_node)
+ return;
+
+ if (!lookup_attribute ("*nonnull field", DECL_ATTRIBUTES (field)))
+ return;
+
+ /* A value-dependent initializer can't be checked yet. */
+ if (instantiation_dependent_expression_p (value))
+ return;
+
+ /* Catch a source-level null constant (nullptr, literal 0) and a
+ pointer-typed zero after conversion to the member's type. */
+ if (null_ptr_cst_p (value)
+ || (POINTER_TYPE_P (TREE_TYPE (value)) && integer_zerop (value)))
+ warning_at (cp_expr_loc_or_input_loc (value), OPT_Wnonnull,
+ "null pointer used to initialize member %qD declared with "
+ "attribute %<nonnull%>", field);
+}
+
/* Subroutine of process_init_constructor, which will process an initializer
INIT for a class of type TYPE. Returns the flags (PICFLAG_*) which describe
the initializers. */
@@ -1983,6 +2018,17 @@ process_init_constructor_record (tree type, tree init,
int nested, int flags,
warning (OPT_Wmissing_field_initializers,
"missing initializer for member %qD", field);
+ /* A pointer member declared with attribute "nonnull" is being
+ implicitly zero-initialized to null (e.g. "X a{}"). */
+ if ((complain & tf_warning)
+ && !cp_unevaluated_operand
+ && !processing_template_decl
+ && POINTER_TYPE_P (fldtype)
+ && lookup_attribute ("*nonnull field", DECL_ATTRIBUTES (field)))
+ warning_at (cp_expr_loc_or_input_loc (init), OPT_Wnonnull,
+ "member %qD declared with attribute %<nonnull%> is "
+ "implicitly initialized to null", field);
+
if (!zero_init_p (fldtype) || skipped < 0)
{
if (TYPE_REF_P (fldtype))
@@ -2014,6 +2060,10 @@ process_init_constructor_record (tree type, tree init,
int nested, int flags,
expects. */
continue;
+ /* Diagnose an explicit null initializer (e.g. "X a{nullptr}") for a
+ pointer member declared with attribute "nonnull". */
+ check_nonnull_field_init (field, next, complain);
+
/* If this is a bitfield, now convert to the lowered type. */
if (fldtype != TREE_TYPE (field))
next = cp_convert_and_check (TREE_TYPE (field), next, complain);
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index ec8da0b981d..c5454161a56 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -3898,6 +3898,30 @@ 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++.
+In C++, the @code{nonnull} attribute may also be written directly on a
+pointer data member of a class or struct. When such an aggregate is
+initialized and the member would be initialized to a null pointer,
+@option{-Wnonnull} issues a warning. For example:
+
+@smallexample
+struct X @{ [[gnu::nonnull]] int *p; @};
+
+void f ()
+@{
+ X a@{@}; // warning: member initialized to null
+ X b@{nullptr@}; // warning: null pointer used to initialize member
+ int i;
+ X c@{&i@};
+@}
+@end smallexample
+
+Like the parameter form, a member-level @code{nonnull} takes no arguments
+and the member must have pointer type. The check is performed for
+brace-enclosed aggregate initialization, including the implicit null that
+results from a null default member initializer. Assigning null to the
+member after initialization is not diagnosed. This form is currently
+supported in C++ only.
+
@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-field-1.C
b/gcc/testsuite/g++.dg/warn/Wnonnull-field-1.C
new file mode 100644
index 00000000000..4d09738949a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wnonnull-field-1.C
@@ -0,0 +1,63 @@
+// Test the "nonnull" attribute applied directly to a pointer data member.
+// A null initializer for such a member is diagnosed under -Wnonnull.
+// { dg-do compile { target c++11 } }
+// { dg-options "-Wnonnull" }
+
+struct X { [[gnu::nonnull]] int *x; };
+
+// Alternate spellings all attach the attribute to the member.
+struct A { __attribute__((nonnull)) int *a; };
+struct B { int * __attribute__((nonnull)) b; };
+struct C { int *c [[gnu::nonnull]]; };
+
+int gi;
+
+void f ()
+{
+ X a{}; // { dg-warning "declared with attribute
.nonnull. is implicitly initialized to null" }
+ X b{nullptr}; // { dg-warning "null pointer used to initialize member .X::x.
declared with attribute .nonnull." }
+ X c{0}; // { dg-warning "null pointer used to
initialize member .X::x. declared with attribute .nonnull." }
+ X d{&gi};
+ (void) a; (void) b; (void) c; (void) d;
+}
+
+// Mixed members: only the nonnull one is diagnosed.
+struct Y { int *p; [[gnu::nonnull]] int *q; int n; };
+
+void g ()
+{
+ Y y1{&gi, &gi, 5};
+ Y y2{&gi, nullptr, 5}; // { dg-warning "null pointer used to
initialize member .Y::q." }
+ Y y3{}; // { dg-warning "member .Y::q. declared with attribute
.nonnull. is implicitly initialized to null" }
+ Y y4{nullptr, &gi, 5};
+ (void) y1; (void) y2; (void) y3; (void) y4;
+}
+
+// A struct without the attribute is never diagnosed.
+struct Z { int *z; };
+
+void i ()
+{
+ Z z1{};
+ Z z2{nullptr};
+ (void) z1; (void) z2;
+}
+
+// Unevaluated operands must not warn.
+struct U { [[gnu::nonnull]] int *p; };
+
+void j ()
+{
+ (void) sizeof (U{});
+}
+
+// Templates: one diagnostic per instantiation, none at definition.
+template <class T> struct TX { [[gnu::nonnull]] T *p; };
+
+template <class T> void tf ()
+{
+ TX<T> a{}; // { dg-warning "declared with attribute .nonnull. is implicitly
initialized to null" }
+ (void) a;
+}
+
+template void tf<int> ();
diff --git a/gcc/testsuite/g++.dg/warn/Wnonnull-field-2.C
b/gcc/testsuite/g++.dg/warn/Wnonnull-field-2.C
new file mode 100644
index 00000000000..dccab151fcb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wnonnull-field-2.C
@@ -0,0 +1,12 @@
+// Test diagnostics for invalid uses of "nonnull" on a data member.
+// { dg-do compile { target c++11 } }
+
+// nonnull on a non-pointer member is rejected.
+struct E { [[gnu::nonnull]] int e; }; // { dg-warning ".nonnull. attribute
applied to member .e. of non-pointer type" }
+
+// nonnull on a member takes no arguments.
+struct F { [[gnu::nonnull(1)]] int *f; }; // { dg-warning ".nonnull. attribute
on a data member takes no arguments" }
+
+// A function-pointer member keeps nonnull's historical meaning: it redirects
+// to the pointed-to function type, so it is accepted (as in GCC's own
headers).
+struct G { void (*cb) (int *) __attribute__((nonnull (1))); };
diff --git a/gcc/testsuite/g++.dg/warn/Wnonnull-field-3.C
b/gcc/testsuite/g++.dg/warn/Wnonnull-field-3.C
new file mode 100644
index 00000000000..2ee9e282b23
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wnonnull-field-3.C
@@ -0,0 +1,20 @@
+// A nonnull member with a null default member initializer is diagnosed when
+// the enclosing class is an aggregate (C++14 and later, where a class with an
+// NSDMI is still an aggregate).
+// { dg-do compile { target c++14 } }
+// { dg-options "-Wnonnull" }
+
+int gi;
+
+// Non-null NSDMI: fine.
+struct M { [[gnu::nonnull]] int *p = &gi; };
+
+// Null NSDMI: diagnosed on default construction.
+struct N { [[gnu::nonnull]] int *p = nullptr; };
+
+void h ()
+{
+ M m{}; // NSDMI is &gi, no warning
+ N n{}; // { dg-warning "null pointer used to initialize member .N::p." }
+ (void) m; (void) n;
+}
--
2.43.0