diff mbox series

c++: check_flexarray fixes [PR117516]

Message ID Z1Qkm/bLU7bULsWH@tucnak
State New
Headers show
Series c++: check_flexarray fixes [PR117516] | expand

Commit Message

Jakub Jelinek Dec. 7, 2024, 10:34 a.m. UTC
Hi!

On the pr117516.C testcase check_flexarrays and its helper functions
have exponential complexity, plus it reports the same bug over and over
again in some cases instead of reporting perhaps other bugs.
The functions want to diagnose flexible array member (and strangely [0]
arrays too) followed by some other non-empty or array members in the same
strcuture, or followed by other non-empty or array members in a containing
structure (any of them), or flexible array members/[0] arrays in structures
with no other non-empty members, or those nested in other structures.
Strangely it doesn't complain if flexible array member is in a structure
used in an array.

As can be seeen on e.g. the flexary41.C test, it keeps reporting the
same bug over and over:
flexary41.C:5:24: error: flexible array member ‘A::b’ not at end of ‘struct A’
flexary41.C:5:24: error: flexible array member ‘A::b’ not at end of ‘struct B’
flexary41.C:5:24: error: flexible array member ‘A::b’ not at end of ‘struct C’
flexary41.C:5:24: error: flexible array member ‘A::b’ not at end of ‘struct D’
flexary41.C:13:39: error: flexible array member ‘E::<unnamed struct>::n’ not at end of ‘struct E’
flexary41.C:18:23: error: flexible array member ‘H::t’ not at end of ‘struct K’
flexary41.C:25:36: note: next member ‘int K::ab’ declared here
flexary41.C:25:8: note: in the definition of ‘struct K’
The bug that A::b is followed by A::c is one bug reported 4 times, while it
doesn't report the other bugs, that B::e flexarray is followed by B::f
and that C::h flexarray is followed by C::i.
That is because it always walks all the structures/unions of all the members
and just finds the first flexarray in there.

Now, this has horrible complexity plus it doesn't seem really useful to
users.  So, for cases where a flexible array member is followed by a
non-empty other member in the same structure, the following patch just
reports it once when finalizing that structure, and otherwise just recurses
in structures solely into the last member, so that it can report cases like
struct X { int a; int b[]; };
struct Y { X c; int d; };
or
struct Z { X c; };
i.e. correct use of flexarray in X but following it by another member in Y
or just nesting it (the former is error, the latter pedwarn as before).
By only looking at the last member for structures we get rid of the complexity.

Note, the patch doesn't do anything about unions, I think we still could
spend a lot of time compiling.
struct S { char s; };
union U0 { S a, b; };
union U1 { union U0 a, b; };
union U2 { union U1 a, b; };
...
union U32 { union U31 a, b; };
struct T { union U32 a; int b; };
Not really sure what we could do about that, all the elements are "last"
(but admittedly I haven't studied in detail how the original code worked
in union, there is fmem->after[pun] where pun is whether it is somewhere
inside of a union).  Perhaps in a hash table marking unions which don't have
any flexarrays at the end, nested or not, so that we don't walk them again?
Plus if we find some with flexarray at the end, maybe there is no point
to look other union members?  In any case, I think that is less severe,
because people usually don't nest unions deeply.

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

2024-12-07  Jakub Jelinek  <jakub@redhat.com>

	PR c++/117516
	* class.cc (field_nonempty_p): Formatting fixes.  Use
	integer_zerop instead of tree_int_cst_equal with size_zero_node.
	(struct flexmems_t): Change type of first member from tree to bool.
	(find_flexarrays): Add nested_p argument.  Change pun argument type
	from tree to bool, adjust uses.  Formatting fixes.  If BASE_P or
	NESTED_P and T is RECORD_TYPE, start looking only at the last
	non-empty or array FIELD_DECL.  Adjust recursive call, set first
	if it was a nested call and found an array.
	(diagnose_invalid_flexarray, diagnose_flexarrays, check_flexarrays):
	Formatting fixes.

	* g++.dg/ext/flexary9.C: Expect different wording of one of the
	warnings and at a different line.
	* g++.dg/ext/flexary19.C: Likewise.
	* g++.dg/ext/flexary41.C: New test.
	* g++.dg/other/pr117516.C: New test.


	Jakub
diff mbox series

Patch

--- gcc/cp/class.cc.jj	2024-12-06 11:00:21.418671398 +0100
+++ gcc/cp/class.cc	2024-12-06 13:55:39.074460055 +0100
@@ -141,8 +141,8 @@  static bool accessible_nvdtor_p (tree);
 /* Used by find_flexarrays and related functions.  */
 struct flexmems_t;
 static void diagnose_flexarrays (tree, const flexmems_t *);
-static void find_flexarrays (tree, flexmems_t *, bool = false,
-			     tree = NULL_TREE, tree = NULL_TREE);
+static void find_flexarrays (tree, flexmems_t *, bool, bool = false,
+			     bool = false, tree = NULL_TREE);
 static void check_flexarrays (tree, flexmems_t * = NULL, bool = false);
 static void check_bases (tree, int *, int *);
 static void check_bases_and_members (tree);
@@ -7362,11 +7362,9 @@  field_nonempty_p (const_tree fld)
   if (TREE_CODE (fld) == FIELD_DECL
       && TREE_CODE (type) != ERROR_MARK
       && (DECL_NAME (fld) || RECORD_OR_UNION_TYPE_P (type)))
-    {
-      return TYPE_SIZE (type)
-	&& (TREE_CODE (TYPE_SIZE (type)) != INTEGER_CST
-	    || !tree_int_cst_equal (size_zero_node, TYPE_SIZE (type)));
-    }
+    return (TYPE_SIZE (type)
+	    && (TREE_CODE (TYPE_SIZE (type)) != INTEGER_CST
+		|| !integer_zerop (TYPE_SIZE (type))));
 
   return false;
 }
@@ -7378,8 +7376,9 @@  struct flexmems_t
   /* The first flexible array member or non-zero array member found
      in the order of layout.  */
   tree array;
-  /* First non-static non-empty data member in the class or its bases.  */
-  tree first;
+  /* True if there is a non-static non-empty data member in the class or
+     its bases.  */
+  bool first;
   /* The first non-static non-empty data member following either
      the flexible array member, if found, or the zero-length array member
      otherwise.  AFTER[1] refers to the first such data member of a union
@@ -7400,24 +7399,50 @@  struct flexmems_t
    array, in that order of preference, among members of class T (but not
    its base classes), and set members of FMEM accordingly.
    BASE_P is true if T is a base class of another class.
-   PUN is set to the outermost union in which the flexible array member
-   (or zero-length array) is defined if one such union exists, otherwise
-   to NULL.
-   Similarly, PSTR is set to a data member of the outermost struct of
+   PUN is true when inside of a union (perhaps recursively).
+   PSTR is set to a data member of the outermost struct of
    which the flexible array is a member if one such struct exists,
    otherwise to NULL.  */
 
 static void
 find_flexarrays (tree t, flexmems_t *fmem, bool base_p,
-		 tree pun /* = NULL_TREE */,
+		 bool nested_p /* = false */, bool pun /* = false */,
 		 tree pstr /* = NULL_TREE */)
 {
-  /* Set the "pointer" to the outermost enclosing union if not set
-     yet and maintain it for the remainder of the recursion.   */
-  if (!pun && TREE_CODE (t) == UNION_TYPE)
-    pun = t;
+  if (TREE_CODE (t) == UNION_TYPE)
+    pun = true;
 
-  for (tree fld = TYPE_FIELDS (t); fld; fld = DECL_CHAIN (fld))
+  tree fld = TYPE_FIELDS (t);
+  if ((base_p || nested_p) && TREE_CODE (t) == RECORD_TYPE)
+    {
+      /* In bases or in nested structures, only process the last
+	 non-static data member.  If we have say
+	 struct A { int a; int b[]; int c; };
+	 struct B { int d; int e[]; int f; };
+	 struct C : A { int g; B h, i; int j; };
+	 then the A::b followed by A::c should have been diagnosed
+	 already when completing struct A, and B::e followed by B::f
+	 when completing struct B, so no need to repeat that when completing
+	 struct C.  So, only look at the last member so we cover e.g.
+	 struct D { int k; int l[]; };
+	 struct E : D { int m; };
+	 struct F { D n; int o; };
+	 where flexible array member at the end of D is fine, but it isn't
+	 correct that E::m in E or F::o in F follows it.  */
+      tree last_fld = NULL_TREE;
+      for (; fld; fld = DECL_CHAIN (fld))
+	if (fld == error_mark_node)
+	  return;
+	else if (DECL_ARTIFICIAL (fld) || TREE_CODE (fld) != FIELD_DECL)
+	  continue;
+	else if (TREE_TYPE (fld) == error_mark_node)
+	  return;
+	else if (TREE_CODE (TREE_TYPE (fld)) == ARRAY_TYPE
+		 || field_nonempty_p (fld))
+	  last_fld = fld;
+      fld = last_fld;
+    }
+  for (; fld; fld = DECL_CHAIN (fld))
     {
       if (fld == error_mark_node)
 	return;
@@ -7462,11 +7487,11 @@  find_flexarrays (tree t, flexmems_t *fme
 
       if (RECORD_OR_UNION_TYPE_P (eltype))
 	{
-	  if (fmem->array && !fmem->after[bool (pun)])
+	  if (fmem->array && !fmem->after[pun])
 	    {
 	      /* Once the member after the flexible array has been found
 		 we're done.  */
-	      fmem->after[bool (pun)] = fld;
+	      fmem->after[pun] = fld;
 	      break;
 	    }
 
@@ -7480,32 +7505,43 @@  find_flexarrays (tree t, flexmems_t *fme
 		 are already in the process of being checked by one of the
 		 recursive calls).  */
 
-	      tree first = fmem->first;
+	      bool first = fmem->first;
 	      tree array = fmem->array;
+	      bool maybe_anon_p = TYPE_UNNAMED_P (eltype);
+	      if (tree ctx = maybe_anon_p ? TYPE_CONTEXT (eltype) : NULL_TREE)
+		maybe_anon_p = RECORD_OR_UNION_TYPE_P (ctx);
 
 	      /* If this member isn't anonymous and a prior non-flexible array
 		 member has been seen in one of the enclosing structs, clear
 		 the FIRST member since it doesn't contribute to the flexible
 		 array struct's members.  */
 	      if (first && !array && !ANON_AGGR_TYPE_P (eltype))
-		fmem->first = NULL_TREE;
+		fmem->first = false;
 
-	      find_flexarrays (eltype, fmem, false, pun,
-			       !pstr && TREE_CODE (t) == RECORD_TYPE ? fld : pstr);
+	      find_flexarrays (eltype, fmem, false, !maybe_anon_p, pun,
+			       !pstr && TREE_CODE (t) == RECORD_TYPE
+			       ? fld : pstr);
 
 	      if (fmem->array != array)
-		continue;
-
-	      if (first && !array && !ANON_AGGR_TYPE_P (eltype))
 		{
-		  /* Restore the FIRST member reset above if no flexible
-		     array member has been found in this member's struct.  */
-		  fmem->first = first;
+		  /* If the recursive call will have nested_p set,
+		     it looks just at the last field and we do not
+		     want to diagnose in that case the "in otherwise empty"
+		     case, just if it is followed by some other non-empty
+		     member.  So set fmem->first.  */
+		  if (!maybe_anon_p)
+		    fmem->first = true;
+		  continue;
 		}
 
+	      if (first && !array && !ANON_AGGR_TYPE_P (eltype))
+		/* Restore the FIRST member reset above if no flexible
+		   array member has been found in this member's struct.  */
+		fmem->first = first;
+
 	      /* If the member struct contains the first flexible array
 		 member, or if this member is a base class, continue to
-		 the next member and avoid setting the FMEM->NEXT pointer
+		 the next member and avoid setting the FMEM->AFTER pointer
 		 to point to it.  */
 	      if (base_p)
 		continue;
@@ -7516,13 +7552,13 @@  find_flexarrays (tree t, flexmems_t *fme
 	{
 	  /* Remember the first non-static data member.  */
 	  if (!fmem->first)
-	    fmem->first = fld;
+	    fmem->first = true;
 
 	  /* Remember the first non-static data member after the flexible
 	     array member, if one has been found, or the zero-length array
 	     if it has been found.  */
-	  if (fmem->array && !fmem->after[bool (pun)])
-	    fmem->after[bool (pun)] = fld;
+	  if (fmem->array && !fmem->after[pun])
+	    fmem->after[pun] = fld;
 	}
 
       /* Skip non-arrays.  */
@@ -7538,8 +7574,8 @@  find_flexarrays (tree t, flexmems_t *fme
 		 such field or a flexible array member has been seen to
 		 handle the pathological and unlikely case of multiple
 		 such members.  */
-	      if (!fmem->after[bool (pun)])
-		fmem->after[bool (pun)] = fld;
+	      if (!fmem->after[pun])
+		fmem->after[pun] = fld;
 	    }
 	  else if (integer_all_onesp (TYPE_MAX_VALUE (TYPE_DOMAIN (fldtype))))
 	    {
@@ -7558,13 +7594,13 @@  find_flexarrays (tree t, flexmems_t *fme
 		{
 		  /* Replace the zero-length array if it's been stored and
 		     reset the after pointer.  */
-		  fmem->after[bool (pun)] = NULL_TREE;
+		  fmem->after[pun] = NULL_TREE;
 		  fmem->array = fld;
 		  fmem->enclosing = pstr;
 		}
-	      else if (!fmem->after[bool (pun)])
+	      else if (!fmem->after[pun])
 		/* Make a record of another flexible array member.  */
-		fmem->after[bool (pun)] = fld;
+		fmem->after[pun] = fld;
 	    }
 	  else
 	    {
@@ -7585,15 +7621,14 @@  diagnose_invalid_flexarray (const flexme
     {
       auto_diagnostic_group d;
       if (pedwarn (location_of (fmem->enclosing), OPT_Wpedantic,
-		     TYPE_DOMAIN (TREE_TYPE (fmem->array))
-		     ? G_("invalid use of %q#T with a zero-size array "
-			  "in %q#D")
-		     : G_("invalid use of %q#T with a flexible array member "
-			  "in %q#T"),
-		     DECL_CONTEXT (fmem->array),
-		     DECL_CONTEXT (fmem->enclosing)))
+		   TYPE_DOMAIN (TREE_TYPE (fmem->array))
+		   ? G_("invalid use of %q#T with a zero-size array in %q#D")
+		   : G_("invalid use of %q#T with a flexible array member "
+			"in %q#T"),
+		   DECL_CONTEXT (fmem->array),
+		   DECL_CONTEXT (fmem->enclosing)))
 	inform (DECL_SOURCE_LOCATION (fmem->array),
-		  "array member %q#D declared here", fmem->array);
+		"array member %q#D declared here", fmem->array);
     }
 }
 
@@ -7662,8 +7697,8 @@  diagnose_flexarrays (tree t, const flexm
 	     overlaps another member of a common union, point to it.
 	     Otherwise it should be obvious.  */
 	  if (fmem->after[0]
-	      && ((DECL_CONTEXT (fmem->after[0])
-		   != DECL_CONTEXT (fmem->array))))
+	      && (DECL_CONTEXT (fmem->after[0])
+		  != DECL_CONTEXT (fmem->array)))
 	    {
 	      inform (DECL_SOURCE_LOCATION (fmem->after[0]),
 		      "next member %q#D declared here",
@@ -7756,14 +7791,12 @@  check_flexarrays (tree t, flexmems_t *fm
     find_flexarrays (t, fmem, base_p || fam != fmem->array);
 
   if (fmem == &flexmems && !maybe_anon_p)
-    {
-      /* Issue diagnostics for invalid flexible and zero-length array
-	 members found in base classes or among the members of the current
-	 class.  Ignore anonymous structs and unions whose members are
-	 considered to be members of the enclosing class and thus will
-	 be diagnosed when checking it.  */
-      diagnose_flexarrays (t, fmem);
-    }
+    /* Issue diagnostics for invalid flexible and zero-length array
+       members found in base classes or among the members of the current
+       class.  Ignore anonymous structs and unions whose members are
+       considered to be members of the enclosing class and thus will
+       be diagnosed when checking it.  */
+    diagnose_flexarrays (t, fmem);
 }
 
 /* Perform processing required when the definition of T (a class type)
--- gcc/testsuite/g++.dg/ext/flexary9.C.jj	2020-01-12 11:54:37.168402018 +0100
+++ gcc/testsuite/g++.dg/ext/flexary9.C	2024-12-06 13:57:57.287527367 +0100
@@ -272,7 +272,7 @@  struct S_S_S_x {
   struct A {
     struct B {
       int a[0];             // { dg-warning "zero-size array" }
-    } b;
+    } b;		    // { dg-warning "invalid use" }
   } a;
 };
 
--- gcc/testsuite/g++.dg/ext/flexary19.C.jj	2024-05-07 08:47:36.150834558 +0200
+++ gcc/testsuite/g++.dg/ext/flexary19.C	2024-12-06 13:57:08.681207050 +0100
@@ -210,7 +210,7 @@  struct S22
     static int i;
 
     int a[];        // { dg-warning "in an otherwise empty" }
-  } s;
+  } s;		    // { dg-warning "invalid use" }
 };
 
 struct S23
--- gcc/testsuite/g++.dg/ext/flexary41.C.jj	2024-12-06 14:19:53.146129348 +0100
+++ gcc/testsuite/g++.dg/ext/flexary41.C	2024-12-06 14:19:40.672303712 +0100
@@ -0,0 +1,26 @@ 
+// PR c++/117516
+// { dg-do compile }
+// { dg-options "-Wpedantic -Wno-error=pedantic" }
+
+struct A { int a; char b[]; int c; };						// { dg-warning "forbids flexible array member" }
+										// { dg-error "flexible array member 'A::b' not at end of 'struct A'" "" { target *-*-* } .-1 }
+struct B { struct A d; struct A e[]; struct A f; };				// { dg-warning "forbids flexible array member" }
+										// { dg-error "flexible array member 'B::e' not at end of 'struct B'" "" { target *-*-* } .-1 }
+struct C { struct B g; struct B h[]; struct B i; };				// { dg-warning "forbids flexible array member" }
+										// { dg-error "flexible array member 'C::h' not at end of 'struct C'" "" { target *-*-* } .-1 }
+struct D { struct C j; };
+D k;
+struct E { int l; struct { int m; int n[]; int o; } p; int q; };		// { dg-warning "forbids flexible array member" }
+										// { dg-error "flexible array member 'E::<unnamed struct>::n' not at end of 'struct E'" "" { target *-*-* } .-1 }
+struct F {};
+struct G { int r[]; };								// { dg-warning "forbids flexible array member" }
+										// { dg-warning "flexible array member 'G::r' in an otherwise empty 'struct G' is a GCC extension" "" { target *-*-* } .-1 }
+struct H { int s; int t[]; };							// { dg-warning "forbids flexible array member" }
+										// { dg-error "flexible array member 'H::t' not at end of 'struct K'" "" { target *-*-* } .-1 }
+										// { dg-message "array member 'int H::t \\\[\\\]' declared here" "" { target *-*-* } .-2 }
+#if __cplusplus >= 202002L
+struct I { [[no_unique_address]] F u; [[no_unique_address]] F v; int w[]; };	// { dg-warning "forbids flexible array member" "" { target c++20 } }
+#endif
+struct J { int x; struct H y; };						// { dg-warning "invalid use of 'struct H' with a flexible array member in 'struct J'" }
+struct K { int z; struct H aa; int ab; };					// { dg-message "next member 'int K::ab' declared here" }
+										// { dg-message "in the definition of 'struct K'" "" { target *-*-* } .-1 }
--- gcc/testsuite/g++.dg/other/pr117516.C.jj	2024-12-06 14:08:05.814018076 +0100
+++ gcc/testsuite/g++.dg/other/pr117516.C	2024-12-06 14:03:04.993224592 +0100
@@ -0,0 +1,21 @@ 
+// PR c++/117516
+// { dg-do compile }
+
+struct S10 { char a; };
+#define A(m, n) struct S##m { struct S##n a, b; };
+#define B(m) A(m##1, m##0) A(m##2, m##1) A(m##3, m##2) A(m##4, m##3) \
+	     A(m##5, m##4) A(m##6, m##5) A(m##7, m##6) A(m##8, m##7)
+B(1)
+#define S20 S18
+B(2)
+#define S30 S28
+B(3)
+#define S40 S38
+A(41, 40) A(42, 41) A(43, 42)
+
+int
+main ()
+{
+  struct S43 s;
+  __builtin_memset (&s, 0, sizeof (s));
+}