diff mbox

C++ backports to 6

Message ID 20170314160822.GS3172@redhat.com
State New
Headers show

Commit Message

Marek Polacek March 14, 2017, 4:08 p.m. UTC
I've backported the attached patches gcc-6.

	Marek
Bootstrapped/regtested on x86_64-linux, ok for trunk?

2017-03-14  Marek Polacek  <polacek@redhat.com>

	PR c++/79962
	PR c++/79984
	* c-common.c (handle_nonnull_attribute): Save the result of default
	conversion to the attribute list.

	* c-c++-common/nonnull-3.c: New test.
	* g++.dg/warn/Wnonnull3.C: New test.
We crash on an assert in strip_typedefs, because we find ourselves in a
scenario where RESULT, the main variant of a struct, was modified in
finalize_record_size (its TYPE_ALIGN changed), but its variants (T in
strip_typedefs) weren't fixed-up yet; that will happen slightly later in
finalize_type_size.  So there's a discrepancy that confuses the code.

This patch implements what Jason suggested to me on IRC, i.e., skip the
attribute handling if RESULT is complete and T isn't.

Bootstrapped/regtested on x86_64-linux, ok for trunk?

2017-03-09  Marek Polacek  <polacek@redhat.com>

	PR c++/79900 - ICE in strip_typedefs
	* tree.c (strip_typedefs): Skip the attribute handling if T is
	a variant type which hasn't been updated yet.

	* g++.dg/warn/Wpadded-1.C: New test.

diff --git gcc/cp/tree.c gcc/cp/tree.c
index d3c63b8..b3a4a10 100644
--- gcc/cp/tree.c
+++ gcc/cp/tree.c
@@ -1548,29 +1548,40 @@ strip_typedefs (tree t, bool *remove_attributes)
 	result = TYPE_MAIN_VARIANT (t);
     }
   gcc_assert (!typedef_variant_p (result));
-  if (TYPE_USER_ALIGN (t) != TYPE_USER_ALIGN (result)
-      || TYPE_ALIGN (t) != TYPE_ALIGN (result))
+
+  if (COMPLETE_TYPE_P (result) && !COMPLETE_TYPE_P (t))
+  /* If RESULT is complete and T isn't, it's likely the case that T
+     is a variant of RESULT which hasn't been updated yet.  Skip the
+     attribute handling.  */;
+  else
     {
-      gcc_assert (TYPE_USER_ALIGN (t));
-      if (remove_attributes)
-	*remove_attributes = true;
-      else
+      if (TYPE_USER_ALIGN (t) != TYPE_USER_ALIGN (result)
+	  || TYPE_ALIGN (t) != TYPE_ALIGN (result))
 	{
-	  if (TYPE_ALIGN (t) == TYPE_ALIGN (result))
-	    result = build_variant_type_copy (result);
+	  gcc_assert (TYPE_USER_ALIGN (t));
+	  if (remove_attributes)
+	    *remove_attributes = true;
 	  else
-	    result = build_aligned_type (result, TYPE_ALIGN (t));
-	  TYPE_USER_ALIGN (result) = true;
+	    {
+	      if (TYPE_ALIGN (t) == TYPE_ALIGN (result))
+		result = build_variant_type_copy (result);
+	      else
+		result = build_aligned_type (result, TYPE_ALIGN (t));
+	      TYPE_USER_ALIGN (result) = true;
+	    }
+	}
+
+      if (TYPE_ATTRIBUTES (t))
+	{
+	  if (remove_attributes)
+	    result = apply_identity_attributes (result, TYPE_ATTRIBUTES (t),
+						remove_attributes);
+	  else
+	    result = cp_build_type_attribute_variant (result,
+						      TYPE_ATTRIBUTES (t));
 	}
     }
-  if (TYPE_ATTRIBUTES (t))
-    {
-      if (remove_attributes)
-	result = apply_identity_attributes (result, TYPE_ATTRIBUTES (t),
-					    remove_attributes);
-      else
-	result = cp_build_type_attribute_variant (result, TYPE_ATTRIBUTES (t));
-    }
+
   return cp_build_qualified_type (result, cp_type_quals (t));
 }
 
diff --git gcc/testsuite/g++.dg/warn/Wpadded-1.C gcc/testsuite/g++.dg/warn/Wpadded-1.C
index e69de29..b3f0581 100644
--- gcc/testsuite/g++.dg/warn/Wpadded-1.C
+++ gcc/testsuite/g++.dg/warn/Wpadded-1.C
@@ -0,0 +1,22 @@
+// PR c++/79900 - ICE in strip_typedefs
+// { dg-do compile }
+// { dg-options "-Wpadded" }
+
+template <class> struct A;
+template <typename> struct B { // { dg-warning "padding struct size to alignment boundary" }
+  long _M_off;
+  int _M_state;
+};
+template <> struct A<char> { typedef B<int> pos_type; };
+enum _Ios_Openmode {};
+struct C {
+  typedef _Ios_Openmode openmode;
+};
+template <typename, typename _Traits> struct D {
+  typedef typename _Traits::pos_type pos_type;
+  pos_type m_fn1(pos_type, C::openmode);
+};
+template class D<char, A<char> >;
+template <typename _CharT, typename _Traits>
+typename D<_CharT, _Traits>::pos_type D<_CharT, _Traits>::m_fn1(pos_type x,
+                                                                C::openmode) { return x; }
Bootstrapped/regtested on x86_64-linux, ok for trunk?

2017-03-09  Marek Polacek  <polacek@redhat.com>

	PR c++/79687
	* init.c (constant_value_1): Break if the variable has a dynamic
	initializer.

	* g++.dg/expr/ptrmem8.C: New test.
	* g++.dg/expr/ptrmem9.C: New test.

diff --git gcc/cp/init.c gcc/cp/init.c
index 7ded37e..12e6bf4 100644
--- gcc/cp/init.c
+++ gcc/cp/init.c
@@ -2193,6 +2193,13 @@ constant_value_1 (tree decl, bool strict_p, bool return_aggregate_cst_ok_p)
       if (TREE_CODE (init) == CONSTRUCTOR
 	  && !DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (decl))
 	break;
+      /* If the variable has a dynamic initializer, don't use its
+	 DECL_INITIAL which doesn't reflect the real value.  */
+      if (VAR_P (decl)
+	  && TREE_STATIC (decl)
+	  && !DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (decl)
+	  && DECL_NONTRIVIALLY_INITIALIZED_P (decl))
+	break;
       decl = unshare_expr (init);
     }
   return decl;
diff --git gcc/testsuite/g++.dg/expr/ptrmem8.C gcc/testsuite/g++.dg/expr/ptrmem8.C
index e69de29..c5a766a 100644
--- gcc/testsuite/g++.dg/expr/ptrmem8.C
+++ gcc/testsuite/g++.dg/expr/ptrmem8.C
@@ -0,0 +1,15 @@
+// PR c++/79687
+// { dg-do run }
+
+struct A
+{
+  char c;
+};
+
+int main()
+{
+  char A::* p = &A::c;
+  static char A::* const q = p;
+  A a;
+  return &(a.*q) - &a.c;
+}
diff --git gcc/testsuite/g++.dg/expr/ptrmem9.C gcc/testsuite/g++.dg/expr/ptrmem9.C
index e69de29..32ce777 100644
--- gcc/testsuite/g++.dg/expr/ptrmem9.C
+++ gcc/testsuite/g++.dg/expr/ptrmem9.C
@@ -0,0 +1,19 @@
+// PR c++/79687
+// { dg-do run }
+
+struct A
+{
+  char c;
+};
+
+int main()
+{
+  static char A::* p1 = &A::c;
+  char A::* const q1 = p1;
+
+  char A::* p2 = &A::c;
+  static char A::* const q2 = p2;
+
+  A a;
+  return (&(a.*q1) - &a.c) || (&(a.*q2) - &a.c);
+}
2017-01-17  Nathan Sidwell  <nathan@acm.org>

	PR c++/61636
	* cp-tree.h (maybe_generic_this_capture): Declare.
	* lambda.c (resolvable_dummy_lambda): New, broken out of ...
	(maybe_resolve_dummy): ... here.  Call it.
	(maybe_generic_this_capture): New.
	* parser.c (cp_parser_postfix_expression): Speculatively capture
	this in generic lambda in unresolved member function call.
	* pt.c (tsubst_copy_and_build): Force hard error from failed
	member function lookup in generic lambda.

	* g++.dg/cpp1y/pr61636-1.C: New.
	* g++.dg/cpp1y/pr61636-2.C: New.
	* g++.dg/cpp1y/pr61636-3.C: New.

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 24de346..0c8f147 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -6551,6 +6551,7 @@ extern bool is_capture_proxy			(tree);
 extern bool is_normal_capture_proxy             (tree);
 extern void register_capture_members		(tree);
 extern tree lambda_expr_this_capture            (tree, bool);
+extern void maybe_generic_this_capture		(tree, tree);
 extern tree maybe_resolve_dummy			(tree, bool);
 extern tree current_nonlambda_function		(void);
 extern tree nonlambda_method_basetype		(void);
diff --git a/gcc/cp/lambda.c b/gcc/cp/lambda.c
index 98fdb74..4d22c3d 100644
--- a/gcc/cp/lambda.c
+++ b/gcc/cp/lambda.c
@@ -793,16 +793,14 @@ lambda_expr_this_capture (tree lambda, bool add_capture_p)
   return result;
 }
 
-/* We don't want to capture 'this' until we know we need it, i.e. after
-   overload resolution has chosen a non-static member function.  At that
-   point we call this function to turn a dummy object into a use of the
-   'this' capture.  */
+/* Return the current LAMBDA_EXPR, if this is a resolvable dummy
+   object.  NULL otherwise..  */
 
-tree
-maybe_resolve_dummy (tree object, bool add_capture_p)
+static tree
+resolvable_dummy_lambda (tree object)
 {
   if (!is_dummy_object (object))
-    return object;
+    return NULL_TREE;
 
   tree type = TYPE_MAIN_VARIANT (TREE_TYPE (object));
   gcc_assert (!TYPE_PTR_P (type));
@@ -812,18 +810,55 @@ maybe_resolve_dummy (tree object, bool add_capture_p)
       && LAMBDA_TYPE_P (current_class_type)
       && lambda_function (current_class_type)
       && DERIVED_FROM_P (type, current_nonlambda_class_type ()))
-    {
-      /* In a lambda, need to go through 'this' capture.  */
-      tree lam = CLASSTYPE_LAMBDA_EXPR (current_class_type);
-      tree cap = lambda_expr_this_capture (lam, add_capture_p);
-      if (cap && cap != error_mark_node)
+    return CLASSTYPE_LAMBDA_EXPR (current_class_type);
+
+  return NULL_TREE;
+}
+
+/* We don't want to capture 'this' until we know we need it, i.e. after
+   overload resolution has chosen a non-static member function.  At that
+   point we call this function to turn a dummy object into a use of the
+   'this' capture.  */
+
+tree
+maybe_resolve_dummy (tree object, bool add_capture_p)
+{
+  if (tree lam = resolvable_dummy_lambda (object))
+    if (tree cap = lambda_expr_this_capture (lam, add_capture_p))
+      if (cap != error_mark_node)
 	object = build_x_indirect_ref (EXPR_LOCATION (object), cap,
 				       RO_NULL, tf_warning_or_error);
-    }
 
   return object;
 }
 
+/* When parsing a generic lambda containing an argument-dependent
+   member function call we defer overload resolution to instantiation
+   time.  But we have to know now whether to capture this or not.
+   Do that if FNS contains any non-static fns.
+   The std doesn't anticipate this case, but I expect this to be the
+   outcome of discussion.  */
+
+void
+maybe_generic_this_capture (tree object, tree fns)
+{
+  if (tree lam = resolvable_dummy_lambda (object))
+    if (!LAMBDA_EXPR_THIS_CAPTURE (lam))
+      {
+	/* We've not yet captured, so look at the function set of
+	   interest.  */
+	if (BASELINK_P (fns))
+	  fns = BASELINK_FUNCTIONS (fns);
+	for (; fns; fns = OVL_NEXT (fns))
+	  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (OVL_CURRENT (fns)))
+	    {
+	      /* Found a non-static member.  Capture this.  */
+	      lambda_expr_this_capture (lam, true);
+	      break;
+	    }
+      }
+}
+
 /* Returns the innermost non-lambda function.  */
 
 tree
diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
index 295c450..6d3b877 100644
--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -6971,6 +6971,7 @@ cp_parser_postfix_expression (cp_parser *parser, bool address_p, bool cast_p,
 			|| type_dependent_expression_p (fn)
 			|| any_type_dependent_arguments_p (args)))
 		  {
+		    maybe_generic_this_capture (instance, fn);
 		    postfix_expression
 		      = build_nt_call_vec (postfix_expression, args);
 		    release_tree_vector (args);
diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index dec7d39..022ffda 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -17142,19 +17142,34 @@ tsubst_copy_and_build (tree t,
 
 		if (unq != function)
 		  {
-		    tree fn = unq;
-		    if (INDIRECT_REF_P (fn))
-		      fn = TREE_OPERAND (fn, 0);
-		    if (TREE_CODE (fn) == COMPONENT_REF)
-		      fn = TREE_OPERAND (fn, 1);
-		    if (is_overloaded_fn (fn))
-		      fn = get_first_fn (fn);
-		    if (permerror (EXPR_LOC_OR_LOC (t, input_location),
-				   "%qD was not declared in this scope, "
-				   "and no declarations were found by "
-				   "argument-dependent lookup at the point "
-				   "of instantiation", function))
+		    /* In a lambda fn, we have to be careful to not
+		       introduce new this captures.  Legacy code can't
+		       be using lambdas anyway, so it's ok to be
+		       stricter.  */
+		    bool in_lambda = (current_class_type
+				      && LAMBDA_TYPE_P (current_class_type));
+		    char const *msg = "%qD was not declared in this scope, "
+		      "and no declarations were found by "
+		      "argument-dependent lookup at the point "
+		      "of instantiation";
+
+		    bool diag = true;
+		    if (in_lambda)
+		      error_at (EXPR_LOC_OR_LOC (t, input_location),
+				msg, function);
+		    else
+		      diag = permerror (EXPR_LOC_OR_LOC (t, input_location),
+					msg, function);
+		    if (diag)
 		      {
+			tree fn = unq;
+			if (INDIRECT_REF_P (fn))
+			  fn = TREE_OPERAND (fn, 0);
+			if (TREE_CODE (fn) == COMPONENT_REF)
+			  fn = TREE_OPERAND (fn, 1);
+			if (is_overloaded_fn (fn))
+			  fn = get_first_fn (fn);
+
 			if (!DECL_P (fn))
 			  /* Can't say anything more.  */;
 			else if (DECL_CLASS_SCOPE_P (fn))
@@ -17177,7 +17192,13 @@ tsubst_copy_and_build (tree t,
 			  inform (DECL_SOURCE_LOCATION (fn),
 				  "%qD declared here, later in the "
 				  "translation unit", fn);
+			if (in_lambda)
+			  {
+			    release_tree_vector (call_args);
+			    RETURN (error_mark_node);
+			  }
 		      }
+
 		    function = unq;
 		  }
 	      }
diff --git a/gcc/testsuite/g++.dg/cpp1y/pr61636-1.C b/gcc/testsuite/g++.dg/cpp1y/pr61636-1.C
new file mode 100644
index 0000000..9426d5f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/pr61636-1.C
@@ -0,0 +1,31 @@
+// PR c++/61636
+// { dg-do compile { target c++14 } }
+
+// ICE because we figure this capture too late.
+
+struct Base
+{
+  void Bar (int);
+};
+
+struct A : Base {
+  void b ();
+  void Foo (int);
+  using Base::Bar;
+  template <typename T> void Baz (T);
+};
+
+void A::b() {
+
+  auto lam = [&](auto asdf) { Foo (asdf); };
+
+  lam (0);
+
+  auto lam1 = [&](auto asdf) { Bar (asdf); };
+
+  lam1 (0);
+
+  auto lam2 = [&](auto asdf) { Baz (asdf); };
+
+  lam2 (0);
+}
diff --git a/gcc/testsuite/g++.dg/cpp1y/pr61636-2.C b/gcc/testsuite/g++.dg/cpp1y/pr61636-2.C
new file mode 100644
index 0000000..a1bd597
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/pr61636-2.C
@@ -0,0 +1,72 @@
+// PR c++/61636
+// { dg-do run { target c++14 } }
+
+// Check we don't capture this (too) unnecessarily
+
+struct A {
+  int b ();
+  void f (int) {}
+  static void f (double) {}
+
+  static void g (int) {}
+  static void g (double) {}
+};
+
+struct O {
+  void x (int) {}
+  static void x (double) {}
+};
+
+namespace N {
+  void y (double) {}
+}
+
+int Check (bool expect, unsigned size)
+{
+  return (expect ? sizeof (void *) : 1) != size;
+}
+
+int A::b() {
+  int r = 0;
+
+  // one of the functions is non-static
+  auto l0 = [&](auto z) { f (z); };
+  r += Check (true, sizeof l0);
+  l0(0.0); // doesn't need this capture for A::f(double), but too late
+  l0 (0); // Needs this capture for A::f(int)
+
+  // no fn is non-static.
+  auto l00 = [&](auto z) { g (z); };
+  r += Check (false, sizeof l00);
+  l00(0.0); 
+  l00 (0);
+
+  // sizeof isn't an evaluation context, so no this capture
+  auto l1 = [&](auto z) { sizeof (f (z), 1); };
+  r += Check (false, sizeof l1);
+  l1(0.0); l1 (0); 
+
+  auto l2 = [&](auto) { f (2.4); };
+  auto l3 = [&](auto) { f (0); };
+  l2(0); l3(0); l2(0.0); l3 (0.0);
+  r += Check (false, sizeof l2);
+  r += Check (true, sizeof l3);
+
+  auto l4 = [&](auto) { O::x (2.4); };
+  auto l5 = [&](auto) { N::y (2.4); };
+  auto l6 = [&](auto) { };
+  l4(0); l5(0); l6(0);
+  l4(0.0); l5(0.0); l6(0.0);
+  r += Check (false, sizeof l4);
+  r += Check (false, sizeof l5);
+  r += Check (false, sizeof l6);
+
+  return r;
+}
+
+int main ()
+{
+  A a;
+
+  return a.b () ? 1 : 0;
+}
diff --git a/gcc/testsuite/g++.dg/cpp1y/pr61636-3.C b/gcc/testsuite/g++.dg/cpp1y/pr61636-3.C
new file mode 100644
index 0000000..18f83fe
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/pr61636-3.C
@@ -0,0 +1,25 @@
+// PR c++/61636
+// { dg-do compile { target c++14 } }
+// permissiveness doesn't make this permitted
+// { dg-additional-options "-fpermissive" }
+
+// ICE because we attempt to use dependent Foo during error recovery
+// and die with an unexpected this capture need.
+
+template <typename T> struct Base
+{
+  void Foo (int);
+};
+
+template <typename T> struct A : Base<T> {
+  void b ();
+};
+
+template <typename T> void A<T>::b() {
+
+  auto lam = [&](auto asdf) { Foo (asdf); }; // { dg-error "not declared" }
+
+  lam (T(0));
+}
+
+template void A<int>::b ();
2017-01-31  Nathan Sidwell  <nathan@acm.org>

	PR c++/79264
	* lambda.c (maybe_generic_this_capture): Deal with template-id-exprs.
	* semantics.c (finish_member_declaration): Assert class is being
	defined.

	* g++.dg/cpp1y/pr61636-1.C: Augment.

diff --git a/gcc/cp/lambda.c b/gcc/cp/lambda.c
index 538c806..46ab30f 100644
--- a/gcc/cp/lambda.c
+++ b/gcc/cp/lambda.c
@@ -849,13 +849,21 @@ maybe_generic_this_capture (tree object, tree fns)
 	   interest.  */
 	if (BASELINK_P (fns))
 	  fns = BASELINK_FUNCTIONS (fns);
+	bool id_expr = TREE_CODE (fns) == TEMPLATE_ID_EXPR;
+	if (id_expr)
+	  fns = TREE_OPERAND (fns, 0);
 	for (; fns; fns = OVL_NEXT (fns))
-	  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (OVL_CURRENT (fns)))
-	    {
-	      /* Found a non-static member.  Capture this.  */
-	      lambda_expr_this_capture (lam, true);
-	      break;
-	    }
+	  {
+	    tree fn = OVL_CURRENT (fns);
+
+	    if ((!id_expr || TREE_CODE (fn) == TEMPLATE_DECL)
+		&& DECL_NONSTATIC_MEMBER_FUNCTION_P (fn))
+	      {
+		/* Found a non-static member.  Capture this.  */
+		lambda_expr_this_capture (lam, true);
+		break;
+	      }
+	  }
       }
 }
 
diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c
index bd91e18..e4f2a6a 100644
--- a/gcc/cp/semantics.c
+++ b/gcc/cp/semantics.c
@@ -2962,6 +2962,12 @@ finish_member_declaration (tree decl)
   /* We should see only one DECL at a time.  */
   gcc_assert (DECL_CHAIN (decl) == NULL_TREE);
 
+  /* Don't add decls after definition.  */
+  gcc_assert (TYPE_BEING_DEFINED (current_class_type)
+	      /* We can add lambda types when late parsing default
+		 arguments.  */
+	      || LAMBDA_TYPE_P (TREE_TYPE (decl)));
+
   /* Set up access control for DECL.  */
   TREE_PRIVATE (decl)
     = (current_access_specifier == access_private_node);
diff --git a/gcc/testsuite/g++.dg/cpp1y/pr61636-1.C b/gcc/testsuite/g++.dg/cpp1y/pr61636-1.C
index 9426d5f..5cc8ca1 100644
--- a/gcc/testsuite/g++.dg/cpp1y/pr61636-1.C
+++ b/gcc/testsuite/g++.dg/cpp1y/pr61636-1.C
@@ -1,4 +1,5 @@
 // PR c++/61636
+// PR c++/79264
 // { dg-do compile { target c++14 } }
 
 // ICE because we figure this capture too late.
@@ -28,4 +29,8 @@ void A::b() {
   auto lam2 = [&](auto asdf) { Baz (asdf); };
 
   lam2 (0);
+
+  auto lam3 = [&](auto asdf) { Baz<int> (asdf); };
+
+  lam3 (0);
 }

Comments

Markus Trippelsdorf March 18, 2017, 9:16 a.m. UTC | #1
On 2017.03.14 at 17:08 +0100, Marek Polacek wrote:
> I've backported the attached patches gcc-6.

The PR79264 backport caused:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80091
diff mbox

Patch

diff --git gcc/c-family/c-common.c gcc/c-family/c-common.c
index d2e3ad4..5f17984 100644
--- gcc/c-family/c-common.c
+++ gcc/c-family/c-common.c
@@ -9061,7 +9061,7 @@  handle_nonnull_attribute (tree *node, tree ARG_UNUSED (name),
       tree arg = TREE_VALUE (args);
       if (arg && TREE_CODE (arg) != IDENTIFIER_NODE
 	  && TREE_CODE (arg) != FUNCTION_DECL)
-	arg = default_conversion (arg);
+	TREE_VALUE (args) = arg = default_conversion (arg);
 
       if (!get_nonnull_operand (arg, &arg_num))
 	{
diff --git gcc/testsuite/c-c++-common/nonnull-3.c gcc/testsuite/c-c++-common/nonnull-3.c
index e69de29..d2ccb24 100644
--- gcc/testsuite/c-c++-common/nonnull-3.c
+++ gcc/testsuite/c-c++-common/nonnull-3.c
@@ -0,0 +1,11 @@ 
+/* PR c++/79984 */
+/* { dg-do compile } */
+/* { dg-options "-Wnonnull-compare" } */
+
+enum { r = 1 };
+
+__attribute__ ((nonnull (r))) int
+f (int *p)
+{
+  return p == 0; /* { dg-warning "nonnull argument 'p' compared to NULL" } */
+}
diff --git gcc/testsuite/g++.dg/warn/Wnonnull3.C gcc/testsuite/g++.dg/warn/Wnonnull3.C
index e69de29..d1918ef 100644
--- gcc/testsuite/g++.dg/warn/Wnonnull3.C
+++ gcc/testsuite/g++.dg/warn/Wnonnull3.C
@@ -0,0 +1,15 @@ 
+// PR c++/79962
+// { dg-options "-Wnonnull" }
+
+template <class T>
+__attribute__ ((__nonnull__ (T::i))) void f (typename T::U) { }
+
+struct S1 { enum { i = 1 }; typedef void* U; };
+struct S2 { static const int i = 1; typedef void* U; };
+
+void
+g ()
+{
+  f<S1>(0); // { dg-warning "null argument where non-null required" }
+  f<S2>(0); // { dg-warning "null argument where non-null required" }
+}