On 4/17/20 5:18 PM, Martin Sebor wrote:
On 4/17/20 12:19 AM, Jason Merrill wrote:
On 4/15/20 1:30 PM, Martin Sebor wrote:
On 4/13/20 8:43 PM, Jason Merrill wrote:
On 4/12/20 5:49 PM, Martin Sebor wrote:
On 4/10/20 8:52 AM, Jason Merrill wrote:
On 4/9/20 4:23 PM, Martin Sebor wrote:
On 4/9/20 1:32 PM, Jason Merrill wrote:
On 4/9/20 3:24 PM, Martin Sebor wrote:
On 4/9/20 1:03 PM, Jason Merrill wrote:
On 4/8/20 1:23 PM, Martin Sebor wrote:
On 4/7/20 3:36 PM, Marek Polacek wrote:
On Tue, Apr 07, 2020 at 02:46:52PM -0600, Martin Sebor wrote:
On 4/7/20 1:50 PM, Marek Polacek wrote:
On Tue, Apr 07, 2020 at 12:50:48PM -0600, Martin Sebor via Gcc-patches wrote:
Among the numerous regressions introduced by the change committed to GCC 9 to allow string literals as template arguments is a failure to recognize the C++ nullptr and GCC's __null constants as pointers. For one, I didn't realize that nullptr, being a null pointer constant, doesn't have a pointer type, and two, I didn't think of __null (which is a special integer constant that NULL sometimes expands to).

The attached patch adjusts the special handling of trailing zero initializers in reshape_init_array_1 to recognize both kinds of constants and avoid treating them as zeros of the array integer element type.  This restores the expected diagnostics when either
constant is used in the initializer list.

Martin

PR c++/94510 - nullptr_t implicitly cast to zero twice in std::array

gcc/cp/ChangeLog:

    PR c++/94510
    * decl.c (reshape_init_array_1): Exclude mismatches with all kinds
    of pointers.

gcc/testsuite/ChangeLog:

    PR c++/94510
    * g++.dg/init/array57.C: New test.
    * g++.dg/init/array58.C: New test.

diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
index a127734af69..692c8ed73f4 100644
--- a/gcc/cp/decl.c
+++ b/gcc/cp/decl.c
@@ -6041,9 +6041,14 @@ reshape_init_array_1 (tree elt_type, tree max_index, reshape_iter *d,
       TREE_CONSTANT (new_init) = false;
         /* Pointers initialized to strings must be treated as non-zero
-     even if the string is empty.  */
+     even if the string is empty.  Handle all kinds of pointers, +     including std::nullptr and GCC's __nullptr, neither of which
+     has a pointer type.  */
         tree init_type = TREE_TYPE (elt_init);
-      if (POINTER_TYPE_P (elt_type) != POINTER_TYPE_P (init_type)
+      bool init_is_ptr = (POINTER_TYPE_P (init_type)
+              || NULLPTR_TYPE_P (init_type)
+              || null_node_p (elt_init));
+      if (POINTER_TYPE_P (elt_type) != init_is_ptr
         || !type_initializer_zero_p (elt_type, elt_init))
       last_nonzero = index;

It looks like this still won't handle e.g. pointers to member functions,
e.g.

struct S { };
int arr[3] = { (void (S::*) ()) 0, 0, 0 };

would still be accepted.  You could use TYPE_PTR_OR_PTRMEM_P instead of
POINTER_TYPE_P to catch this case.

Good catch!  That doesn't fail because unlike null data member pointers which are represented as -1, member function pointers are represented
as a zero.

I had looked for an API that would answer the question: "is this expression a pointer?" without having to think of all the different kinds of them but all I could find was null_node_p().  Is this a rare, isolated case that having an API like that wouldn't be worth having
or should I add one like in the attached update?

Martin

PR c++/94510 - nullptr_t implicitly cast to zero twice in std::array

gcc/cp/ChangeLog:

    PR c++/94510
    * decl.c (reshape_init_array_1): Exclude mismatches with all kinds
    of pointers.
    * gcc/cp/cp-tree.h (null_pointer_constant_p): New function.

(Drop the gcc/cp/.)

+/* Returns true if EXPR is a null pointer constant of any type.  */
+
+inline bool
+null_pointer_constant_p (tree expr)
+{
+  STRIP_ANY_LOCATION_WRAPPER (expr);
+  if (expr == null_node)
+    return true;
+  tree type = TREE_TYPE (expr);
+  if (NULLPTR_TYPE_P (type))
+    return true;
+  if (POINTER_TYPE_P (type))
+    return integer_zerop (expr);
+  return null_member_pointer_value_p (expr);
+}
+

We already have a null_ptr_cst_p so it would be sort of confusing to have this as well.  But are you really interested in whether it's a null pointer,
not just a pointer?

The goal of the code is to detect a mismatch in "pointerness" between an initializer expression and the type of the initialized element, so it needs to know if the expression is a pointer (non-nulls pointers are detected in type_initializer_zero_p).  That means testing a number
of IMO unintuitive conditions:

   TYPE_PTR_OR_PTRMEM_P (TREE_TYPE (expr))
   || NULLPTR_TYPE_P (TREE_TYPE (expr))
   || null_node_p (expr)

I don't know if this type of a query is common in the C++ FE but unless this is an isolated use case then besides fixing the bug I thought it would be nice to make it easier to get the test above right, or at least
come close to it.

Since null_pointer_constant_p already exists (but isn't suitable here
because it returns true for plain literal zeros)

Why is that unsuitable?  A literal zero is a perfectly good zero-initializer for a pointer.

Right, that's why it's not suitable here.  Because a literal zero
is also not a pointer.

The question the code asks is: "is the initializer expression
a pointer (of any kind)?"

Why is that a question we want to ask?  What we need here is to know whether the initializer expression is equivalent to implicit zero-initialization.  For initializing a pointer, a literal 0 is equivalent, so we don't want to update last_nonzero.

Yes, but that's not the bug we're fixing.  The problem occurs with
an integer array and a pointer initializer:

   int a[2] = { nullptr, 0 };

Aha, you're fixing a different bug than the one I was seeing.

What is that one?  (I'm not aware of any others in this area.)


and with elt_type = TREE_TYPE (a) and init_type TREE_TYPE (nullptr)
the test

   POINTER_TYPE_P (elt_type) != POINTER_TYPE_P (init_type)

evaluates to false because neither type is a pointer type and

   type_initializer_zero_p (elt_type, elt_init)

returns true because nullptr is zero, and so last_nonzero doesn't
get set, the element gets trimmed, and the invalid initialization
of int with nullptr isn't diagnosed.

But I'm not sure if you're questioning the current code, the simple
fix quoted above, or my assertion that null_pointer_constant_p would
not be a suitable function to call to tell if an initializer is
nullptr vs plain zero.

Also, why is the pointer check here rather than part of the POINTER_TYPE_P handling in type_initializer_zero_p?

type_initializer_zero_p is implemented in terms of initializer_zerop
with the only difference that empty strings are considered to be zero
only for char arrays and not char pointers.

Yeah, but that's the fundamental problem: We're assuming that any zero is suitable for initializing any type except for a few exceptions, and adding more exceptions when we find a new testcase that breaks.

Handling this in process_init_constructor_array avoids all these problems by looking at the initializers after they've been converted to the desired type, at which point it's much clearer whether they are zero or not; then we don't need type_initializer_zero_p because the initializer already has the proper type and for zero_init_p types we can just use initializer_zero_p.

I've already expressed my concerns with that change but if you are
comfortable with it I won't insist on waiting until GCC 11.  Your last
request for that patch was to rework the second loop to avoid changing
the counter of the previous loop.  The attached update does that.

I also added another C++ 2a test to exercise a few more cases with
pointers to members.  With it I ran into what looks like an unrelated
bug in this area.  I opened PR 94568 for it, CC'd you, and xfailed
the problem case in the new test.


We do probably want some function that tests whether a particular initializer is equivalent to zero-initialization, which is either initializer_zero_p for zero_init_p types, !expr for pointers to members, and recursing for aggregates.  Maybe cp_initializer_zero_p or zero_init_expr_p?

It could be changed to return false for incompatible initializers
like pointers (or even __null) for non-pointer types, even if they
are zero, but that's not what it's designed to do.

But that's exactly what we did for 90938.  Now you're proposing another small exception, only putting it in the caller instead.  I think we'll keep running into these problems until we fix the design issue.

Somehow that felt different.  But I don't have a problem with moving
the pointer check there as well.  It shouldn't be too much more
intrusive than the original patch for this bug if you decide to
go with it for now.


It would also be possible to improve things by doing the conversion in type_initializer_zero_p before considering its zeroness, but that would again be duplicating work that we're already doing elsewhere.

I agree that it's not worth the trouble given the long-term fix is
in process_init_constructor_array.

Attached is the updated patch with the process_init_constructor_array
changes, retested on x86_64-linux.

+      if (!trunc_zero || !type_initializer_zero_p (eltype, ce->value))
+    last_nonzero = i;

I think we can remove type_initializer_zero_p as well, and use initializer_zerop here.

+      if (last_nonzero < i - 1)
+       {
+         vec_safe_truncate (v, last_nonzero + 1);

This looks like you will never truncate to length 0, which seems like a problem with last_nonzero being both unsigned and an index; perhaps it should be something like num_to_keep?

This whole block appears to serve no real purpose.  It trims trailing
zeros only from arithmetic types, but the trimming only matters for
pointers to members and that's done later.  I've removed it.

Why doesn't it matter for arithmetic types?  Because mangling omits trailing initializer_zerop elements, so the mangling is the same either way, and comparing class-type template arguments is based on mangling?

In that case, why don't we do the same for pointers to members?

Your little patch seems to work for the problems we're fixing (and
even fixes a subset of pr94568 that I mentioned), which is great.
But it fails two tests:

!  FAIL: g++.dg/abi/mangle71.C (3: +3)
!  FAIL: g++.dg/abi/mangle72.C (10: +10)

I don't think the mangling of these things is specified yet so maybe
that's okay (I mostly guessed when I wrote those tests, and could
have very well guessed wrong).  Here's one difference in mangle71.C:

   void f0__ (X<B{{ 0 }}>) { }

mangles as

   _Z4f0__1XIXtl1BtlA3_1AtlS1_Lc0EEtlS1_Lc1EEEEEE

by trunk but as

   _Z4f0__1XIXtl1BtlA3_1AtlS1_EtlS1_Lc1EEEEEE

with your patch.  I'd say the former is better but I'm not sure.

I agree.

The changes in mangle72.C, for example for

   void g__ (Y<B{{ }}>) { }
   void g0_ (Y<B{{ 0 }}>) { }
   void g00 (Y<B{{ 0, 0 }}>) { }

from

   _Z3g__1YIXtl1BtlA2_M1AA2_iLS3_0EEEEE
   _Z3g0_1YIXtl1BtlA2_M1AA2_iLS3_0EEEEE
   _Z3g001YIXtl1BtlA2_M1AA2_iLS3_0EEEEE

to

   _Z3g__1YIXtl1BEEE
   _Z3g0_1YIXtl1BEEE
   _Z3g001YIXtl1BEEE

look like improvements, and the one for

   void g0x (Y<B{{ 0, &A::a }}>) { }

from

   _Z3g0x1YIXtl1BtlA2_M1AA2_iLS3_0EEEEE

to

   _Z3g0x1YIXtl1BtlA2_M1AA2_iLS3_0ELS3_0EEEEE

looks like it might actually fix a bug (does it?).  There are
a few others for member pointers that are similar to the above.

Yes.

If these mangling changes look okay to you I'm much more comfortable
with a simple, targeted fix like this than with messing around with
all of array initialization.

Agreed.

Assuming the fix is correct, what bothers me is that it implies
that all these problems have been caused by forgetting to consider
pointers to members in the fix for pr89833 in r270155, and that all
this time since then I've been barking up the wrong tree by patching
up the wrong functions.  I.e., that none of the stripping of
the trailing initializers outside of mangle.c is or ever was
necessary.  Why did neither of us realize this a year ago?

Yeah, that is frustrating.

I fixed the mangle71.C issue by not doing any pruning for a non-trivial type. I also fixed another bug whereby the first function with a particular pointer to member in its signature (i.e. g0x) got mangled properly, but the next did not. And realized that our mangling of class non-type template args still needs a lot of work, but that will wait.

Any comments before I check this in?
commit 5ae045750f08bc582cd794ef75090c350a18a1b1
Author: Martin Sebor <mse...@gmail.com>
Date:   Tue Apr 21 11:02:06 2020 -0400

    c++: reject scalar array initialization with nullptr [PR94510]
    
    The change committed to GCC 9 to allow string literals as template arguments
    caused the compiler to prune away, and thus miss diagnosing, conversion from
    nullptr to int in an array initializer.  After looking at various approaches
    to improving the pruning, we realized that the only place the pruning is
    necessary is in the mangler.
    
    gcc/cp/ChangeLog
    2020-04-21  Martin Sebor  <mse...@redhat.com>
                Jason Merrill  <ja...@redhat.com>
    
            PR c++/94510
            * decl.c (reshape_init_array_1): Avoid stripping redundant trailing
            zero initializers...
            * mangle.c (write_expression): ...and handle them here even for
            pointers to members by calling zero_init_expr_p.
            * cp-tree.h (zero_init_expr_p): Declare.
            * tree.c (zero_init_expr_p): Define.
            (type_initializer_zero_p): Remove.
            * pt.c (tparm_obj_values): New hash_map.
            (get_template_parm_object): Store to it.
            (tparm_object_argument): New.
    
    gcc/testsuite/ChangeLog
    2020-04-21  Martin Sebor  <mse...@redhat.com>
    
            PR c++/94510
            * g++.dg/init/array58.C: New test.
            * g++.dg/init/array59.C: New test.
            * g++.dg/cpp2a/nontype-class34.C: New test.
            * g++.dg/cpp2a/nontype-class35.C: New test.

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 0b62a775c1b..924c0b9c790 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7001,6 +7001,7 @@ enum { nt_opaque = false, nt_transparent = true };
 extern tree alias_template_specialization_p     (const_tree, bool);
 extern tree dependent_alias_template_spec_p     (const_tree, bool);
 extern bool template_parm_object_p		(const_tree);
+extern tree tparm_object_argument		(tree);
 extern bool explicit_class_specialization_p     (tree);
 extern bool push_tinst_level                    (tree);
 extern bool push_tinst_level_loc                (tree, location_t);
@@ -7375,6 +7376,7 @@ extern bool type_has_nontrivial_copy_init	(const_tree);
 extern void maybe_warn_parm_abi			(tree, location_t);
 extern bool class_tmpl_impl_spec_p		(const_tree);
 extern int zero_init_p				(const_tree);
+extern bool zero_init_expr_p			(tree);
 extern bool check_abi_tag_redeclaration		(const_tree, const_tree,
 						 const_tree);
 extern bool check_abi_tag_args			(tree, tree);
@@ -7492,11 +7494,6 @@ extern tree cxx_copy_lang_qualifiers		(const_tree, const_tree);
 
 extern void cxx_print_statistics		(void);
 extern bool maybe_warn_zero_as_null_pointer_constant (tree, location_t);
-/* Analogous to initializer_zerop but also examines the type for
-   which the initializer is being used.  Unlike initializer_zerop,
-   considers empty strings to be zero initializers for arrays and
-   non-zero for pointers.  */
-extern bool type_initializer_zero_p		(tree, tree);
 
 /* in ptree.c */
 extern void cxx_print_xnode			(FILE *, tree, int);
diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
index 1447b89e692..c8c2f080763 100644
--- a/gcc/cp/decl.c
+++ b/gcc/cp/decl.c
@@ -6038,9 +6038,6 @@ reshape_init_array_1 (tree elt_type, tree max_index, reshape_iter *d,
 	max_index_cst = tree_to_uhwi (fold_convert (size_type_node, max_index));
     }
 
-  /* Set to the index of the last element with a non-zero initializer.
-     Zero initializers for elements past this one can be dropped.  */
-  unsigned HOST_WIDE_INT last_nonzero = -1;
   /* Loop until there are no more initializers.  */
   for (index = 0;
        d->cur != d->end && (!sized_array_p || index <= max_index_cst);
@@ -6067,50 +6064,11 @@ reshape_init_array_1 (tree elt_type, tree max_index, reshape_iter *d,
       if (!TREE_CONSTANT (elt_init))
 	TREE_CONSTANT (new_init) = false;
 
-      /* Pointers initialized to strings must be treated as non-zero
-	 even if the string is empty.  */
-      tree init_type = TREE_TYPE (elt_init);
-      if (POINTER_TYPE_P (elt_type) != POINTER_TYPE_P (init_type)
-	  || !type_initializer_zero_p (elt_type, elt_init))
-	last_nonzero = index;
-
       /* This can happen with an invalid initializer (c++/54501).  */
       if (d->cur == old_cur && !sized_array_p)
 	break;
     }
 
-  if (sized_array_p && trivial_type_p (elt_type))
-    {
-      /* Strip trailing zero-initializers from an array of a trivial
-	 type of known size.  They are redundant and get in the way
-	 of telling them apart from those with implicit zero value.  */
-      unsigned HOST_WIDE_INT nelts = CONSTRUCTOR_NELTS (new_init);
-      if (last_nonzero > nelts)
-	nelts = 0;
-      else if (last_nonzero < nelts - 1)
-	nelts = last_nonzero + 1;
-
-      /* Sharing a stripped constructor can get in the way of
-	 overload resolution.  E.g., initializing a class from
-	 {{0}} might be invalid while initializing the same class
-	 from {{}} might be valid.  */
-      if (reuse && nelts < CONSTRUCTOR_NELTS (new_init))
-	{
-	  vec<constructor_elt, va_gc> *v;
-	  vec_alloc (v, nelts);
-	  for (unsigned int i = 0; i < nelts; i++)
-	    {
-	      constructor_elt elt = *CONSTRUCTOR_ELT (new_init, i);
-	      if (TREE_CODE (elt.value) == CONSTRUCTOR)
-		elt.value = unshare_constructor (elt.value);
-	      v->quick_push (elt);
-	    }
-	  new_init = build_constructor (TREE_TYPE (new_init), v);
-	}
-      else
-	vec_safe_truncate (CONSTRUCTOR_ELTS (new_init), nelts);
-    }
-
   return new_init;
 }
 
diff --git a/gcc/cp/mangle.c b/gcc/cp/mangle.c
index 9e39cfd8dba..090fb529a98 100644
--- a/gcc/cp/mangle.c
+++ b/gcc/cp/mangle.c
@@ -3176,7 +3176,8 @@ write_expression (tree expr)
 	  write_type (etype);
 	}
 
-      if (!initializer_zerop (expr) || !trivial_type_p (etype))
+      bool nontriv = !trivial_type_p (etype);
+      if (nontriv || !zero_init_expr_p (expr))
 	{
 	  /* Convert braced initializer lists to STRING_CSTs so that
 	     A<"Foo"> mangles the same as A<{'F', 'o', 'o', 0}> while
@@ -3187,19 +3188,22 @@ write_expression (tree expr)
 	  if (TREE_CODE (expr) == CONSTRUCTOR)
 	    {
 	      vec<constructor_elt, va_gc> *elts = CONSTRUCTOR_ELTS (expr);
-	      unsigned last_nonzero = -1, i;
+	      unsigned last_nonzero = UINT_MAX, i;
 	      tree val;
 
-	      FOR_EACH_CONSTRUCTOR_VALUE (elts, i, val)
-		if (!initializer_zerop (val))
-		  last_nonzero = i;
+	      if (!nontriv)
+		FOR_EACH_CONSTRUCTOR_VALUE (elts, i, val)
+		  if (!zero_init_expr_p (val))
+		    last_nonzero = i;
 
-	      FOR_EACH_CONSTRUCTOR_VALUE (elts, i, val)
-		{
-		  if (i > last_nonzero)
-		    break;
-		  write_expression (val);
-		}
+	      if (nontriv || last_nonzero != UINT_MAX)
+		FOR_EACH_CONSTRUCTOR_VALUE (elts, i, val)
+		  {
+		    if (i > last_nonzero)
+		      break;
+		    /* FIXME handle RANGE_EXPR */
+		    write_expression (val);
+		  }
 	    }
 	  else
 	    {
@@ -3525,7 +3529,7 @@ write_template_arg (tree node)
 
   if (template_parm_object_p (node))
     /* We want to mangle the argument, not the var we stored it in.  */
-    node = DECL_INITIAL (node);
+    node = tparm_object_argument (node);
 
   /* Strip a conversion added by convert_nontype_argument.  */
   if (TREE_CODE (node) == IMPLICIT_CONV_EXPR)
diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index 6f74c278c23..5012847b78c 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -7006,6 +7006,11 @@ invalid_tparm_referent_p (tree type, tree expr, tsubst_flags_t complain)
 
 }
 
+/* The template arguments corresponding to template parameter objects of types
+   that contain pointers to members.  */
+
+static GTY(()) hash_map<tree, tree> *tparm_obj_values;
+
 /* Return a VAR_DECL for the C++20 template parameter object corresponding to
    template argument EXPR.  */
 
@@ -7039,10 +7044,32 @@ get_template_parm_object (tree expr, tsubst_flags_t complain)
   SET_DECL_ASSEMBLER_NAME (decl, name);
   DECL_CONTEXT (decl) = global_namespace;
   comdat_linkage (decl);
+
+  if (!zero_init_p (type))
+    {
+      /* If EXPR contains any PTRMEM_CST, they will get clobbered by
+	 lower_var_init before we're done mangling.  So store the original
+	 value elsewhere.  */
+      tree copy = unshare_constructor (expr);
+      hash_map_safe_put<hm_ggc> (tparm_obj_values, decl, copy);
+    }
+
   pushdecl_top_level_and_finish (decl, expr);
+
   return decl;
 }
 
+/* Return the actual template argument corresponding to template parameter
+   object VAR.  */
+
+tree
+tparm_object_argument (tree var)
+{
+  if (zero_init_p (TREE_TYPE (var)))
+    return DECL_INITIAL (var);
+  return *(tparm_obj_values->get (var));
+}
+
 /* Attempt to convert the non-type template parameter EXPR to the
    indicated TYPE.  If the conversion is successful, return the
    converted value.  If the conversion is unsuccessful, return
diff --git a/gcc/cp/tree.c b/gcc/cp/tree.c
index 092a2fab356..090c565c093 100644
--- a/gcc/cp/tree.c
+++ b/gcc/cp/tree.c
@@ -4478,6 +4478,33 @@ zero_init_p (const_tree t)
   return 1;
 }
 
+/* Returns true if the expression or initializer T is the result of
+   zero-initialization for its type, taking pointers to members
+   into consideration.  */
+
+bool
+zero_init_expr_p (tree t)
+{
+  tree type = TREE_TYPE (t);
+  if (!type || dependent_type_p (type))
+    return false;
+  if (zero_init_p (type))
+    return initializer_zerop (t);
+  if (TYPE_PTRMEM_P (type))
+    return null_member_pointer_value_p (t);
+  if (TREE_CODE (t) == CONSTRUCTOR
+      && CP_AGGREGATE_TYPE_P (type))
+    {
+      tree elt_init;
+      unsigned HOST_WIDE_INT i;
+      FOR_EACH_CONSTRUCTOR_VALUE (CONSTRUCTOR_ELTS (t), i, elt_init)
+	if (!zero_init_expr_p (elt_init))
+	  return false;
+      return true;
+    }
+  return false;
+}
+
 /* True IFF T is a C++20 structural type (P1907R1) that can be used as a
    non-type template parameter.  If EXPLAIN, explain why not.  */
 
@@ -5746,76 +5773,6 @@ maybe_warn_zero_as_null_pointer_constant (tree expr, location_t loc)
   return false;
 }
 
-/* Given an initializer INIT for a TYPE, return true if INIT is zero
-   so that it can be replaced by value initialization.  This function
-   distinguishes betwen empty strings as initializers for arrays and
-   for pointers (which make it return false).  */
-
-bool
-type_initializer_zero_p (tree type, tree init)
-{
-  if (type == error_mark_node || init == error_mark_node)
-    return false;
-
-  STRIP_NOPS (init);
-
-  if (POINTER_TYPE_P (type))
-    return TREE_CODE (init) != STRING_CST && initializer_zerop (init);
-
-  if (TREE_CODE (init) != CONSTRUCTOR)
-    {
-      /* A class can only be initialized by a non-class type if it has
-	 a ctor that converts from that type.  Such classes are excluded
-	 since their semantics are unknown.  */
-      if (RECORD_OR_UNION_TYPE_P (type)
-	  && !RECORD_OR_UNION_TYPE_P (TREE_TYPE (init)))
-	return false;
-      return initializer_zerop (init);
-    }
-
-  if (TREE_CODE (type) == ARRAY_TYPE)
-    {
-      tree elt_type = TREE_TYPE (type);
-      elt_type = TYPE_MAIN_VARIANT (elt_type);
-      if (elt_type == char_type_node)
-	return initializer_zerop (init);
-
-      tree elt_init;
-      unsigned HOST_WIDE_INT i;
-      FOR_EACH_CONSTRUCTOR_VALUE (CONSTRUCTOR_ELTS (init), i, elt_init)
-	if (!type_initializer_zero_p (elt_type, elt_init))
-	  return false;
-      return true;
-    }
-
-  if (TREE_CODE (type) != RECORD_TYPE)
-    return initializer_zerop (init);
-
-  if (TYPE_NON_AGGREGATE_CLASS (type))
-    return false;
-
-  tree fld = TYPE_FIELDS (type);
-
-  tree fld_init;
-  unsigned HOST_WIDE_INT i;
-  FOR_EACH_CONSTRUCTOR_VALUE (CONSTRUCTOR_ELTS (init), i, fld_init)
-    {
-      fld = next_initializable_field (fld);
-      if (!fld)
-	return true;
-
-      tree fldtype = TREE_TYPE (fld);
-      if (!type_initializer_zero_p (fldtype, fld_init))
-	return false;
-
-      fld = DECL_CHAIN (fld);
-      if (!fld)
-	break;
-    }
-
-  return true;
-}
-
 #if defined ENABLE_TREE_CHECKING && (GCC_VERSION >= 2007)
 /* Complain that some language-specific thing hanging off a tree
    node has been accessed improperly.  */
diff --git a/gcc/testsuite/g++.dg/abi/mangle72.C b/gcc/testsuite/g++.dg/abi/mangle72.C
index 656a0cae403..308865bd2c6 100644
--- a/gcc/testsuite/g++.dg/abi/mangle72.C
+++ b/gcc/testsuite/g++.dg/abi/mangle72.C
@@ -24,56 +24,50 @@ struct B { padm_t a[2]; };
 template <B> struct Y { };
 
 void g__ (Y<B{{ }}>) { }
-// { dg-final { scan-assembler "_Z3g__1YIXtl1BtlA2_M1AA2_iLS3_0EEEEE" } }
+// { dg-final { scan-assembler "_Z3g__1YIXtl1BEEE" } }
 
 void g0_ (Y<B{{ 0 }}>) { }
-// { dg-final { scan-assembler "_Z3g0_1YIXtl1BtlA2_M1AA2_iLS3_0EEEEE" } }
+// { dg-final { scan-assembler "_Z3g0_1YIXtl1BEEE" } }
 
 void g00 (Y<B{{ 0, 0 }}>) { }
-// { dg-final { scan-assembler "_Z3g001YIXtl1BtlA2_M1AA2_iLS3_0EEEEE" } }
+// { dg-final { scan-assembler "_Z3g001YIXtl1BEEE" } }
 
 void g0x (Y<B{{ 0, &A::a }}>) { }
-// FIXME: This needs to mangle differently from g00.  The space at
-// the end is intentional to make the directive fail so that the xfail
-// can be reminder to change this once the mangling is fixed.
-// { dg-final { scan-assembler "_Z3g0x1YIXtl1BtlA2_M1AA2_iLS3_0EEEEE " { xfail *-*-* } } }
+// { dg-final { scan-assembler "_Z3g0x1YIXtl1BtlA2_M1AA2_iLS3_0EadL_ZNS1_1aEEEEEE" } }
 
 void gx_ (Y<B{{ &A::a }}>) { }
-// { dg-final { scan-assembler "_Z3gx_1YIXtl1BtlA2_M1AA2_iLS3_0ELS3_0EEEEE" } }
+// { dg-final { scan-assembler "_Z3gx_1YIXtl1BtlA2_M1AA2_iadL_ZNS1_1aEEEEEE" } }
 
 
 struct C { padm_t a[3]; };
 template <C> struct Z { };
 
 void h___ (Z<C{{ }}>) { }
-// { dg-final { scan-assembler "_Z4h___1ZIXtl1CtlA3_M1AA2_iLS3_0EEEEE" } }
+// { dg-final { scan-assembler "_Z4h___1ZIXtl1CEEE" } }
 
 void h0__ (Z<C{{ 0 }}>) { }
-// { dg-final { scan-assembler "_Z4h0__1ZIXtl1CtlA3_M1AA2_iLS3_0EEEEE" } }
+// { dg-final { scan-assembler "_Z4h0__1ZIXtl1CEEE" } }
 
 void h00_ (Z<C{{ 0, 0 }}>) { }
-// { dg-final { scan-assembler "_Z4h00_1ZIXtl1CtlA3_M1AA2_iLS3_0EEEEE" } }
+// { dg-final { scan-assembler "_Z4h00_1ZIXtl1CEEE" } }
 
 void h000 (Z<C{{ 0, 0, 0 }}>) { }
-// { dg-final { scan-assembler "_Z4h0001ZIXtl1CtlA3_M1AA2_iLS3_0EEEEE" } }
+// { dg-final { scan-assembler "_Z4h0001ZIXtl1CEEE" } }
 
 void h00x (Z<C{{ 0, 0, &A::a }}>) { }
-// FIXME: This needs to mangle differently from hx0_ and hx__.
-// { dg-final { scan-assembler "_Z4h00x1ZIXtl1CtlA3_M1AA2_iLS3_0ELS3_0EEEEE " { xfail *-*-*} } }
+// { dg-final { scan-assembler "_Z4h00x1ZIXtl1CtlA3_M1AA2_iLS3_0ELS3_0EadL_ZNS1_1aEEEEEE" } }
 
 void h0x0 (Z<C{{ 0, &A::a, 0 }}>) { }
-// { dg-final { scan-assembler "_Z4h0x01ZIXtl1CtlA3_M1AA2_iLS3_0ELS3_0ELS3_0EEEEE" } }
+// { dg-final { scan-assembler "_Z4h0x01ZIXtl1CtlA3_M1AA2_iLS3_0EadL_ZNS1_1aEEEEEE" } }
 
 void h0x_ (Z<C{{ 0, &A::a }}>) { }
-// { dg-final { scan-assembler "_Z4h0x_1ZIXtl1CtlA3_M1AA2_iLS3_0ELS3_0ELS3_0EEEEE" } }
+// { dg-final { scan-assembler "_Z4h0x_1ZIXtl1CtlA3_M1AA2_iLS3_0EadL_ZNS1_1aEEEEEE" } }
 
 void hx0_ (Z<C{{ &A::a, 0 }}>) { }
-// FIXME: This needs to mangle differently from h00x and hx__.
-// { dg-final { scan-assembler "_Z4hx0_1ZIXtl1CtlA3_M1AA2_iLS3_0ELS3_0EEEEE " { xfail *-*-*} } }
+// { dg-final { scan-assembler "_Z4hx0_1ZIXtl1CtlA3_M1AA2_iadL_ZNS1_1aEEEEEE" } }
 
 void hx__ (Z<C{{ &A::a }}>) { }
-// FIXME: This needs to mangle differently from h00x and hx0_.
-// { dg-final { scan-assembler "_Z4hx__1ZIXtl1CtlA3_M1AA2_iLS3_0ELS3_0EEEEE " { xfail *-*-* } } }
+// { dg-final { scan-assembler "_Z4hx__1ZIXtl1CtlA3_M1AA2_iadL_ZNS1_1aEEEEEE" } }
 
 
 // Exercise arrays of pointers to function members.
diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class36.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class36.C
new file mode 100644
index 00000000000..1c1e23c10a8
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class36.C
@@ -0,0 +1,76 @@
+/* PR c++/94510 - nullptr_t implicitly cast to zero twice in std::array
+   { dg-do compile { target c++2a } }
+   { dg-options "-Wall" } */
+
+struct A { int i; int f (); };
+typedef int A::*MemPtr;
+typedef int (A::*MemFuncPtr)();
+
+struct B { MemPtr a[3]; MemFuncPtr b[3]; };
+
+static const constexpr MemPtr mp0 = { 0 };
+static const constexpr MemPtr mpn = { nullptr };
+static const constexpr MemPtr mp_ = { };
+static const constexpr MemPtr mpi = { &A::i };
+
+template <B> struct X { };
+
+typedef X<B{ }>                               XB;
+typedef X<B{ 0 }>                             XB;
+typedef X<B{{ 0 }}>                           XB;
+typedef X<B{{ MemPtr{ }}}>                    XB;
+typedef X<B{{ MemPtr{ 0 }}}>                  XB;
+typedef X<B{{ MemPtr () }}>                   XB;
+typedef X<B{{ MemPtr{ nullptr }}}>            XB;
+typedef X<B{{ mp_ }}>                         XB;
+typedef X<B{{ mpn }}>                         XB;
+typedef X<B{{ mp0 }}>                         XB;
+
+typedef X<B{ mpi }>                           XBp;
+typedef X<B{ mpi, 0 }>                        XBp;
+typedef X<B{{ mpi, 0 }}>                      XBp;
+typedef X<B{{ mpi, MemPtr{ }}}>               XBp;
+typedef X<B{{ mpi, MemPtr{ 0 }}}>             XBp;
+typedef X<B{{ mpi, MemPtr () }}>              XBp;
+typedef X<B{{ mpi, MemPtr{ nullptr }}}>       XBp;
+typedef X<B{{ mpi, mp_ }}>                    XBp;
+typedef X<B{{ mpi, mpn }}>                    XBp;
+typedef X<B{{ mpi, mp0 }}>                    XBp;
+
+typedef X<B{ mpi, mpi }>                      XBpp;
+typedef X<B{ mpi, mpi, 0 }>                   XBpp;
+typedef X<B{{ mpi, mpi, 0 }}>                 XBpp;
+typedef X<B{{ mpi, mpi, MemPtr{ }}}>          XBpp;
+typedef X<B{{ mpi, mpi, MemPtr{ 0 }}}>        XBpp;
+typedef X<B{{ mpi, mpi, MemPtr () }}>         XBpp;
+typedef X<B{{ mpi, mpi, MemPtr{ nullptr }}}>  XBpp;
+typedef X<B{{ mpi, mpi, mp_ }}>               XBpp;
+typedef X<B{{ mpi, mpi, mpn }}>               XBpp;
+typedef X<B{{ mpi, mpi, mp0 }}>               XBpp;
+
+typedef X<B{ 0, mpi }>                        XB0p;
+typedef X<B{ nullptr, mpi, 0 }>               XB0p;
+typedef X<B{ mp0, mpi, 0 }>                   XB0p;
+
+typedef X<B{ 0, 0, mpi }>                     XB00p;
+typedef X<B{ 0, nullptr, mpi }>               XB00p;
+typedef X<B{ nullptr, 0, mpi }>               XB00p;
+typedef X<B{ nullptr, nullptr, mpi }>         XB00p;
+typedef X<B{ MemPtr{ }, MemPtr{ }, mpi }>     XB00p;
+typedef X<B{ mp0, MemPtr{ }, mpi }>           XB00p;
+typedef X<B{ mpn, mpn, mpi }>                 XB00p;
+typedef X<B{ mpn, mp_, mpi }>                 XB00p;  // { dg-bogus "conflicting declaration" "pr94568" { xfail *-*-* } }
+
+static const constexpr MemFuncPtr mfp0 = { 0 };
+static const constexpr MemFuncPtr mfpn = { nullptr };
+static const constexpr MemFuncPtr mfp_ = { };
+
+typedef X<B{{ }, { }}>                        XB;
+typedef X<B{{ }, { 0 }}>                      XB;
+typedef X<B{{ }, { MemFuncPtr{ }}}>           XB;
+typedef X<B{{ }, { MemFuncPtr{ 0 }}}>         XB;
+typedef X<B{{ }, { MemFuncPtr () }}>          XB;
+typedef X<B{{ }, { MemFuncPtr{ nullptr }}}>   XB;
+typedef X<B{{ }, { mfp_ }}>                   XB;
+typedef X<B{{ }, { mfpn }}>                   XB;
+typedef X<B{{ }, { mfp0 }}>                   XB;
diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class37.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class37.C
new file mode 100644
index 00000000000..5649fa2e6dc
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class37.C
@@ -0,0 +1,80 @@
+/* PR c++/94510 - nullptr_t implicitly cast to zero twice in std::array
+   { dg-do compile { target c++2a } }
+   { dg-options "-Wall" } */
+
+struct A { char a[4]; };
+template <A> struct B { };
+
+constexpr const char c0{ };
+constexpr const char c1{ 1 };
+
+typedef B<A{ }>                     BA;
+typedef B<A{ { } }>                 BA;
+typedef B<A{ { 0 } }>               BA;
+typedef B<A{ { c0 } }>              BA;
+typedef B<A{ { 0, 0 } }>            BA;
+typedef B<A{ { 0, 0, 0 } }>         BA;
+typedef B<A{ { 0, 0, 0, 0 } }>      BA;
+typedef B<A{ { c0, c0, c0 } }>      BA;
+typedef B<A{ { c0, c0, c0, c0 } }>  BA;
+typedef B<A{ "" }>                  BA;
+typedef B<A{ "\0" }>                BA;
+typedef B<A{ "\0\0" }>              BA;
+typedef B<A{ "\0\0\0" }>            BA;
+
+typedef B<A{ 1 }>                   BA1;
+typedef B<A{ { 1 } }>               BA1;
+typedef B<A{ { 1, 0 } }>            BA1;
+typedef B<A{ { 1, 0, 0 } }>         BA1;
+typedef B<A{ { 1, 0, 0, 0 } }>      BA1;
+typedef B<A{ { c1 } }>              BA1;
+typedef B<A{ { c1, c0 } }>          BA1;
+typedef B<A{ { c1, c0, c0 } }>      BA1;
+typedef B<A{ { c1, c0, c0, c0 } }>  BA1;
+typedef B<A{ "\1" }>                BA1;
+typedef B<A{ "\1\0" }>              BA1;
+typedef B<A{ "\1\0\0" }>            BA1;
+
+typedef B<A{ 0, 1 }>                BA01;
+typedef B<A{ { 0, 1 } }>            BA01;
+typedef B<A{ { 0, 1, 0 } }>         BA01;
+typedef B<A{ { 0, 1, 0, 0 } }>      BA01;
+typedef B<A{ { c0, c1 } }>          BA01;
+typedef B<A{ { c0, c1, c0 } }>      BA01;
+typedef B<A{ { c0, c1, c0, c0 } }>  BA01;
+typedef B<A{ "\0\1" }>              BA01;
+typedef B<A{ "\0\1\0" }>            BA01;
+
+
+struct C { int a[4]; };
+template <C> struct D { };
+
+constexpr const int i0{ };
+
+typedef D<C{ }>                     DC;
+typedef D<C{ { } }>                 DC;
+typedef D<C{ { 0 } }>               DC;
+typedef D<C{ { 0, 0 } }>            DC;
+typedef D<C{ { 0, 0, 0 } }>         DC;
+typedef D<C{ { 0, 0, 0, 0 } }>      DC;
+typedef D<C{ { i0 } }>              DC;
+typedef D<C{ { i0, i0 } }>          DC;
+typedef D<C{ { i0, i0, i0 } }>      DC;
+typedef D<C{ { i0, i0, i0, i0 } }>  DC;
+
+
+constexpr const int i1{ 1 };
+
+typedef D<C{ 1 }>                   DC1;
+typedef D<C{ { 1 } }>               DC1;
+typedef D<C{ { 1, 0 } }>            DC1;
+typedef D<C{ { 1, 0, 0 } }>         DC1;
+typedef D<C{ { 1, 0, 0, 0 } }>      DC1;
+typedef D<C{ { i1, i0, i0, i0 } }>  DC1;
+
+typedef D<C{ 0, 1 }>                DC01;
+typedef D<C{ { 0, 1 } }>            DC01;
+typedef D<C{ { 0, 1, 0 } }>         DC01;
+typedef D<C{ { 0, 1, 0, 0 } }>      DC01;
+typedef D<C{ { 0, i1, 0, 0 } }>     DC01;
+typedef D<C{ { i0, i1, i0, i0 } }>  DC01;   // { dg-bogus "conflicting declaration" "pr94567" { xfail *-*-* } }
diff --git a/gcc/testsuite/g++.dg/init/array58.C b/gcc/testsuite/g++.dg/init/array58.C
new file mode 100644
index 00000000000..70e86445c07
--- /dev/null
+++ b/gcc/testsuite/g++.dg/init/array58.C
@@ -0,0 +1,26 @@
+/* PR c++/94510 - nullptr_t implicitly cast to zero twice in std::array
+   { dg-do compile } */
+
+int ia1[2] = { (void*)0 };              // { dg-error "invalid conversion from 'void\\\*'" }
+int ia2[2] = { (void*)0, 0 };           // { dg-error "invalid conversion from 'void\\\*'" }
+int ia3[] = { (void*)0, 0 };            // { dg-error "invalid conversion from 'void\\\*'" }
+
+int ia4[2] = { __null };                // { dg-warning "\\\[-Wconversion-null" }
+int ia5[2] = { __null, 0 };             // { dg-warning "\\\[-Wconversion-null" }
+int ia6[] = { __null, 0 };              // { dg-warning "\\\[-Wconversion-null" }
+
+
+const char ca1[2] = { (char*)0, 0 };    // { dg-error "invalid conversion from 'char\\\*'" }
+
+const char ca2[2] = { __null, 0 };      // { dg-warning "\\\[-Wconversion-null" }
+
+
+typedef void Func ();
+const char ca6[2] = { (Func*)0, 0 };    // { dg-error "invalid conversion from 'void \\\(\\\*\\\)\\\(\\\)' to 'char'" }
+
+struct S;
+typedef int S::*MemPtr;
+typedef int (S::*MemFuncPtr)();
+
+const char ca4[2] = { (MemPtr)0, 0 };   // { dg-error "cannot convert 'MemPtr' " }
+const char ca5[2] = { (MemFuncPtr)0, 0 };   // { dg-error "cannot convert 'int \\\(S::\\\*\\\)\\\(\\\)' "  }
diff --git a/gcc/testsuite/g++.dg/init/array59.C b/gcc/testsuite/g++.dg/init/array59.C
new file mode 100644
index 00000000000..e8680de9456
--- /dev/null
+++ b/gcc/testsuite/g++.dg/init/array59.C
@@ -0,0 +1,42 @@
+/* PR c++/94510 - nullptr_t implicitly cast to zero twice in std::array
+   { dg-do compile { target c++11 } } */
+
+namespace std {
+typedef __typeof__ (nullptr) nullptr_t;
+}
+
+int ia1[2] = { nullptr };                 // { dg-error "cannot convert 'std::nullptr_t' to 'int'" }
+int ia2[2] = { nullptr, 0 };              // { dg-error "cannot convert 'std::nullptr_t' to 'int'" }
+int ia3[] = { nullptr, 0 };               // { dg-error "cannot convert 'std::nullptr_t' to 'int'" }
+
+int ia4[2] = { (std::nullptr_t)0 };      // { dg-error "cannot convert 'std::nullptr_t' to 'int'" }
+int ia5[2] = { (std::nullptr_t)0, 0 };   // { dg-error "cannot convert 'std::nullptr_t' to 'int'" }
+int ia6[] = { (std::nullptr_t)0, 0 };    // { dg-error "cannot convert 'std::nullptr_t' to 'int'" }
+
+
+const char ca1[2] = { nullptr, 0 };       // { dg-error "cannot convert 'std::nullptr_t' to 'const char'" }
+
+const char ca2[2] = { (char*)nullptr, 0 };// { dg-error "invalid conversion from 'char\\\*' to 'char'" }
+
+const char ca3[2] = { std::nullptr_t () };// { dg-error "cannot convert 'std::nullptr_t'" }
+
+/* Verify that arrays of member pointers can be initialized by a literal
+   zero as well as nullptr.  */
+
+struct S { };
+typedef int S::*MemPtr;
+typedef int (S::*MemFuncPtr)();
+
+MemPtr mp1[3] = { 0, nullptr, (MemPtr)0 };
+MemPtr mp2[3] = { 0, std::nullptr_t (), MemPtr () };
+
+MemPtr mp3[3] = { 0, (void*)0 };          // { dg-error "cannot convert 'void\\\*' to 'MemPtr' " }
+MemPtr mp4[3] = { 0, (S*)0 };             // { dg-error "cannot convert 'S\\\*' to 'MemPtr' " }
+MemPtr mp5[3] = { 0, S () };              // { dg-error "cannot convert 'S' to 'MemPtr' " }
+
+MemFuncPtr mfp1[3] = { 0, nullptr, (MemFuncPtr)0 };
+MemFuncPtr mfp2[3] = { 0, std::nullptr_t (), MemFuncPtr () };
+
+MemFuncPtr mfp3[3] = { 0, (void*)0 };     // { dg-error "cannot convert 'void\\\*' to 'MemFuncPtr' " }
+MemFuncPtr mfp4[3] = { 0, (S*)0 };        // { dg-error "cannot convert 'S\\\*' to 'MemFuncPtr' " }
+MemFuncPtr mfp5[3] = { 0, S () };         // { dg-error "cannot convert 'S' to 'MemFuncPtr' " }

Reply via email to