]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c: Implement C23 rules for undefined static functions in _Generic
authorJoseph Myers <josmyers@redhat.com>
Wed, 8 Oct 2025 23:12:11 +0000 (23:12 +0000)
committerJoseph Myers <josmyers@redhat.com>
Wed, 8 Oct 2025 23:12:11 +0000 (23:12 +0000)
A fairly late change in C23, the resolution of CD2 ballot comments
US-077 and US-078, added certain locations in _Generic to the
obviously unevaluated locations where it is permitted to have a
reference to a static function that is never defined.

Implement this feature in GCC.  The main complication is that, unlike
previous cases where it's known at the end of an operand to a
construct such as sizeof whether that operand is obviously unevaluated
and so an appropriate argument can be passed to pop_maybe_used, in the
case of a default generic association in _Generic it may not be known
until the end of that _Generic expression whether that case is
evaluated or not.  Thus, we arrange for the state of the
maybe_used_decls stack to be saved in this case and later restored
once the correct argument to pop_maybe_used is known.

There may well be further changes in this area in C2y (if the
"discarded" proposal is adopted, further locations will be OK for such
references to undefined static functions).  For now, only expressions
and not type names in _Generic have this special treatment.

Bootstrapped with no regressions for x86_64-pc-linux-gnu.

gcc/c/
* c-typeck.cc (in_generic, save_maybe_used, restore_maybe_used):
New.
(mark_decl_used, record_maybe_used_decl, pop_maybe_used): Use
in_generic.
(struct maybe_used_decl): Move to c-tree.h.
* c-tree.h (struct maybe_used_decl): Move from c-typeck.cc.
(in_generic, save_maybe_used, restore_maybe_used): Declare.
* c-parser.cc (c_parser_generic_selection): Increment and
decrement in_generic.  Use pop_maybe_used, save_maybe_used and
restore_maybe_used.

gcc/testsuite/
* gcc.dg/c11-generic-4.c, gcc.dg/c23-generic-5.c,
gcc.dg/c2y-generic-5.c: New tests.

gcc/c/c-parser.cc
gcc/c/c-tree.h
gcc/c/c-typeck.cc
gcc/testsuite/gcc.dg/c11-generic-4.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/c23-generic-5.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/c2y-generic-5.c [new file with mode: 0644]

index bc90be47151b4bf1daa3bd7dc6daaba83b3f7cae..ea0294f0738e0a707bcae0b972cfcafd3ade5a55 100644 (file)
@@ -11183,9 +11183,12 @@ c_parser_generic_selection (c_parser *parser)
   else
     {
       c_inhibit_evaluation_warnings++;
+      in_generic++;
       selector = c_parser_expr_no_commas (parser, NULL);
       selector = default_function_array_conversion (selector_loc, selector);
       c_inhibit_evaluation_warnings--;
+      in_generic--;
+      pop_maybe_used (!flag_isoc23);
 
       if (selector.value == error_mark_node)
        {
@@ -11214,6 +11217,7 @@ c_parser_generic_selection (c_parser *parser)
     }
 
   auto_vec<c_generic_association> associations;
+  struct maybe_used_decl *maybe_used_default = NULL;
   while (1)
     {
       struct c_generic_association assoc, *iter;
@@ -11269,11 +11273,19 @@ c_parser_generic_selection (c_parser *parser)
 
       if (!match)
        c_inhibit_evaluation_warnings++;
+      in_generic++;
 
       assoc.expression = c_parser_expr_no_commas (parser, NULL);
 
       if (!match)
          c_inhibit_evaluation_warnings--;
+      in_generic--;
+      if (!match)
+       pop_maybe_used (!flag_isoc23);
+      else if (assoc.type == NULL_TREE)
+       maybe_used_default = save_maybe_used ();
+      else
+       pop_maybe_used (true);
 
       if (assoc.expression.value == error_mark_node)
        {
@@ -11334,6 +11346,20 @@ c_parser_generic_selection (c_parser *parser)
       c_parser_consume_token (parser);
     }
 
+  if (match_found >= 0 && matched_assoc.type == NULL_TREE)
+    {
+      /* Declarations referenced in the default association are used.  */
+      restore_maybe_used (maybe_used_default);
+      pop_maybe_used (true);
+    }
+  else if (maybe_used_default)
+    {
+      /* Declarations referenced in the default association are not used, but
+        are treated as used before C23.  */
+      restore_maybe_used (maybe_used_default);
+      pop_maybe_used (!flag_isoc23);
+    }
+
   unsigned int ix;
   struct c_generic_association *iter;
   FOR_EACH_VEC_ELT (associations, ix, iter)
index a06565d1e8bc3e6f9a608834ef31c17b68ce7e84..162add0522acb2d30223edcd4e051e5e16574b85 100644 (file)
@@ -624,6 +624,19 @@ enum c_inline_static_type {
   csi_modifiable
 };
 
+/* Record details of decls possibly used inside sizeof or typeof.  */
+struct maybe_used_decl
+{
+  /* The decl.  */
+  tree decl;
+  /* The level seen at (in_sizeof + in_typeof + in_countof + in_generic).  */
+  int level;
+  /* Seen in address-of.  */
+  bool address;
+  /* The next one at this level or above, or NULL.  */
+  struct maybe_used_decl *next;
+};
+
 \f
 /* in c-parser.cc */
 struct c_tree_token_vec;
@@ -774,6 +787,7 @@ extern int in_alignof;
 extern int in_sizeof;
 extern int in_countof;
 extern int in_typeof;
+extern int in_generic;
 extern bool c_in_omp_for;
 extern bool c_omp_array_section_p;
 
@@ -833,6 +847,8 @@ extern tree build_array_ref (location_t, tree, tree);
 extern tree build_omp_array_section (location_t, tree, tree, tree);
 extern tree build_external_ref (location_t, tree, bool, tree *);
 extern void pop_maybe_used (bool);
+extern struct maybe_used_decl *save_maybe_used ();
+extern void restore_maybe_used (struct maybe_used_decl *);
 extern void mark_decl_used (tree, bool);
 extern struct c_expr c_expr_sizeof_expr (location_t, struct c_expr);
 extern struct c_expr c_expr_sizeof_type (location_t, struct c_type_name *);
index 9b2aeea50f4a16a4001f0865ec15cbc3ef6d3c2e..371583bd64ed8e6af1aa88d6c980b6f8227c1737 100644 (file)
@@ -78,6 +78,9 @@ int in_countof;
 /* The level of nesting inside "typeof".  */
 int in_typeof;
 
+/* The level of nesting inside "_Generic".  */
+int in_generic;
+
 /* True when parsing OpenMP loop expressions.  */
 bool c_in_omp_for;
 
@@ -3690,7 +3693,7 @@ mark_decl_used (tree ref, bool address)
     return;
 
   /* If we may be in an unevaluated context, delay the decision.  */
-  if (in_sizeof || in_typeof || in_countof)
+  if (in_sizeof || in_typeof || in_countof || in_generic)
     return record_maybe_used_decl (ref, address);
 
   if (static_p)
@@ -3842,46 +3845,36 @@ build_external_ref (location_t loc, tree id, bool fun, tree *type)
   return ref;
 }
 
-/* Record details of decls possibly used inside sizeof or typeof.  */
-struct maybe_used_decl
-{
-  /* The decl.  */
-  tree decl;
-  /* The level seen at (in_sizeof + in_typeof + in_countof).  */
-  int level;
-  /* Seen in address-of.  */
-  bool address;
-  /* The next one at this level or above, or NULL.  */
-  struct maybe_used_decl *next;
-};
-
 static struct maybe_used_decl *maybe_used_decls;
 
-/* Record that DECL, a reference seen inside sizeof or typeof, might be used
-   if the operand of sizeof is a VLA type or the operand of typeof is a variably
-   modified type.  */
+/* Record that DECL, a reference seen inside sizeof or typeof or _Countof or
+   _Generic, might be used if the operand of sizeof is a VLA type or the
+   operand of typeof is a variably modified type or the operand of _Countof has
+   a variable number of elements or the operand of _Generic is the one selected
+   as the result.  */
 
 static void
 record_maybe_used_decl (tree decl, bool address)
 {
   struct maybe_used_decl *t = XOBNEW (&parser_obstack, struct maybe_used_decl);
   t->decl = decl;
-  t->level = in_sizeof + in_typeof + in_countof;
+  t->level = in_sizeof + in_typeof + in_countof + in_generic;
   t->address = address;
   t->next = maybe_used_decls;
   maybe_used_decls = t;
 }
 
-/* Pop the stack of decls possibly used inside sizeof or typeof.  If
-   USED is false, just discard them.  If it is true, mark them used
-   (if no longer inside sizeof or typeof) or move them to the next
-   level up (if still inside sizeof or typeof).  */
+/* Pop the stack of decls possibly used inside sizeof or typeof or _Countof or
+   _Generic.  If USED is false, just discard them.  If it is true, mark them
+   used (if no longer inside sizeof or typeof or _Countof or _Generic) or move
+   them to the next level up (if still inside sizeof or typeof or _Countof or
+   _Generic).  */
 
 void
 pop_maybe_used (bool used)
 {
   struct maybe_used_decl *p = maybe_used_decls;
-  int cur_level = in_sizeof + in_typeof + in_countof;
+  int cur_level = in_sizeof + in_typeof + in_countof + in_generic;
   while (p && p->level > cur_level)
     {
       if (used)
@@ -3897,6 +3890,35 @@ pop_maybe_used (bool used)
     maybe_used_decls = p;
 }
 
+/* Pop the stack of decls possibly used inside sizeof or typeof or _Countof or
+   _Generic, without acting on them, and return the pointer to the previous top
+   of the stack.  This for use at the end of a default generic association when
+   it is not yet known whether the expression is used.  If it later turns out
+   the expression is used (or treated as used before C23), restore_maybe_used
+   should be called on the return value followed by pop_maybe_used (true);
+   otherwise, the return value can be discarded.  */
+
+struct maybe_used_decl *
+save_maybe_used ()
+{
+  struct maybe_used_decl *p = maybe_used_decls, *orig = p;
+  int cur_level = in_sizeof + in_typeof + in_countof + in_generic;
+  while (p && p->level > cur_level)
+    p = p->next;
+  maybe_used_decls = p;
+  return orig;
+}
+
+/* Restore the stack of decls possibly used inside sizeof or typeof or _Countof
+   or _Generic returned by save_maybe_used.  It is required that the stack is
+   at exactly the point where it was left by save_maybe_used.  */
+
+void
+restore_maybe_used (struct maybe_used_decl *stack)
+{
+  maybe_used_decls = stack;
+}
+
 /* Return the result of sizeof applied to EXPR.  */
 
 struct c_expr
diff --git a/gcc/testsuite/gcc.dg/c11-generic-4.c b/gcc/testsuite/gcc.dg/c11-generic-4.c
new file mode 100644 (file)
index 0000000..41309df
--- /dev/null
@@ -0,0 +1,38 @@
+/* Test references to never-defined static functions in _Generic: allowed in
+   certain places for C23 but not before.  */
+/* { dg-do compile } */
+/* { dg-options "-std=c11 -pedantic-errors" } */
+
+static int ok1_c23 (); /* { dg-error "used but never defined" } */
+static int ok2_c23 (); /* { dg-error "used but never defined" } */
+static int ok3_c23 (); /* { dg-error "used but never defined" } */
+static int ok4_c23 (); /* { dg-error "used but never defined" } */
+static int ok5_c23 (); /* { dg-error "used but never defined" } */
+static int ok6 ();
+static int ok7 ();
+static int ok8 ();
+static int ok9 ();
+static int ok10 ();
+static int ok11 ();
+static int ok12 ();
+static int not_ok1 (); /* { dg-error "used but never defined" } */
+static int not_ok2 (); /* { dg-error "used but never defined" } */
+
+void
+f ()
+{
+  _Generic (ok1_c23 (), int: 2);
+  _Generic (1, int: 2, default: ok2_c23 ());
+  _Generic (1, default: ok3_c23 (), int: 3);
+  _Generic (1, int: 2, float: ok4_c23 ());
+  _Generic (1, float: ok5_c23 (), int: 3);
+  sizeof (_Generic (ok8 (), int: 2));
+  sizeof (_Generic (1, int: 2, default: ok9 ()));
+  sizeof (_Generic (1, default: ok10 (), int: 3));
+  sizeof (_Generic (1, int: 2, float: ok11 ()));
+  sizeof (_Generic (1, float: ok12 (), int: 3));
+  _Generic (1.0, int: 2, default: not_ok1 ());
+  _Generic (1.0, default: not_ok2 (), int: 3);
+  sizeof (_Generic (1.0, int: 2, default: ok6 ()));
+  sizeof (_Generic (1.0, default: ok7 (), int: 3));
+}
diff --git a/gcc/testsuite/gcc.dg/c23-generic-5.c b/gcc/testsuite/gcc.dg/c23-generic-5.c
new file mode 100644 (file)
index 0000000..4603ec8
--- /dev/null
@@ -0,0 +1,38 @@
+/* Test references to never-defined static functions in _Generic: allowed in
+   certain places for C23 but not before.  */
+/* { dg-do compile } */
+/* { dg-options "-std=c23 -pedantic-errors" } */
+
+static int ok1_c23 ();
+static int ok2_c23 ();
+static int ok3_c23 ();
+static int ok4_c23 ();
+static int ok5_c23 ();
+static int ok6 ();
+static int ok7 ();
+static int ok8 ();
+static int ok9 ();
+static int ok10 ();
+static int ok11 ();
+static int ok12 ();
+static int not_ok1 (); /* { dg-error "used but never defined" } */
+static int not_ok2 (); /* { dg-error "used but never defined" } */
+
+void
+f ()
+{
+  _Generic (ok1_c23 (), int: 2);
+  _Generic (1, int: 2, default: ok2_c23 ());
+  _Generic (1, default: ok3_c23 (), int: 3);
+  _Generic (1, int: 2, float: ok4_c23 ());
+  _Generic (1, float: ok5_c23 (), int: 3);
+  sizeof (_Generic (ok8 (), int: 2));
+  sizeof (_Generic (1, int: 2, default: ok9 ()));
+  sizeof (_Generic (1, default: ok10 (), int: 3));
+  sizeof (_Generic (1, int: 2, float: ok11 ()));
+  sizeof (_Generic (1, float: ok12 (), int: 3));
+  _Generic (1.0, int: 2, default: not_ok1 ());
+  _Generic (1.0, default: not_ok2 (), int: 3);
+  sizeof (_Generic (1.0, int: 2, default: ok6 ()));
+  sizeof (_Generic (1.0, default: ok7 (), int: 3));
+}
diff --git a/gcc/testsuite/gcc.dg/c2y-generic-5.c b/gcc/testsuite/gcc.dg/c2y-generic-5.c
new file mode 100644 (file)
index 0000000..80097f0
--- /dev/null
@@ -0,0 +1,13 @@
+/* Test references to never-defined static functions in _Generic: still not
+   allowed in a type name used for selection, only an expression (may change if
+   "discarded" is adopted).  */
+/* { dg-do compile } */
+/* { dg-options "-std=c2y -pedantic-errors" } */
+
+static int not_ok1 (); /* { dg-error "used but never defined" } */
+
+void
+f ()
+{
+  _Generic (int (*)[not_ok1 ()], default: 1);
+}