diff mbox series

[C,v2] Fix for redeclared enumerator initialized with different type [PR115109]

Message ID 194e5cd68595c5c923b869be2d875ed0991b98f0.camel@tugraz.at
State New
Headers show
Series [C,v2] Fix for redeclared enumerator initialized with different type [PR115109] | expand

Commit Message

Martin Uecker May 19, 2024, 10:24 a.m. UTC
First version was flawed, as it used the wrong type.
Here is another iteration.

Bootstrapped and regression tested on x86_64.


    c23: Fix for redeclared enumerator initialized with different type [PR115109]
    
    c23 specifies that the type of a redeclared enumerator is the one of the
    previous declaration.  Convert initializers with different type accordingly
    and add -Woverflow warning.
    
    2024-05-18 Martin Uecker  <uecker@tugraz.at>
    
    PR c/115109
    
    gcc/c/
            * c-decl.cc (build_enumerator): When redeclaring an
            enumerator convert value to previous type.  For redeclared
            enumerators use underlying type for computing the next value.
    
    gcc/testsuite/
            * gcc.dg/pr115109.c: New test.
            * gcc.dg/c23-tag-enum-6.c: New test.
            * gcc.dg/c23-tag-enum-7.c: New test.

Comments

Joseph Myers May 20, 2024, 9:30 p.m. UTC | #1
On Sun, 19 May 2024, Martin Uecker wrote:

>     c23 specifies that the type of a redeclared enumerator is the one of the
>     previous declaration.  Convert initializers with different type accordingly
>     and add -Woverflow warning.

It doesn't make sense to use -Woverflow.  Either the value is the same (in 
which case it fits in the desired type), or it's different (and you should 
get the "conflicting redeclaration of enumerator" error or some equivalent 
error, whether or not the value in the redeclaration fits in the previous 
type).

Note that this includes both explicit values and values determined by 
adding 1 implicitly.  E.g.

  enum e { A = 0, B = UINT_MAX };
  enum e { B = UINT_MAX, A };

is not valid, because in the redefinition, A gets the value 1 greater than 
UINT_MAX (which is not representable in unsigned int) - there is *not* an 
addition in type unsigned int, or in type enum e.

The constraint violated is the general one "If an identifier has no 
linkage, there shall be no more than one declaration of the identifier (in 
a declarator or type specifier) with the same scope and in the same name 
space, except that: ... enumeration constants and tags may be redeclared 
as specified in 6.7.3.3 and 6.7.3.4, respectively." (where 6.7.3.3 says 
"Enumeration constants can be redefined in the same scope with the same 
value as part of a redeclaration of the same enumerated type." - as the 
redefinition is not with the same value, the "as specified in 6.7.3.3" is 
not satisfied and so the general constraint against redeclarations with no 
linkage applies).
Martin Uecker May 21, 2024, 5:40 a.m. UTC | #2
Am Montag, dem 20.05.2024 um 21:30 +0000 schrieb Joseph Myers:
> On Sun, 19 May 2024, Martin Uecker wrote:
> 
> >     c23 specifies that the type of a redeclared enumerator is the one of the
> >     previous declaration.  Convert initializers with different type accordingly
> >     and add -Woverflow warning.
> 
> It doesn't make sense to use -Woverflow.  Either the value is the same (in 
> which case it fits in the desired type), or it's different (and you should 
> get the "conflicting redeclaration of enumerator" error or some equivalent 
> error, whether or not the value in the redeclaration fits in the previous 
> type).
> 
> Note that this includes both explicit values and values determined by 
> adding 1 implicitly.  E.g.
> 
>   enum e { A = 0, B = UINT_MAX };
>   enum e { B = UINT_MAX, A };
> 
> is not valid, because in the redefinition, A gets the value 1 greater than 
> UINT_MAX (which is not representable in unsigned int) - there is *not* an 
> addition in type unsigned int, or in type enum e.
> 
> The constraint violated is the general one "If an identifier has no 
> linkage, there shall be no more than one declaration of the identifier (in 
> a declarator or type specifier) with the same scope and in the same name 
> space, except that: ... enumeration constants and tags may be redeclared 
> as specified in 6.7.3.3 and 6.7.3.4, respectively." (where 6.7.3.3 says 
> "Enumeration constants can be redefined in the same scope with the same 
> value as part of a redeclaration of the same enumerated type." - as the 
> redefinition is not with the same value, the "as specified in 6.7.3.3" is 
> not satisfied and so the general constraint against redeclarations with no 
> linkage applies).

This assumes that the value in question is the one of the initializer and not the
one after initialization (with no clear rules how this works in this case), 
which is probably not how this wording would be understood in other contexts.
But I agree that your interpretation is probably closer to what was intended
and makes more sense in this case.

Martin

>
Joseph Myers May 23, 2024, 8:51 p.m. UTC | #3
On Tue, 21 May 2024, Martin Uecker wrote:

> > The constraint violated is the general one "If an identifier has no 
> > linkage, there shall be no more than one declaration of the identifier (in 
> > a declarator or type specifier) with the same scope and in the same name 
> > space, except that: ... enumeration constants and tags may be redeclared 
> > as specified in 6.7.3.3 and 6.7.3.4, respectively." (where 6.7.3.3 says 
> > "Enumeration constants can be redefined in the same scope with the same 
> > value as part of a redeclaration of the same enumerated type." - as the 
> > redefinition is not with the same value, the "as specified in 6.7.3.3" is 
> > not satisfied and so the general constraint against redeclarations with no 
> > linkage applies).
> 
> This assumes that the value in question is the one of the initializer and not the
> one after initialization (with no clear rules how this works in this case), 

There are no initializers here.  The constant-expression after '=' in the 
syntax for enumerator is not an initializer, and none of the rules for 
initializers apply to it.  (If the initializer with an out-of-range value 
also gets used in an expression, the constraint on constants would be 
violated, "Each constant shall have a type and the value of a constant 
shall be in the range of representable values for its type." - but the 
constraint on multiple declarations applies whether it's used or not.)
diff mbox series

Patch

diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
index b691b91b3db..540927a8df6 100644
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -10209,6 +10209,7 @@  build_enumerator (location_t decl_loc, location_t loc,
 		  struct c_enum_contents *the_enum, tree name, tree value)
 {
   tree decl;
+  tree old_decl;
 
   /* Validate and default VALUE.  */
 
@@ -10268,6 +10269,24 @@  build_enumerator (location_t decl_loc, location_t loc,
 	 definition.  */
       value = convert (the_enum->enum_type, value);
     }
+  else if (flag_isoc23
+	   && (old_decl = lookup_name_in_scope (name, current_scope))
+	   && old_decl != error_mark_node
+	   && TREE_TYPE (old_decl)
+	   && TREE_TYPE (TREE_TYPE (old_decl))
+	   && TREE_CODE (old_decl) == CONST_DECL)
+    {
+      /* Enumeration constants in a redeclaration have the previous type.  */
+      tree previous_type = TREE_TYPE (DECL_INITIAL (old_decl));
+      if (!int_fits_type_p (value, previous_type))
+	{
+	  warning_at (loc, OPT_Woverflow,
+		      "value of redeclared enumerator outside the range of "
+		      "the previous type %qT", previous_type);
+	  locate_old_decl (old_decl);
+	}
+      value = convert (previous_type, value);
+    }
   else
     {
       /* Even though the underlying type of an enum is unspecified, the
@@ -10334,9 +10353,14 @@  build_enumerator (location_t decl_loc, location_t loc,
 			     false);
     }
   else
-    the_enum->enum_next_value
-      = build_binary_op (EXPR_LOC_OR_LOC (value, input_location),
-			 PLUS_EXPR, value, integer_one_node, false);
+    {
+      /* In a redeclaration the type can already be the enumeral type.  */
+      if (TREE_CODE (TREE_TYPE (value)) == ENUMERAL_TYPE)
+	value = convert (ENUM_UNDERLYING_TYPE (TREE_TYPE (value)), value);
+      the_enum->enum_next_value
+	= build_binary_op (EXPR_LOC_OR_LOC (value, input_location),
+			   PLUS_EXPR, value, integer_one_node, false);
+    }
   the_enum->enum_overflow = tree_int_cst_lt (the_enum->enum_next_value, value);
   if (the_enum->enum_overflow
       && !ENUM_FIXED_UNDERLYING_TYPE_P (the_enum->enum_type))
diff --git a/gcc/testsuite/gcc.dg/c23-tag-enum-6.c b/gcc/testsuite/gcc.dg/c23-tag-enum-6.c
new file mode 100644
index 00000000000..ff9ec89775e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c23-tag-enum-6.c
@@ -0,0 +1,14 @@ 
+/* { dg-do compile } */
+/* { dg-options "-std=c23" } */
+
+#include <limits.h>
+
+enum E : int { a = 1, b = 2 };
+enum E : int { b = _Generic(a, enum E: 2), a = 1 };
+
+enum H { x = 1 };
+enum H { x = 2UL + UINT_MAX };		/* { dg-warning "outside the range" } */
+
+enum K : int { z = 1 };
+enum K : int { z = 2UL + UINT_MAX };	/* { dg-error "outside the range" } */
+
diff --git a/gcc/testsuite/gcc.dg/c23-tag-enum-7.c b/gcc/testsuite/gcc.dg/c23-tag-enum-7.c
new file mode 100644
index 00000000000..4a5b4bc63f6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c23-tag-enum-7.c
@@ -0,0 +1,41 @@ 
+/* { dg-do compile }
+ * { dg-options "-std=c23" } */
+
+#include <limits.h>
+
+// enumerators are all representable in int
+enum E { a = 1UL, b = _Generic(a, int: 2) };
+static_assert(_Generic(a, int: 1));
+static_assert(_Generic(b, int: 1));
+enum E { a = 1UL, b = _Generic(a, int: 2) };
+static_assert(_Generic(a, int: 1));
+static_assert(_Generic(b, int: 1));
+
+// enumerators are not representable in int
+enum H { c = 1UL << (UINT_WIDTH + 1), d = 2 };
+static_assert(_Generic(c, enum H: 1));
+static_assert(_Generic(d, enum H: 1));
+enum H { c = 1UL << (UINT_WIDTH + 1), d = _Generic(c, enum H: 2) };
+static_assert(_Generic(c, enum H: 1));
+static_assert(_Generic(d, enum H: 1));
+
+// there is an overflow in the first redeclaration
+enum K { e = UINT_MAX, f, g = _Generic(e, unsigned int: 0) + _Generic(f, unsigned long: 1) };
+static_assert(_Generic(e, enum K: 1));
+static_assert(_Generic(f, enum K: 1));
+static_assert(_Generic(g, enum K: 1));
+enum K { e = UINT_MAX, f, g = _Generic(e, enum K: 0) + _Generic(f, enum K: 1) };
+static_assert(_Generic(e, enum K: 1));
+static_assert(_Generic(f, enum K: 1));
+static_assert(_Generic(g, enum K: 1));
+
+// there is an overflow in the first redeclaration
+enum U { k = INT_MAX, l, m = _Generic(k, int: 0) + _Generic(l, long: 1) };
+static_assert(_Generic(k, enum U: 1));
+static_assert(_Generic(l, enum U: 1));
+static_assert(_Generic(m, enum U: 1));
+enum U { k = INT_MAX, l, m = _Generic(k, enum U: 0) + _Generic(l, enum U: 1) };
+static_assert(_Generic(k, enum U: 1));
+static_assert(_Generic(l, enum U: 1));
+static_assert(_Generic(m, enum U: 1));
+
diff --git a/gcc/testsuite/gcc.dg/pr115109.c b/gcc/testsuite/gcc.dg/pr115109.c
new file mode 100644
index 00000000000..961197d0c71
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pr115109.c
@@ -0,0 +1,8 @@ 
+/* { dg-do compile } */
+/* { dg-options "-std=c23" } */
+
+#include <limits.h>
+
+enum E { a = 1UL << (ULONG_WIDTH - 5), b = 2 };
+enum E { a = 1ULL << (ULONG_WIDTH - 5), b = _Generic(a, enum E: 2) };
+