c: Implement C23 rules for undefined static functions in _Generic

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.
This commit is contained in:
Joseph Myers
2025-10-08 23:12:11 +00:00
parent 50959e53e4
commit 921d6497ae
6 changed files with 176 additions and 23 deletions

View 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)

View 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;
};
/* 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 *);

View 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

View File

@@ -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));
}

View File

@@ -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));
}

View File

@@ -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);
}