Linux SPARSE checker discussions
 help / color / mirror / Atom feed
From: Bart Van Assche <bvanassche@acm.org>
To: linux-sparse@vger.kernel.org
Cc: Linus Torvalds <torvalds@linux-foundation.org>,
	Bart Van Assche <bvanassche@acm.org>
Subject: [PATCH 51/73] c23: Support enumerations with fixed underlying types
Date: Mon,  9 Nov 2026 12:32:37 -0800	[thread overview]
Message-ID: <20261109203259.362845-52-bvanassche@acm.org> (raw)
In-Reply-To: <20261109203259.362845-1-bvanassche@acm.org>

Implement ISO C23 enumerations with fixed underlying types
('enum [tag] : type { ... }' and standalone 'enum tag : type;'):
- Parse and canonicalize fixed integer underlying types, enforce
  redeclaration compatibility, and reject non-standalone opaque
  specifiers per C23 6.7.2.2p5.
- Disambiguate fixed-type ': type' from unnamed enum bit-fields and
  _Generic 'enum tag: expr' associations.
- Type enumerators per C23 6.7.2.2p12-13 in C23 mode (giving fixed-enum
  members the underlying type during and after the brace list, and giving
  unfixed-enum members 'int' only when the entire enum fits in 'int'),
  while preserving per-enumerator GNU typing in pre-C23 modes.
- Support bool-backed enums and diagnose ULLONG_MAX auto-increment
  overflow before wraparound.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 evaluate.c                       |   2 +-
 expand.c                         |  13 +-
 parse.c                          | 269 +++++++++++++++++++++++++------
 symbol.c                         |  19 ++-
 symbol.h                         |  38 ++---
 validation/c23-enum-type-cast.c  |  70 ++++++++
 validation/c23-enum-type-error.c |  40 +++++
 validation/c23-enum-type.c       | 116 +++++++++++++
 8 files changed, 494 insertions(+), 73 deletions(-)
 create mode 100644 validation/c23-enum-type-cast.c
 create mode 100644 validation/c23-enum-type-error.c
 create mode 100644 validation/c23-enum-type.c

diff --git a/evaluate.c b/evaluate.c
index b7e352f55e6c..43ad5f59b860 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -2486,7 +2486,7 @@ static struct symbol *evaluate_sizeof(struct expression *expr)
 	}
 
 	if (is_bool_type(type)) {
-		if (Wsizeof_bool)
+		if (Wsizeof_bool && cbase_type(type) == &bool_ctype)
 			warning(expr->pos, "expression using sizeof _Bool");
 		size = bits_to_bytes(bits_in_bool) * bits_in_char;
 	}
diff --git a/expand.c b/expand.c
index 73b37b93534e..13a3bc0457ba 100644
--- a/expand.c
+++ b/expand.c
@@ -106,12 +106,23 @@ void cast_value(struct expression *expr, struct symbol *newtype, struct expressi
 	// For pointers and integers, we can just move the value around
 	expr->type = EXPR_VALUE;
 	expr->taint = old->taint;
-	if (old_size == new_size) {
+	if (old_size == new_size && !is_bool_type(newtype)) {
 		expr->value = old->value;
 		expr->ctype = newtype;
 		return;
 	}
 
+	if (is_bool_type(newtype)) {
+		bool non_zero = old->value != 0;
+		bool is_one = old->value == 1;
+		expr->ctype = newtype;
+		expr->value = non_zero;
+		if (!conservative && non_zero && !is_one)
+			warning(old->pos, "odd constant _Bool cast (%llx becomes 1)",
+				old->value);
+		return;
+	}
+
 	// expand it to the full "long long" value
 	value = get_longlong(old);
 	expr->ctype = newtype;
diff --git a/parse.c b/parse.c
index a7d31fad1a10..48793dd51671 100644
--- a/parse.c
+++ b/parse.c
@@ -50,6 +50,7 @@ struct statement_list *function_computed_goto_list;
 
 static struct token *statement(struct token *token, struct statement **tree);
 static struct token *handle_attributes(struct token *token, struct decl_state *ctx);
+static struct token *skip_attributes(struct token *token);
 
 typedef struct token *declarator_t(struct token *, struct symbol *, struct decl_state *);
 static declarator_t
@@ -765,6 +766,32 @@ struct symbol *label_symbol(struct token *token, int used)
 	return sym;
 }
 
+static struct symbol *canonical_enum_underlying_type(struct symbol *sym)
+{
+	sym = cbase_type(sym);
+	if (sym == &sshort_ctype)
+		return &short_ctype;
+	if (sym == &sint_ctype)
+		return &int_ctype;
+	if (sym == &slong_ctype)
+		return &long_ctype;
+	if (sym == &sllong_ctype)
+		return &llong_ctype;
+	if (sym == &sint128_ctype)
+		return &int128_ctype;
+	return sym;
+}
+
+static bool has_enum_type_specifier(struct token *token)
+{
+	struct token *next;
+
+	if (!match_op(token, ':'))
+		return false;
+	next = skip_attributes(token->next);
+	return lookup_type(next) != 0;
+}
+
 static struct token *struct_union_enum_specifier(enum type type,
 	struct token *token, struct decl_state *ctx,
 	struct token *(*parse)(struct token *, struct symbol *))
@@ -778,7 +805,10 @@ static struct token *struct_union_enum_specifier(enum type type,
 		sym = lookup_symbol(token->ident, NS_STRUCT);
 		if (!sym ||
 		    (is_outer_scope(sym->scope) &&
-		     (match_op(token->next,';') || match_op(token->next,'{')))) {
+		     (match_op(token->next, ';') ||
+		      match_op(token->next, '{') ||
+		      (type == SYM_ENUM &&
+		       has_enum_type_specifier(token->next))))) {
 			// Either a new symbol, or else an out-of-scope
 			// symbol being redefined.
 			sym = alloc_symbol(token->pos, type);
@@ -789,6 +819,60 @@ static struct token *struct_union_enum_specifier(enum type type,
 		ctx->ctype.base_type = sym;
 		repos = &token->pos;
 		token = token->next;
+
+		if (type == SYM_ENUM && has_enum_type_specifier(token)) {
+			struct position colon_pos = token->pos;
+			struct symbol *underlying = NULL;
+			if (standard < STANDARD_C23 && Wpedantic)
+				warning(token->pos,
+					"ISO C does not support specifying 'enum' underlying types before C23");
+			token = typename(token->next, &underlying, NULL);
+			underlying = examine_symbol_type(underlying);
+			if (!underlying || !is_int_type(underlying) ||
+			    is_enum_type(underlying)) {
+				sparse_error(token->pos,
+					     "invalid 'enum' underlying type");
+				underlying = &int_ctype;
+			}
+			underlying = canonical_enum_underlying_type(underlying);
+			if (sym->fixed_type) {
+				if (canonical_enum_underlying_type(sym->ctype.base_type) !=
+				    underlying)
+					sparse_error(
+						token->pos,
+						"'enum' underlying type incompatible with previous declaration");
+			} else if (sym->examined || sym->symbol_list ||
+				   (sym->ctype.base_type ==
+				    &incomplete_ctype)) {
+				sparse_error(
+					token->pos,
+					"'enum' declared both with and without fixed underlying type");
+			} else {
+				sym->fixed_type = 1;
+				sym->ctype.base_type = underlying;
+				sym->ctype.modifiers |=
+					(underlying->ctype.modifiers &
+					 MOD_UNSIGNED);
+				if (underlying == &bool_ctype)
+					sym->bit_size = bits_to_bytes(bits_in_bool) * bits_in_char;
+				else
+					sym->bit_size = underlying->bit_size;
+				sym->ctype.alignment =
+					underlying->ctype.alignment;
+				sym->rank = underlying->rank;
+			}
+			token = handle_attributes(token, &attr);
+			if (!match_op(token, '{')) {
+				ctx->opaque_fixed_enum = 1;
+				ctx->opaque_fixed_enum_pos = colon_pos;
+			}
+		} else if (type == SYM_ENUM && sym->fixed_type &&
+			   match_op(token, '{')) {
+			sparse_error(
+				token->pos,
+				"'enum' declared with but defined without fixed underlying type");
+		}
+
 		if (!match_op(token, '{')) {
 			if (attr.designated_init) {
 				if (sym->type == SYM_STRUCT)
@@ -814,11 +898,44 @@ static struct token *struct_union_enum_specifier(enum type type,
 
 		// Mark the structure as needing re-examination
 		sym->examined = 0;
-	} else if (match_op(token, '{')) {
+	} else if (match_op(token, '{') ||
+		   (type == SYM_ENUM && match_op(token, ':'))) {
 		// private struct/union/enum type
 		sym = alloc_symbol(token->pos, type);
 		set_current_scope(sym);		// used by dissect
 		ctx->ctype.base_type = sym;
+		if (type == SYM_ENUM && match_op(token, ':')) {
+			struct symbol *underlying = NULL;
+			if (standard < STANDARD_C23 && Wpedantic)
+				warning(token->pos,
+					"ISO C does not support specifying 'enum' underlying types before C23");
+			token = typename(token->next, &underlying, NULL);
+			underlying = examine_symbol_type(underlying);
+			if (!underlying || !is_int_type(underlying) ||
+			    is_enum_type(underlying)) {
+				sparse_error(token->pos,
+					     "invalid 'enum' underlying type");
+				underlying = &int_ctype;
+			}
+			underlying = canonical_enum_underlying_type(underlying);
+			sym->fixed_type = 1;
+			sym->ctype.base_type = underlying;
+			sym->ctype.modifiers |=
+				(underlying->ctype.modifiers & MOD_UNSIGNED);
+			if (underlying == &bool_ctype)
+				sym->bit_size = bits_to_bytes(bits_in_bool) * bits_in_char;
+			else
+				sym->bit_size = underlying->bit_size;
+			sym->ctype.alignment = underlying->ctype.alignment;
+			sym->rank = underlying->rank;
+			token = handle_attributes(token, &attr);
+			if (!match_op(token, '{')) {
+				sparse_error(
+					token->pos,
+					"expected '{' after enum type specifier");
+				return token;
+			}
+		}
 	} else {
 		sparse_error(token->pos, "expected declaration");
 		ctx->ctype.base_type = &bad_ctype;
@@ -941,8 +1058,8 @@ static struct range type_range(struct symbol *type)
 	long long min;
 
 	if (is_signed_type(type)) {
-		min = sign_bit(size);
-		max = min - 1;
+		min = sign_extend(sign_bit(size), size);
+		max = sign_mask(size);
 	} else {
 		min = 0;
 		max = bits_mask(size);
@@ -963,24 +1080,37 @@ static int val_in_range(struct range *range, long long sval, struct symbol *vtyp
 		return uval <= range->pos;
 }
 
-static void cast_enum_list(struct symbol_list *list, struct symbol *base_type)
+static void cast_enum_list(struct symbol_list *list, struct symbol *base_type,
+			   struct range range)
 {
 	struct range irange = type_range(&int_ctype);
+	struct symbol *target = base_type;
 	struct symbol *sym;
 
+	if (is_int_type(base_type) && range.neg >= irange.neg &&
+	    range.pos <= irange.pos)
+		target = &int_ctype;
+
 	FOR_EACH_PTR(list, sym) {
 		struct expression *expr = sym->initializer;
 		struct symbol *ctype;
-		long long val;
+		struct symbol *etype = target;
+
 		if (expr->type != EXPR_VALUE)
 			continue;
 		ctype = expr->ctype;
-		val = get_expression_value(expr);
-		if (is_int_type(ctype) && val_in_range(&irange, val, ctype)) {
-			expr->ctype = &int_ctype;
-			continue;
+		if (standard < STANDARD_C23 && etype != &int_ctype &&
+		    is_int_type(ctype)) {
+			long long val = get_expression_value(expr);
+
+			if (!(is_signed_type(ctype) && val < 0) &&
+			    (unsigned long long)val <= irange.pos)
+				etype = &int_ctype;
 		}
-		cast_value(expr, base_type, expr);
+		if (!is_int_type(ctype) && etype == &int_ctype)
+			continue;
+		cast_value(expr, etype, expr);
+		expr->ctype = etype;
 	} END_FOR_EACH_PTR(sym);
 }
 
@@ -992,13 +1122,16 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
 	int mix_bitwise = 0;
 
 	parent->examined = 1;
-	parent->ctype.base_type = &int_ctype;
+	if (parent->fixed_type)
+		base_type = parent->ctype.base_type;
+	else
+		parent->ctype.base_type = &int_ctype;
 	while (token_type(token) == TOKEN_IDENT) {
 		struct expression *expr = NULL;
 		struct token *next = token->next;
 		struct decl_state ctx = { };
 		struct symbol *sym;
-		bool overflow = false;
+		bool overflowed = false;
 
 		// FIXME: only 'deprecated' should be accepted
 		next = handle_attributes(next, &ctx);
@@ -1016,7 +1149,7 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
 				ctype = &int_ctype;
 			}
 		} else if (!ctype) {
-			ctype = &int_ctype;
+			ctype = parent->fixed_type ? base_type : &int_ctype;
 		} else if (is_int_type(ctype)) {
 			struct range crange = type_range(ctype);
 			if (is_signed_type(ctype) && (long long)lastval == crange.pos) {
@@ -1038,13 +1171,20 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
 				else if (ctype->bit_size < bits_in_longlong)
 					ctype = &ullong_ctype;
 				else {
-					sparse_error(token->pos,
-						"enumerator value for '%s' overflows",
-						show_ident(token->ident));
-					overflow = true;
+					if (parent->fixed_type)
+						sparse_error(
+							token->pos,
+							"enumerator value outside the range of underlying type");
+					else
+						sparse_error(
+							token->pos,
+							"enumerator value for '%s' overflows",
+							show_ident(token->ident));
+					ctype = &ullong_ctype;
+					overflowed = true;
 				}
 			}
-			if (!overflow)
+			if (!overflowed)
 				lastval++;
 		} else {
 			error_die(token->pos, "can't increment the last enum member");
@@ -1056,6 +1196,16 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
 			expr->ctype = ctype;
 		}
 
+		if (parent->fixed_type) {
+			struct range urange = type_range(base_type);
+			if (!overflowed && !val_in_range(&urange, lastval, ctype))
+				sparse_error(
+					token->pos,
+					"enumerator value outside the range of underlying type");
+			cast_value(expr, base_type, expr);
+			ctype = base_type;
+		}
+
 		sym = alloc_symbol(token->pos, SYM_NODE);
 		bind_symbol(sym, token->ident, NS_SYMBOL);
 		sym->ctype.modifiers &= ~MOD_ADDRESSABLE;
@@ -1064,7 +1214,7 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
 		sym->ctype.base_type = parent;
 		add_ptr_list(&parent->symbol_list, sym);
 
-		if (base_type != &bad_ctype) {
+		if (!parent->fixed_type && base_type != &bad_ctype) {
 			if (ctype->type == SYM_NODE)
 				ctype = ctype->ctype.base_type;
 			if (ctype->type == SYM_ENUM) {
@@ -1104,7 +1254,7 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
 			}
 			parent->ctype.base_type = base_type;
 		}
-		if (is_int_type(base_type)) {
+		if (!parent->fixed_type && is_int_type(base_type)) {
 			update_range(&range, lastval, ctype);
 		}
 		token = next;
@@ -1115,33 +1265,40 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
 			break;
 		token = token->next;
 	}
-	if (!base_type) {
-		sparse_error(token->pos, "empty enum definition");
-		base_type = &bad_ctype;
-	}
-	else if (!is_int_type(base_type))
-		;
-	else if (type_is_ok(&uint_ctype, range))
-		base_type = &uint_ctype;
-	else if (type_is_ok(&int_ctype, range))
-		base_type = &int_ctype;
-	else if (type_is_ok(&ulong_ctype, range))
-		base_type = &ulong_ctype;
-	else if (type_is_ok(&long_ctype, range))
-		base_type = &long_ctype;
-	else if (type_is_ok(&ullong_ctype, range))
-		base_type = &ullong_ctype;
-	else if (type_is_ok(&llong_ctype, range))
-		base_type = &llong_ctype;
-	else
-		base_type = &bad_ctype;
-	parent->ctype.base_type = base_type;
-	parent->ctype.modifiers |= (base_type->ctype.modifiers & MOD_UNSIGNED);
-	parent->examined = 0;
+	if (!parent->fixed_type) {
+		if (!base_type) {
+			sparse_error(token->pos, "empty enum definition");
+			base_type = &bad_ctype;
+		} else if (!is_int_type(base_type))
+			;
+		else if (type_is_ok(&uint_ctype, range))
+			base_type = &uint_ctype;
+		else if (type_is_ok(&int_ctype, range))
+			base_type = &int_ctype;
+		else if (type_is_ok(&ulong_ctype, range))
+			base_type = &ulong_ctype;
+		else if (type_is_ok(&long_ctype, range))
+			base_type = &long_ctype;
+		else if (type_is_ok(&ullong_ctype, range))
+			base_type = &ullong_ctype;
+		else if (type_is_ok(&llong_ctype, range))
+			base_type = &llong_ctype;
+		else
+			base_type = &bad_ctype;
+		parent->ctype.base_type = base_type;
+		parent->ctype.modifiers |=
+			(base_type->ctype.modifiers & MOD_UNSIGNED);
+		parent->examined = 0;
 
-	if (mix_bitwise)
-		return token;
-	cast_enum_list(parent->symbol_list, base_type);
+		if (mix_bitwise)
+			return token;
+		cast_enum_list(parent->symbol_list, base_type, range);
+	} else {
+		if (!parent->symbol_list) {
+			sparse_error(token->pos, "empty enum definition");
+		}
+		parent->examined = 0;
+	}
 
 	return token;
 }
@@ -2130,6 +2287,9 @@ static struct token *declaration_list(struct token *token, struct symbol_list **
 	unsigned long mod;
 
 	token = declaration_specifiers(token, &ctx);
+	if (ctx.opaque_fixed_enum)
+		sparse_error(ctx.opaque_fixed_enum_pos,
+			     "'enum' underlying type may not be specified here");
 	mod = decl_modifiers(&ctx);
 	saved = ctx.ctype;
 	for (;;) {
@@ -2200,6 +2360,9 @@ static struct token *parameter_declaration(struct token *token, struct symbol *s
 	struct decl_state ctx = {.prefer_abstract = 1};
 
 	token = declaration_specifiers(token, &ctx);
+	if (ctx.opaque_fixed_enum)
+		sparse_error(ctx.opaque_fixed_enum_pos,
+			     "'enum' underlying type may not be specified here");
 	ctx.ident = &sym->ident;
 	token = declarator(token, &ctx);
 	token = handle_attributes(token, &ctx);
@@ -2240,6 +2403,9 @@ struct token *typename(struct token *token, struct symbol **p, int *forced)
 	token = declaration_specifiers(token, &ctx);
 	if (ctx.has_alignas)
 		sparse_error(sym->pos, "alignment specified for type name");
+	if (ctx.opaque_fixed_enum)
+		sparse_error(ctx.opaque_fixed_enum_pos,
+			     "'enum' underlying type may not be specified here");
 	if (ctx.autotype) {
 		sparse_error(sym->pos, "'%s' in type name",
 			     ctx.has_auto ? "auto" : "__auto_type");
@@ -3309,6 +3475,12 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 	decl = alloc_symbol(token->pos, SYM_NODE);
 	/* Just a type declaration? */
 	if (match_op(token, ';')) {
+		if (ctx.opaque_fixed_enum &&
+		    (ctx.storage_class || (mod & ~MOD_UNUSED) ||
+		     ctx.ctype.modifiers || ctx.has_auto || ctx.is_constexpr))
+			sparse_error(
+				ctx.opaque_fixed_enum_pos,
+				"'enum' underlying type may not be specified here");
 		if (ctx.autotype)
 			sparse_error(token->pos, "%s in empty declaration",
 				     ctx.has_auto ? "auto" : "__auto_type");
@@ -3318,6 +3490,9 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		apply_modifiers(token->pos, &ctx);
 		return token->next;
 	}
+	if (ctx.opaque_fixed_enum)
+		sparse_error(ctx.opaque_fixed_enum_pos,
+			     "'enum' underlying type may not be specified here");
 
 	saved = ctx.ctype;
 	token = declarator(token, &ctx);
diff --git a/symbol.c b/symbol.c
index fc11e1bbcec3..28fa12352569 100644
--- a/symbol.c
+++ b/symbol.c
@@ -584,12 +584,21 @@ static struct symbol *examine_enum_type(struct symbol *sym)
 	struct symbol *base_type = examine_base_type(sym);
 
 	sym->ctype.modifiers |= (base_type->ctype.modifiers & MOD_SIGNEDNESS);
-	sym->bit_size = bits_in_enum;
-	if (base_type->bit_size > sym->bit_size)
-		sym->bit_size = base_type->bit_size;
-	sym->ctype.alignment = enum_alignment;
-	if (base_type->ctype.alignment > sym->ctype.alignment)
+	if (sym->fixed_type) {
+		if (base_type == &bool_ctype)
+			sym->bit_size = bits_to_bytes(bits_in_bool) * bits_in_char;
+		else
+			sym->bit_size = base_type->bit_size;
 		sym->ctype.alignment = base_type->ctype.alignment;
+	} else {
+		sym->bit_size = bits_in_enum;
+		if (base_type->bit_size > sym->bit_size)
+			sym->bit_size = base_type->bit_size;
+		sym->ctype.alignment = enum_alignment;
+		if (base_type->ctype.alignment > sym->ctype.alignment)
+			sym->ctype.alignment = base_type->ctype.alignment;
+	}
+	sym->rank = base_type->rank;
 	return sym;
 }
 
diff --git a/symbol.h b/symbol.h
index f8556fdf3b86..8035d9d21b3c 100644
--- a/symbol.h
+++ b/symbol.h
@@ -120,6 +120,8 @@ struct decl_state {
 	unsigned char is_constexpr;
 	unsigned char designated_init;
 	unsigned char transparent_union;
+	unsigned char opaque_fixed_enum;
+	struct position opaque_fixed_enum_pos;
 };
 
 struct pseudo;
@@ -186,22 +188,14 @@ struct symbol {
 		struct /* NS_SYMBOL */ {
 			unsigned long	offset;
 			int		bit_size;
-			unsigned int	bit_offset:8,
-					bogus_linear:1,
-					variadic:1,
-					initialized:1,
-					examined:1,
-					expanding:1,
-					evaluated:1,
-					has_flex_array:1,
-					string:1,
-					designated_init:1,
-					forced_arg:1,
-					accessed:1,
-					builtin:1,
-					torename:1,
-					packed:1,
-					transparent_union:1;
+			unsigned int bit_offset : 8, bogus_linear : 1,
+				variadic : 1, initialized : 1, examined : 1,
+				expanding : 1, evaluated : 1,
+				has_flex_array : 1, string : 1,
+				designated_init : 1, forced_arg : 1,
+				accessed : 1, builtin : 1, torename : 1,
+				packed : 1, transparent_union : 1,
+				fixed_type : 1;
 			unsigned int has_alignas : 1;
 			unsigned int underspecified : 1;
 			unsigned int c23_auto : 1;
@@ -435,6 +429,13 @@ static inline int is_func_type(struct symbol *type)
 	return type->type == SYM_FN;
 }
 
+static inline struct symbol *cbase_type(struct symbol *sym)
+{
+	while (sym && sym->type == SYM_NODE)
+		sym = sym->ctype.base_type;
+	return sym;
+}
+
 static inline int is_array_type(struct symbol *type)
 {
 	if (type->type == SYM_NODE)
@@ -484,9 +485,8 @@ static inline int is_void_type(struct symbol *type)
 
 static inline int is_bool_type(struct symbol *type)
 {
-	if (type->type == SYM_NODE)
-		type = type->ctype.base_type;
-	if (type->type == SYM_BITFIELD)
+	while (type && (type->type == SYM_NODE || type->type == SYM_ENUM ||
+			type->type == SYM_BITFIELD))
 		type = type->ctype.base_type;
 	return type == &bool_ctype;
 }
diff --git a/validation/c23-enum-type-cast.c b/validation/c23-enum-type-cast.c
new file mode 100644
index 000000000000..447a9882ca3e
--- /dev/null
+++ b/validation/c23-enum-type-cast.c
@@ -0,0 +1,70 @@
+/*
+ * Check enumerator types and explicit/implicit casts involving enums
+ * with fixed underlying types.
+ */
+
+enum E1 : unsigned long long {
+	A = 0,
+	B = 1ULL << 40,
+};
+
+_Static_assert(_Generic(A, unsigned long long: 1, default: 0) == 1, "A type");
+_Static_assert(_Generic(B, unsigned long long: 1, default: 0) == 1, "B type");
+_Static_assert(sizeof(A) == sizeof(unsigned long long), "A size");
+_Static_assert(sizeof(B) == sizeof(unsigned long long), "B size");
+_Static_assert(A == 0, "A value");
+_Static_assert(B == (1ULL << 40), "B value");
+_Static_assert((enum E1)(1ULL << 40) == B, "cast to E1");
+_Static_assert((unsigned long long)B == (1ULL << 40), "cast from B");
+_Static_assert((short)A == 0, "cast A to short");
+_Static_assert((unsigned short)B == 0, "narrow cast B to ushort");
+
+enum E2 : long long {
+	C = -1LL,
+	D = 1LL << 40,
+};
+
+_Static_assert(_Generic(C, long long: 1, default: 0) == 1, "C type");
+_Static_assert(_Generic(D, long long: 1, default: 0) == 1, "D type");
+_Static_assert(sizeof(C) == sizeof(long long), "C size");
+_Static_assert(sizeof(D) == sizeof(long long), "D size");
+_Static_assert((enum E2)-1 == C, "cast -1 to E2");
+
+enum U8 : unsigned char {
+	U8_ZERO = 0,
+};
+
+_Static_assert((enum U8)200 == 200, "narrow u8 cast");
+_Static_assert((enum U8)258 == 2, "out-of-range u8 cast wraps");
+_Static_assert((enum U8)-1 == 255, "negative u8 cast wraps");
+_Static_assert(_Generic((enum U8)200, enum U8: 1, int: 0, default: 0) == 1,
+	       "cast yields enum U8");
+_Static_assert(_Generic((enum U8)200, unsigned char: 1, int: 0, default: 0) == 1,
+	       "enum U8 compatible with unsigned char");
+
+enum S8 : signed char {
+	S8_NEG = -1,
+};
+
+_Static_assert((enum S8)-128 == -128, "narrow s8 cast");
+_Static_assert(_Generic((enum S8)0, signed char: 1, int: 0, default: 0) == 1,
+	       "enum S8 compatible with signed char");
+
+enum B : bool {
+	B_FALSE = false,
+	B_TRUE = true,
+};
+
+_Static_assert((enum B)2 == 1, "bool enum cast from 2");
+_Static_assert((enum B)-5 == 1, "bool enum cast from -5");
+_Static_assert((enum B)0 == 0, "bool enum cast from 0");
+
+/*
+ * check-name: C23 enum underlying type casting
+ * check-command: sparse -std=c23 $file
+ *
+ * check-error-start
+c23-enum-type-cast.c:20:32: warning: cast truncates bits from constant value (10000000000 becomes 0)
+c23-enum-type-cast.c:38:25: warning: cast truncates bits from constant value (102 becomes 2)
+ * check-error-end
+ */
diff --git a/validation/c23-enum-type-error.c b/validation/c23-enum-type-error.c
new file mode 100644
index 000000000000..3d2a9d5ec6a4
--- /dev/null
+++ b/validation/c23-enum-type-error.c
@@ -0,0 +1,40 @@
+enum E1 : char { A = 300 };
+enum E2 : unsigned char { B = -1 };
+enum E3 : unsigned char { C = 255, D };
+enum E4 : short;
+enum E4 : int;
+enum E5;
+enum E5 : short;
+enum E6 : short;
+enum E6 { F };
+enum E7 : float;
+enum E8 : bool { B0 = 0, B1, B2 };
+enum E9 : unsigned long long { UMAX = ~0ULL, UOVER };
+enum E10 : int bad_var;
+typedef enum E11 : short e11_t;
+void bad_enum_param(enum E12 : int p);
+int bad_enum_sizeof = sizeof(enum E13 : int);
+enum E14 : char;
+enum E14 : signed char;
+
+/*
+ * check-name: C23 enum with fixed underlying type errors
+ * check-command: sparse -std=c23 $file
+ *
+ * check-error-start
+c23-enum-type-error.c:1:18: error: enumerator value outside the range of underlying type
+c23-enum-type-error.c:2:27: error: enumerator value outside the range of underlying type
+c23-enum-type-error.c:3:36: error: enumerator value outside the range of underlying type
+c23-enum-type-error.c:5:14: error: 'enum' underlying type incompatible with previous declaration
+c23-enum-type-error.c:7:16: error: 'enum' declared both with and without fixed underlying type
+c23-enum-type-error.c:9:9: error: 'enum' declared with but defined without fixed underlying type
+c23-enum-type-error.c:10:16: error: invalid 'enum' underlying type
+c23-enum-type-error.c:11:30: error: enumerator value outside the range of underlying type
+c23-enum-type-error.c:12:46: error: enumerator value outside the range of underlying type
+c23-enum-type-error.c:13:10: error: 'enum' underlying type may not be specified here
+c23-enum-type-error.c:14:18: error: 'enum' underlying type may not be specified here
+c23-enum-type-error.c:15:30: error: 'enum' underlying type may not be specified here
+c23-enum-type-error.c:16:39: error: 'enum' underlying type may not be specified here
+c23-enum-type-error.c:18:23: error: 'enum' underlying type incompatible with previous declaration
+ * check-error-end
+ */
diff --git a/validation/c23-enum-type.c b/validation/c23-enum-type.c
new file mode 100644
index 000000000000..03571bc99e06
--- /dev/null
+++ b/validation/c23-enum-type.c
@@ -0,0 +1,116 @@
+enum E_char : char {
+	A = 0,
+	B,
+};
+static_assert(sizeof(enum E_char) == sizeof(char), "sizeof E_char");
+static_assert(sizeof(A) == sizeof(char), "sizeof A");
+static_assert(A == 0, "val A");
+static_assert(B == 1, "val B");
+
+enum E_short : short;
+static_assert(sizeof(enum E_short) == sizeof(short), "sizeof E_short fwd");
+enum E_short : short {
+	C = 10,
+	D,
+};
+static_assert(sizeof(enum E_short) == sizeof(short), "sizeof E_short def");
+static_assert(sizeof(C) == sizeof(short), "sizeof C");
+static_assert(C == 10, "val C");
+static_assert(D == 11, "val D");
+
+enum : unsigned long long {
+	ULL1 = 0x100000000ULL,
+	ULL2,
+};
+static_assert(sizeof(ULL1) == sizeof(unsigned long long), "sizeof ULL1");
+static_assert(ULL1 == 0x100000000ULL, "val ULL1");
+static_assert(ULL2 == 0x100000001ULL, "val ULL2");
+
+enum E_redecl : int;
+enum E_redecl : signed int;
+enum E_redecl : int {
+	R1 = 100,
+	R2 = 200,
+};
+static_assert(sizeof(enum E_redecl) == sizeof(int), "sizeof E_redecl");
+static_assert(R1 == 100, "val R1");
+static_assert(R2 == 200, "val R2");
+
+enum E_compat : signed short;
+enum E_compat : short {
+	EC1 = -10,
+};
+static_assert(sizeof(enum E_compat) == sizeof(short), "sizeof E_compat");
+static_assert(EC1 == -10, "val EC1");
+
+enum E_signed : signed char {
+	NEG = -5,
+	NEG_PLUS_ONE,
+};
+static_assert(sizeof(enum E_signed) == 1, "sizeof E_signed");
+static_assert(NEG == -5, "val NEG");
+static_assert(NEG_PLUS_ONE == -4, "val NEG_PLUS_ONE");
+
+enum E_uchar : unsigned char {
+	MAX_BYTE = 255,
+};
+static_assert(sizeof(enum E_uchar) == 1, "sizeof E_uchar");
+static_assert(MAX_BYTE == 255, "val MAX_BYTE");
+
+typedef unsigned short uint16_t;
+enum E_typedef : uint16_t {
+	T1 = 65535,
+};
+static_assert(sizeof(enum E_typedef) == sizeof(uint16_t), "sizeof E_typedef");
+static_assert(T1 == 65535, "val T1");
+
+enum E_bool : bool {
+	EB_FALSE,
+	EB_TRUE,
+};
+static_assert(sizeof(enum E_bool) == sizeof(bool), "sizeof E_bool");
+static_assert(EB_FALSE == 0 && EB_TRUE == 1, "val E_bool");
+
+enum E_qual : const unsigned int {
+	EQ1 = 42,
+};
+static_assert(_Generic(EQ1, unsigned int: 1, default: 0) == 1, "EQ1 type");
+
+enum E_brace : unsigned long long {
+	EB_A = 1,
+	EB_B = _Generic(EB_A, unsigned long long: 2, default: 0),
+};
+static_assert(EB_B == 2, "inside-brace fixed type");
+static_assert(_Generic(EB_A, unsigned long long: 1, default: 0) == 1,
+	      "after-brace fixed type");
+
+struct E_unnamed_bf {
+	enum E_uchar : 8;
+	int x;
+};
+static_assert(sizeof(struct E_unnamed_bf) >= sizeof(int), "unnamed enum bitfield");
+
+enum E_unfixed_fit {
+	UF_A = 1U,
+};
+static_assert(_Generic(UF_A, int: 1, default: 0) == 1, "unfixed fits int");
+
+enum E_unfixed_wide {
+	UW_A = 1,
+	UW_B = ~0U,
+};
+static_assert(_Generic(UW_A, unsigned int: 1, default: 0) == 1,
+	      "C23 unfixed wide enum gives all members underlying type");
+static_assert(_Generic(UW_B, unsigned int: 1, default: 0) == 1,
+	      "C23 unfixed wide enum second member");
+
+enum FwdFixed : int;
+static int add_fwd_fixed_enum(enum FwdFixed x)
+{
+	return x + 1;
+}
+
+/*
+ * check-name: C23 enumerations with fixed underlying types
+ * check-command: sparse -std=c23 $file
+ */

  parent reply	other threads:[~2026-11-09 20:33 UTC|newest]

Thread overview: 74+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-09 20:31 Bart Van Assche
2026-11-09 20:31 ` [PATCH 01/73] Makefile: Pass '--' to rm in clean target to handle dash-prefixed files Bart Van Assche
2026-11-09 20:31 ` [PATCH 02/73] sparse-llvm: Use LLVMCreateBuilderForModule() with LLVM 24 and later Bart Van Assche
2026-11-09 20:31 ` [PATCH 03/73] typeof_unqual: Strip qualifiers and address space from types and arrays Bart Van Assche
2026-11-09 20:31 ` [PATCH 04/73] bitfield: Pack consecutive _Bool bitfields into byte containers Bart Van Assche
2026-11-09 20:31 ` [PATCH 05/73] allocate: Harden slab allocation bounds, zeroing, and mmap checks Bart Van Assche
2026-11-09 20:31 ` [PATCH 06/73] tokenize: Support 4096-byte identifiers and fix escape bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 07/73] pre-process: Fix macro expansion and include directive handling Bart Van Assche
2026-11-09 20:31 ` [PATCH 08/73] options, lib: Harden warning buffers and command-line option parsing Bart Van Assche
2026-11-09 20:31 ` [PATCH 09/73] builtin: Fix atomic builtin type checking and object_size bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 10/73] parse, show-parse: Fix attribute propagation, enum overflow, and bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 11/73] target-openrisc: Set target_openrisc.mach to MACH_OPENRISC Bart Van Assche
2026-11-09 20:31 ` [PATCH 12/73] evaluate: Fix pointer arithmetic, incomplete types, and qualifiers Bart Van Assche
2026-11-09 20:31 ` [PATCH 13/73] expand: Fix static initializer folding and floating-point casts Bart Van Assche
2026-11-09 20:32 ` [PATCH 14/73] inline: Copy union cast types and VLA bounds when inlining Bart Van Assche
2026-11-09 20:32 ` [PATCH 15/73] linearize, flow: Fix asm operands, switch case bounds, and DSE Bart Van Assche
2026-11-09 20:32 ` [PATCH 16/73] memops, ssa: Walk parent insns in reverse and skip mixed-size SSA Bart Van Assche
2026-11-09 20:32 ` [PATCH 17/73] simplify: Prevent overflow on LLONG_MIN negation and fix simplifications Bart Van Assche
2026-11-09 20:32 ` [PATCH 18/73] sparse: Gate context failure warning on Wcontext and catch underflow Bart Van Assche
2026-11-09 20:32 ` [PATCH 19/73] ast-model: Return AST_N_COLUMNS and inspect child nodes lazily Bart Van Assche
2026-11-09 20:32 ` [PATCH 20/73] c2xml: Guard against recursive symbols and free xmlGetProp() string Bart Van Assche
2026-11-09 20:32 ` [PATCH 21/73] compile-i386: Fix buffer bounds, unsigned '<=' codegen, and null checks Bart Van Assche
2026-11-09 20:32 ` [PATCH 22/73] ctags: Fix line comparison order and handle SYM_RESTRICT and SYM_LABEL Bart Van Assche
2026-11-09 20:32 ` [PATCH 23/73] dissect: Unwrap array element type via base_type() Bart Van Assche
2026-11-09 20:32 ` [PATCH 24/73] example: Fix show_op() rotating buffer dimensions Bart Van Assche
2026-11-09 20:32 ` [PATCH 25/73] graph: Check PSEUDO_SYM before dereferencing insn->func->sym Bart Van Assche
2026-11-09 20:32 ` [PATCH 26/73] scheck: Fix slice() upper bit index calculation Bart Van Assche
2026-11-09 20:32 ` [PATCH 27/73] sparse-llvm: Support arbitrary struct member counts and fix operand types Bart Van Assche
2026-11-09 20:32 ` [PATCH 28/73] inline: Duplicate generic expressions instead of evaluating them Bart Van Assche
2026-11-09 20:32 ` [PATCH 29/73] lib: Avoid mutating fmt before va_start() in warning() Bart Van Assche
2026-11-09 20:32 ` [PATCH 30/73] cse, canon: Cast pointers to uintptr_t before relational comparison Bart Van Assche
2026-11-09 20:32 ` [PATCH 31/73] context: Add "conditional context increment" model Bart Van Assche
2026-11-09 20:32 ` [PATCH 32/73] expand: Simplify signed constants early Bart Van Assche
2026-11-09 20:32 ` [PATCH 33/73] bitwise: Allow all-bits-set as a restricted value Bart Van Assche
2026-11-09 20:32 ` [PATCH 34/73] bitwise: Allow restricted ordered compares with unrestricted values Bart Van Assche
2026-11-09 20:32 ` [PATCH 35/73] bitwise: Keep unrestricted values unrestricted after a cast Bart Van Assche
2026-11-09 20:32 ` [PATCH 36/73] bitwise: Don't remove MOD_SPECIFIER from 'bitwise' types Bart Van Assche
2026-11-09 20:32 ` [PATCH 37/73] bitwise: Allow both regular casts and forced casts for restricted values Bart Van Assche
2026-11-09 20:32 ` [PATCH 38/73] clang-format: Add .clang-format configuration file Bart Van Assche
2026-11-09 20:32 ` [PATCH 39/73] options: Add support for -std=c23, -std=c2x and -std=gnu23 Bart Van Assche
2026-11-09 20:32 ` [PATCH 40/73] preprocessor: Add support for #elifdef and #elifndef Bart Van Assche
2026-11-09 20:32 ` [PATCH 41/73] preprocessor: Add support for __has_include and __has_include_next Bart Van Assche
2026-11-09 20:32 ` [PATCH 42/73] c23: Add support for static_assert, alignas, alignof, thread_local Bart Van Assche
2026-11-09 20:32 ` [PATCH 43/73] c23: Add support for bool, true, and false keywords Bart Van Assche
2026-11-09 20:32 ` [PATCH 44/73] c23: Add support for nullptr and nullptr_t Bart Van Assche
2026-11-09 20:32 ` [PATCH 45/73] c23: Add support for digit separators in numeric literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 46/73] c23: Support unnamed parameters in function definitions Bart Van Assche
2026-11-09 20:32 ` [PATCH 47/73] c23: Support variadic functions without named parameters Bart Van Assche
2026-11-09 20:32 ` [PATCH 48/73] c23: Support labels before declarations and at block ends Bart Van Assche
2026-11-09 20:32 ` [PATCH 49/73] c23: Support auto type inference Bart Van Assche
2026-11-09 20:32 ` [PATCH 50/73] c23: Support constexpr object specifier Bart Van Assche
2026-11-09 20:32 ` Bart Van Assche [this message]
2026-11-09 20:32 ` [PATCH 52/73] c23: Support standard [[]] attribute syntax Bart Van Assche
2026-11-09 20:32 ` [PATCH 53/73] preprocessor: Add support for __has_c_attribute Bart Van Assche
2026-11-09 20:32 ` [PATCH 54/73] c23: Support empty initializer braces {} Bart Van Assche
2026-11-09 20:32 ` [PATCH 55/73] preprocessor: Add support for __has_embed Bart Van Assche
2026-11-09 20:32 ` [PATCH 56/73] preprocessor: Add support for #embed directive Bart Van Assche
2026-11-09 20:32 ` [PATCH 57/73] c23: Add support for u8 character constants and string literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 58/73] preprocessor: Add support for __VA_OPT__ Bart Van Assche
2026-11-09 20:32 ` [PATCH 59/73] c23: Treat empty parameter list () as (void) and reject K&R functions Bart Van Assche
2026-11-09 20:32 ` [PATCH 60/73] c23: Support storage-class specifiers in compound literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 61/73] c23: Add support for _BitInt(N) Bart Van Assche
2026-11-09 20:32 ` [PATCH 62/73] enum: Support machine mode attributes on enumeration types Bart Van Assche
2026-11-09 20:32 ` [PATCH 63/73] enum: Support __attribute__((packed)) " Bart Van Assche
2026-11-09 20:32 ` [PATCH 64/73] expand: Ignore top-level non-array address space in compare_types() Bart Van Assche
2026-11-09 20:32 ` [PATCH 65/73] builtin: Support __builtin_infer_alloc_token() as a size_t ICE Bart Van Assche
2026-11-09 20:32 ` [PATCH 66/73] builtin: Add support for Linux kernel compiler builtins Bart Van Assche
2026-11-09 20:32 ` [PATCH 67/73] builtin: Add support for __builtin_has_attribute() Bart Van Assche
2026-11-09 20:32 ` [PATCH 68/73] cleanup: Support __attribute__((cleanup(...))) in linearize and -Wcontext Bart Van Assche
2026-11-09 20:32 ` [PATCH 69/73] builtin: Do not evaluate __builtin_clz(0) and __builtin_ctz(0) Bart Van Assche
2026-11-09 20:32 ` [PATCH 70/73] pre-process: Support long token and string expansions dynamically Bart Van Assche
2026-11-09 20:32 ` [PATCH 71/73] x86: Undefine conflicting bitness macros when parsing -m flags Bart Van Assche
2026-11-09 20:32 ` [PATCH 72/73] evaluate: Accept conditional expressions with only one void side Bart Van Assche
2026-11-09 20:32 ` [PATCH 73/73] ci: Add Linux kernel GitHub Actions workflow and build script Bart Van Assche

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261109203259.362845-52-bvanassche@acm.org \
    --to=bvanassche@acm.org \
    --cc=linux-sparse@vger.kernel.org \
    --cc=torvalds@linux-foundation.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox