]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c: Mark derived types variably-modified after struct/union completion [PR123424]
authorMartin Uecker <uecker@tugraz.at>
Sat, 7 Mar 2026 15:28:00 +0000 (16:28 +0100)
committerMartin Uecker <uecker@gcc.gnu.org>
Tue, 24 Mar 2026 05:52:40 +0000 (06:52 +0100)
When structure types are completed and are variably-modified, we
need to make sure that C_TYPE_VARIABLY_MODIFIED is updated also
for derived types.  For derived types which are not updated and
remain TYPE_STRUCTURAL_EQUALITY_P, we do recursion when checking
the bit instead.  This change then fixes also a weird corner
case when forming composite types of mutually recursively
defined types.

We do not update other structure or union type that end up
with variably modified pointers, as it is not clear this is
needed and also needs further analysis.

PR c/123424

gcc/c/ChangeLog:
* c-decl.cc (c_update_variably_modified): New function.
(finish_struct): Call c_update_variably_modified.
* c-tree.h (c_variably_modified_p): Recurse if necessary.
* c-typeck.cc (c_verify_type): Update.
(c_set_type_bits): Use c_variably_modified_p.
(mark_decl_used): Dito.
(build_function_call_vec): Dito.
(c_build_qualified_type): Dito.
* c-objc-common.cc (c_var_p): Dito.

gcc/testsuite/ChangeLog:
* gcc.dg/gnu23-tag-composite-7.c: New test.
* gcc.dg/gnu23-varmod-3.c: New test.
* gcc.dg/pr123424.c: New test.

gcc/c/c-decl.cc
gcc/c/c-objc-common.cc
gcc/c/c-tree.h
gcc/c/c-typeck.cc
gcc/testsuite/gcc.dg/gnu23-tag-composite-7.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/gnu23-varmod-3.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/pr123424.c [new file with mode: 0644]

index 98249b922695ce6f6dd9c18b45caef8c1bf8b8de..8a894de3a1b7b63f4e48c47ec9c79749f445fd6b 100644 (file)
@@ -9502,6 +9502,26 @@ c_update_type_canonical (tree t)
     }
 }
 
+
+/* We set C_TYPE_VARIABLY_MODIFIED for derived types.  We will not update
+   array types, pointers to array types, function types and other derived
+   types created while the type was still incomplete.  We need to update
+   at least all types for which TYPE_CANONICAL will bet set, because for
+   those we later assume (in c_variably_modified_p) that the bit is
+   up-to-date.  */
+
+static void
+c_update_variably_modified (tree t)
+{
+  for (tree x = t; x; x = TYPE_NEXT_VARIANT (x))
+    {
+      C_TYPE_VARIABLY_MODIFIED (x) = 1;
+      for (tree p = TYPE_POINTER_TO (x); p; p = TYPE_NEXT_PTR_TO (p))
+       c_update_variably_modified (p);
+    }
+}
+
+
 /* Verify the argument of the counted_by attribute of each field of
    the containing structure, OUTMOST_STRUCT_TYPE, including its inner
    anonymous struct/union, Report error and remove the corresponding
@@ -9701,7 +9721,7 @@ finish_struct (location_t loc, tree t, tree fieldlist, tree attributes,
        C_TYPE_VARIABLE_SIZE (t) = 1;
 
       /* If any field is variably modified, record this fact. */
-      if (C_TYPE_VARIABLY_MODIFIED (TREE_TYPE (x)))
+      if (c_type_variably_modified_p (TREE_TYPE (x)))
        C_TYPE_VARIABLY_MODIFIED (t) = 1;
 
       if (DECL_C_BIT_FIELD (x))
@@ -10031,12 +10051,16 @@ finish_struct (location_t loc, tree t, tree fieldlist, tree attributes,
 
   finish_incomplete_vars (incomplete_vars, toplevel);
 
-  /* Make sure a DECL_EXPR is created for structs with VLA members.
-     Because we do not know the context, we always pass expr
-     to force creation of a BIND_EXPR which is required in some
-     contexts.  */
+
   if (c_type_variably_modified_p (t))
-    add_decl_expr (loc, t, expr, false);
+    {
+      c_update_variably_modified (t);
+      /* Make sure a DECL_EXPR is created for structs with VLA members.
+        Because we do not know the context, we always pass expr
+        to force creation of a BIND_EXPR which is required in some
+        contexts.  */
+      add_decl_expr (loc, t, expr, false);
+    }
 
   if (warn_cxx_compat)
     warn_cxx_compat_finish_struct (fieldlist, TREE_CODE (t), loc);
index 38e88733b5098ba6e92c066deedf07e3c8970ab6..e56e936316f7adef4ff4e578de983cfa49e4eaff 100644 (file)
@@ -438,7 +438,7 @@ c_types_compatible_p (tree x, tree y)
 bool
 c_var_mod_p (tree x, tree fn ATTRIBUTE_UNUSED)
 {
-  return C_TYPE_VARIABLY_MODIFIED (x);
+  return c_type_variably_modified_p (x);
 }
 
 /* Special routine to get the alias set of T for C.  */
index 30c602b731f874e441bf07dc24e7bf4d4edc271d..e151095c3a197cf368cfe47dec25ada5d4f1eaf0 100644 (file)
@@ -807,7 +807,31 @@ extern bool null_pointer_constant_p (const_tree);
 inline bool
 c_type_variably_modified_p (tree t)
 {
-  return error_mark_node != t && C_TYPE_VARIABLY_MODIFIED (t);
+  if (error_mark_node == t)
+    return false;
+  if (C_TYPE_VARIABLY_MODIFIED (t))
+    return true;
+  if (TYPE_STRUCTURAL_EQUALITY_P (t))
+    {
+      /* The flag may not have been set yet because of incomplete
+        structure or union types completed later.  */
+      switch (TREE_CODE (t))
+       {
+       case ARRAY_TYPE:
+       case FUNCTION_TYPE:
+       case POINTER_TYPE:
+         /* Recurse.  */
+         if (c_type_variably_modified_p (TREE_TYPE (t)))
+           {
+             C_TYPE_VARIABLY_MODIFIED (t) = 1;
+             return true;
+           }
+         break;
+       default:
+         break;
+       }
+    }
+  return false;
 }
 
 inline bool
index 051773a0cd4dca91f4b6bf4ff0939ad9bad24080..4452bb32728c8a393799312a0030eb40bdf5e8fb 100644 (file)
@@ -379,13 +379,28 @@ c_verify_type (tree type)
     {
     case POINTER_TYPE:
     case FUNCTION_TYPE:
-      /* Pointer and funcions can not have variable size.  */
-      if (C_TYPE_VARIABLE_SIZE (type))
+    case ARRAY_TYPE:
+      /* Pointer, array, functions are variably modified if and only if the
+        target, element, return type is variably modified.  */
+      if (!TYPE_STRUCTURAL_EQUALITY_P (type)
+         && (C_TYPE_VARIABLY_MODIFIED (type)
+             != C_TYPE_VARIABLY_MODIFIED (TREE_TYPE (type))))
        return false;
-      /* Pointer and funcions are variably modified if and only if the
-        return / target type is variably modified.  */
-      if (C_TYPE_VARIABLY_MODIFIED (type)
-         != C_TYPE_VARIABLY_MODIFIED (TREE_TYPE (type)))
+      /* If the target type is structural equality, the type should also. */
+      if (!TYPE_STRUCTURAL_EQUALITY_P (type)
+         && TYPE_STRUCTURAL_EQUALITY_P (TREE_TYPE (type)))
+       return false;
+
+     default:
+       break;
+    }
+
+  switch (TREE_CODE (type))
+    {
+    case POINTER_TYPE:
+    case FUNCTION_TYPE:
+      /* Pointer and functions can not have variable size.  */
+      if (C_TYPE_VARIABLE_SIZE (type))
        return false;
       break;
     case ARRAY_TYPE:
@@ -407,11 +422,6 @@ c_verify_type (tree type)
          if (!C_TYPE_VARIABLY_MODIFIED (type))
            return false;
        }
-      /* If the element type is variably modified, then also the array.  */
-      if (C_TYPE_VARIABLY_MODIFIED (TREE_TYPE (type))
-         && !C_TYPE_VARIABLY_MODIFIED (type))
-       return false;
-      break;
     default:
       break;
     }
@@ -426,7 +436,7 @@ c_set_type_bits (tree new_type, tree old_type)
 {
   gcc_checking_assert (c_verify_type (old_type));
 
-  if (C_TYPE_VARIABLY_MODIFIED (old_type))
+  if (c_type_variably_modified_p (old_type))
     C_TYPE_VARIABLY_MODIFIED (new_type) = true;
 
   if (TREE_CODE (new_type) == ARRAY_TYPE && C_TYPE_VARIABLE_SIZE (old_type))
@@ -3785,7 +3795,7 @@ mark_decl_used (tree ref, bool address)
 
   /* Filter out the cases where referencing a non-local variable does not
      require a non-local context passed via the static chain.  */
-  if (!C_TYPE_VARIABLY_MODIFIED (TREE_TYPE (ref)))
+  if (!c_type_variably_modified_p (TREE_TYPE (ref)))
     switch (TREE_CODE (ref))
       {
       case FUNCTION_DECL:
@@ -4452,7 +4462,7 @@ build_function_call_vec (location_t loc, vec<location_t> arg_loc,
   /* In this improbable scenario, a nested function returns a VM type.
      Create a TARGET_EXPR so that the call always has a LHS, much as
      what the C++ FE does for functions returning non-PODs.  */
-  if (C_TYPE_VARIABLY_MODIFIED (TREE_TYPE (fntype)))
+  if (c_type_variably_modified_p (TREE_TYPE (fntype)))
     {
       tree tmp = create_tmp_var_raw (TREE_TYPE (fntype));
       result = build4 (TARGET_EXPR, TREE_TYPE (fntype), tmp, result,
@@ -18698,8 +18708,8 @@ c_build_qualified_type (tree type, int type_quals, tree orig_qual_type,
 
   gcc_checking_assert (C_TYPE_VARIABLE_SIZE (var_type)
                       == C_TYPE_VARIABLE_SIZE (type));
-  gcc_checking_assert (C_TYPE_VARIABLY_MODIFIED (var_type)
-                      == C_TYPE_VARIABLY_MODIFIED (type));
+  gcc_checking_assert (c_type_variably_modified_p (var_type)
+                      == c_type_variably_modified_p (type));
 
   /* A variant type does not inherit the list of incomplete vars from the
      type main variant.  */
diff --git a/gcc/testsuite/gcc.dg/gnu23-tag-composite-7.c b/gcc/testsuite/gcc.dg/gnu23-tag-composite-7.c
new file mode 100644 (file)
index 0000000..31fda0f
--- /dev/null
@@ -0,0 +1,73 @@
+/* { dg-do compile } */
+/* { dg-options "-std=gnu23" } */
+
+#define NEST(...) typeof(*({ __VA_ARGS__ tmp = { }; &tmp; }))
+
+int e(int n)
+{
+    struct foo { char buf[n]; } *p;
+    {
+           struct foo { char buf[n]; } *q;
+           1 ? p : q;
+    }
+}
+
+int f(int n)
+{
+    typedef struct foo bar;
+    struct foo { NEST(struct foo { bar *x; char buf[n]; }) *x; char buf[n]; } *q;
+    typeof(q->x) p0;
+    typeof(q->x) p1;
+    1 ? p0 : q;
+    1 ? p1 : q;
+    1 ? p0 : p1;
+}
+
+int g(int n)
+{
+    typedef struct fo2 bar;
+    struct fo2 { NEST(struct fo2 { NEST(struct fo2 { bar *x; char buf[n]; }) * x; char buf[n]; }) *x; char buf[n]; } *q;
+    typeof(q->x) p0;
+    typeof(q->x->x) p1;
+    typeof(q->x->x->x) p2;
+    1 ? p0 : q;
+    1 ? p1 : q;
+    1 ? p2 : q;
+    1 ? p0 : p1;
+    1 ? p2 : p1;
+    1 ? p0 : p2;
+}
+
+int h0(int n)
+{
+    typedef struct foo bar;
+    struct foo { NEST(struct foo { bar *x; char buf[3]; }) *x; char buf[n]; } *q;
+    typeof(q->x) p0;
+    typeof(q->x) p1;
+    1 ? p0 : q;
+    1 ? p1 : q;
+    1 ? p0 : p1;
+}
+
+int h1(int n)
+{
+    typedef struct foo bar;
+    struct foo { NEST(struct foo { bar *x; char buf[n]; }) *x; char buf[3]; } *q;
+    typeof(q->x) p0;
+    typeof(q->x) p1;
+    1 ? p0 : q;
+    1 ? p1 : q;
+    1 ? p0 : p1;
+}
+
+int h2(int n)
+{
+    typedef struct foo bar;
+    struct foo { NEST(struct foo { bar *x; char buf[n]; }) *x; char buf[ ]; } *q;
+    typeof(q->x) p0;
+    typeof(q->x) p1;
+    1 ? p0 : q;
+    1 ? p1 : q;
+    1 ? p0 : p1;
+}
+
diff --git a/gcc/testsuite/gcc.dg/gnu23-varmod-3.c b/gcc/testsuite/gcc.dg/gnu23-varmod-3.c
new file mode 100644 (file)
index 0000000..009867c
--- /dev/null
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-std=gnu23" } */
+
+void f1(int n)
+{
+    struct foo b();
+    struct foo {
+        char buf[n];
+    };
+    goto out;          /* { dg-error "jump into scope" } */
+    typeof(b) *t;
+out:
+}
+
+void f2(int n)
+{
+    struct foo *b[1];
+    struct foo {
+        char buf[n];
+    };
+    goto out;          /* { dg-error "jump into scope" } */
+    typeof(b) *t;
+out:
+}
+
+void f3(int n)
+{
+    struct foo (*b[1])();
+    struct foo {
+        char buf[n];
+    };
+    goto out;          /* { dg-error "jump into scope" } */
+    typeof(b) *t;
+out:
+}
+
+
+void f4(int n)
+{
+    const struct foo * const * const c;
+    struct foo {
+        char buf[n];
+    };
+    goto out;          /* { dg-error "jump into scope" } */
+    typeof(c) *t;
+out:
+}
+
diff --git a/gcc/testsuite/gcc.dg/pr123424.c b/gcc/testsuite/gcc.dg/pr123424.c
new file mode 100644 (file)
index 0000000..7f97df6
--- /dev/null
@@ -0,0 +1,9 @@
+/* { dg-do compile } */
+/* { dg-options "-std=c23" } */
+
+void bar(const struct S *x, int y) {   /* { dg-warning "not be visible" } */
+  const struct S {
+    int d[y];
+  } *a = x;
+};
+