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 57/73] c23: Add support for u8 character constants and string literals
Date: Mon,  9 Nov 2026 12:32:43 -0800	[thread overview]
Message-ID: <20261109203259.362845-58-bvanassche@acm.org> (raw)
In-Reply-To: <20261109203259.362845-1-bvanassche@acm.org>

Support C23 UTF-8 character constants (u8'c') and UTF-8 string literals
(u8"...") with unsigned char (char8_t) element type per ISO C23 6.4.4.4
and 6.4.5:
- Type u8"..." literals (including narrow + u8"" concatenations) as
  arrays of unsigned char in C23 mode while preserving char[] typing in
  C11/C17, and diagnose pointer signedness mismatches with char * in C23.
- Parse \uXXXX and \UXXXXXXXX universal character names in character and
  string literals, reject u8"" concatenation with wide strings, and emit
  errors on multi-byte or multi-character u8'...' constants.
- Warn under -Wpedantic when u8 character constants are used before C23.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 char.c                            | 111 ++++++++++++++++++++++++++++--
 evaluate.c                        |   7 +-
 expression.c                      |  15 ++--
 expression.h                      |   3 +-
 ident-list.h                      |   3 +-
 pre-process.c                     |  21 +++++-
 token.h                           |   6 ++
 tokenize.c                        | 102 ++++++++++++++++++---------
 validation/c23-u8-char-error.c    |  16 +++++
 validation/c23-u8-char-pedantic.c |  10 +++
 validation/c23-u8-char-warn.c     |  14 ++++
 validation/c23-u8-char.c          |  37 ++++++++++
 12 files changed, 296 insertions(+), 49 deletions(-)
 create mode 100644 validation/c23-u8-char-error.c
 create mode 100644 validation/c23-u8-char-pedantic.c
 create mode 100644 validation/c23-u8-char-warn.c
 create mode 100644 validation/c23-u8-char.c

diff --git a/char.c b/char.c
index 1f6eb1f6be83..a0776b560e29 100644
--- a/char.c
+++ b/char.c
@@ -6,6 +6,54 @@
 #include "expression.h"
 #include "char.h"
 
+static const char *parse_ucn(const char *p, const char *end, int digits,
+			     unsigned *cp, struct position pos)
+{
+	unsigned val = 0;
+	int i;
+
+	for (i = 0; i < digits; i++) {
+		unsigned d;
+		if (p >= end || (d = hexval(*p)) > 15) {
+			sparse_error(pos, "incomplete universal character name");
+			*cp = 0;
+			return p;
+		}
+		val = (val << 4) + d;
+		p++;
+	}
+	if ((val >= 0xd800 && val <= 0xdfff) || val > 0x10ffff) {
+		sparse_error(pos, "invalid universal character name");
+		val = 0;
+	}
+	*cp = val;
+	return p;
+}
+
+static int encode_utf8(unsigned cp, char out[4])
+{
+	if (cp <= 0x7f) {
+		out[0] = cp;
+		return 1;
+	}
+	if (cp <= 0x7ff) {
+		out[0] = 0xc0 | (cp >> 6);
+		out[1] = 0x80 | (cp & 0x3f);
+		return 2;
+	}
+	if (cp <= 0xffff) {
+		out[0] = 0xe0 | (cp >> 12);
+		out[1] = 0x80 | ((cp >> 6) & 0x3f);
+		out[2] = 0x80 | (cp & 0x3f);
+		return 3;
+	}
+	out[0] = 0xf0 | (cp >> 18);
+	out[1] = 0x80 | ((cp >> 12) & 0x3f);
+	out[2] = 0x80 | ((cp >> 6) & 0x3f);
+	out[3] = 0x80 | (cp & 0x3f);
+	return 4;
+}
+
 static const char *parse_escape(const char *p, unsigned *val, const char *end, int bits, struct position pos)
 {
 	unsigned c = (unsigned char)*p++;
@@ -26,6 +74,11 @@ static const char *parse_escape(const char *p, unsigned *val, const char *end, i
 	case 'f': c = '\f'; break;
 	case 'r': c = '\r'; break;
 	case 'e': c = '\e'; break;
+	case 'u':
+	case 'U':
+		p = parse_ucn(p, end, c == 'u' ? 4 : 8, &c, pos);
+		*val = c;
+		return p;
 	case 'x': {
 		unsigned mask = -(1U << (bits - 4));
 		for (c = 0; p < end; c = (c << 4) + d) {
@@ -75,6 +128,7 @@ void get_char_constant(struct token *token, unsigned long long *val)
 	switch (type) {
 	case TOKEN_CHAR:
 	case TOKEN_WIDE_CHAR:
+	case TOKEN_UTF8_CHAR:
 		p = token->string->data;
 		end = p + token->string->length - 1;
 		if (end == p) {
@@ -86,14 +140,34 @@ void get_char_constant(struct token *token, unsigned long long *val)
 	case TOKEN_CHAR_EMBEDDED_0 ... TOKEN_CHAR_EMBEDDED_3:
 		end = p + type - TOKEN_CHAR;
 		break;
-	default:
+	case TOKEN_WIDE_CHAR_EMBEDDED_0 ... TOKEN_WIDE_CHAR_EMBEDDED_3:
 		end = p + type - TOKEN_WIDE_CHAR;
+		break;
+	default:
+		end = p + type - TOKEN_UTF8_CHAR;
+		break;
 	}
 	p = parse_escape(p, &v, end,
-			type < TOKEN_WIDE_CHAR ? bits_in_char : wchar_ctype->bit_size, token->pos);
-	if (p != end)
+			 (type >= TOKEN_WIDE_CHAR &&
+			  type <= TOKEN_WIDE_CHAR_EMBEDDED_3) ?
+				 wchar_ctype->bit_size :
+				 bits_in_char,
+			 token->pos);
+	if (type >= TOKEN_UTF8_CHAR && type <= TOKEN_UTF8_CHAR_EMBEDDED_3) {
+		if (p != end || v > 0xff ||
+		    (token->embedded[0] == '\\' &&
+		     (token->embedded[1] == 'u' || token->embedded[1] == 'U') &&
+		     v > 0x7f) ||
+		    (type == TOKEN_UTF8_CHAR && token->string->data[0] == '\\' &&
+		     (token->string->data[1] == 'u' ||
+		      token->string->data[1] == 'U') &&
+		     v > 0x7f))
+			sparse_error(token->pos,
+				     "multi-character UTF-8 character constant");
+	} else if (p != end) {
 		warning(token->pos,
 			"multi-character character constant");
+	}
 	*val = v;
 }
 
@@ -103,6 +177,7 @@ struct token *get_string_constant(struct token *token, struct expression *expr)
 	struct token *next = token->next, *done = NULL;
 	int stringtype = token_type(token);
 	int is_wide = stringtype == TOKEN_WIDE_STRING;
+	int is_utf8 = stringtype == TOKEN_UTF8_STRING;
 	static char buffer[MAX_STRING];
 	int len = 0;
 	int bits;
@@ -112,6 +187,12 @@ struct token *get_string_constant(struct token *token, struct expression *expr)
 		switch (token_type(next)) {
 		case TOKEN_WIDE_STRING:
 			is_wide = 1;
+			next = next->next;
+			break;
+		case TOKEN_UTF8_STRING:
+			is_utf8 = 1;
+			next = next->next;
+			break;
 		case TOKEN_STRING:
 			next = next->next;
 			break;
@@ -119,18 +200,35 @@ struct token *get_string_constant(struct token *token, struct expression *expr)
 			done = next;
 		}
 	}
+	if (is_wide && is_utf8) {
+		sparse_error(token->pos,
+			     "unsupported non-standard concatenation of string literals");
+		is_utf8 = 0;
+	}
 	bits = is_wide ? wchar_ctype->bit_size: bits_in_char;
 	while (token != done) {
 		unsigned v;
 		const char *p = token->string->data;
 		const char *end = p + token->string->length - 1;
 		while (p < end) {
+			bool is_ucn = (*p == '\\' && p + 1 < end &&
+				       (p[1] == 'u' || p[1] == 'U'));
 			if (*p == '\\')
 				esc_count++;
 			p = parse_escape(p, &v, end, bits, token->pos);
-			if (len < MAX_STRING)
-				buffer[len] = v;
-			len++;
+			if (is_ucn && !is_wide) {
+				char utf8[4];
+				int i, n = encode_utf8(v, utf8);
+				for (i = 0; i < n; i++) {
+					if (len < MAX_STRING)
+						buffer[len] = utf8[i];
+					len++;
+				}
+			} else {
+				if (len < MAX_STRING)
+					buffer[len] = v;
+				len++;
+			}
 		}
 		token = token->next;
 	}
@@ -148,5 +246,6 @@ struct token *get_string_constant(struct token *token, struct expression *expr)
 	}
 	expr->string = string;
 	expr->wide = is_wide;
+	expr->utf8 = is_utf8;
 	return token;
 }
diff --git a/evaluate.c b/evaluate.c
index f40e35773639..e46b1a7d4f70 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -135,7 +135,8 @@ static struct symbol *evaluate_string(struct expression *expr)
 	struct expression *addr = alloc_expression(expr->pos, EXPR_SYMBOL);
 	struct expression *initstr = alloc_expression(expr->pos, EXPR_STRING);
 	unsigned int length = expr->string->length;
-	struct symbol *char_type = expr->wide ? wchar_ctype : &char_ctype;
+	struct symbol *char_type = expr->wide ? wchar_ctype :
+		(expr->utf8 && standard >= STANDARD_C23) ? &uchar_ctype : &char_ctype;
 
 	sym->array_size = alloc_const_expression(expr->pos, length);
 	sym->bit_size = length * char_type->bit_size;
@@ -148,6 +149,8 @@ static struct symbol *evaluate_string(struct expression *expr)
 	sym->evaluated = 1;
 
 	initstr->ctype = sym;
+	initstr->wide = expr->wide;
+	initstr->utf8 = expr->utf8;
 	initstr->string = expr->string;
 
 	array->array_size = sym->array_size;
@@ -1599,7 +1602,7 @@ static int whitelist_pointers(struct symbol *t1, struct symbol *t2)
 	if (t1 == t2)
 		return 1;
 	if (t1->rank == -2 && t2->rank == -2)
-		return 1;
+		return !Wtypesign && !Wpedantic && standard < STANDARD_C23;
 	if (t1->rank != t2->rank)
 		return 0;
 	return !Wtypesign;
diff --git a/expression.c b/expression.c
index 1b04d977a2d5..ace0bf6d1a31 100644
--- a/expression.c
+++ b/expression.c
@@ -599,17 +599,23 @@ struct token *primary_expression(struct token *token, struct expression **tree)
 	struct expression *expr = NULL;
 
 	switch (token_type(token)) {
-	case TOKEN_CHAR ... TOKEN_WIDE_CHAR_EMBEDDED_3:
+	case TOKEN_CHAR ... TOKEN_UTF8_CHAR_EMBEDDED_3:
 		expr = alloc_expression(token->pos, EXPR_VALUE);
 		expr->flags = CEF_SET_CHAR;
 		get_char_constant(token, &expr->value);
 
-		// TODO: handle 'u8', 'u' & 'U' prefixes.
-		if (token_type(token) < TOKEN_WIDE_CHAR) {
+		// TODO: handle 'u' & 'U' prefixes.
+		if (token_type(token) <= TOKEN_CHAR_EMBEDDED_3) {
 			expr->ctype = &char_ctype;
 			cast_value(expr, &int_ctype, expr);
-		} else {
+		} else if (token_type(token) <= TOKEN_WIDE_CHAR_EMBEDDED_3) {
 			expr->ctype = wchar_ctype;
+		} else {
+			if (standard < STANDARD_C23 && Wpedantic)
+				warning(token->pos,
+					"UTF-8 character constants are a C23 feature");
+			expr->ctype = &uchar_ctype;
+			cast_value(expr, &uchar_ctype, expr);
 		}
 		token = token->next;
 		break;
@@ -709,6 +715,7 @@ struct token *primary_expression(struct token *token, struct expression **tree)
 
 	case TOKEN_STRING:
 	case TOKEN_WIDE_STRING:
+	case TOKEN_UTF8_STRING:
 		expr = alloc_expression(token->pos, EXPR_STRING);
 		token = get_string_constant(token, expr);
 		break;
diff --git a/expression.h b/expression.h
index 8bf40d32e10b..8c387adf9d5b 100644
--- a/expression.h
+++ b/expression.h
@@ -175,7 +175,8 @@ struct expression {
 
 		// EXPR_STRING
 		struct {
-			int wide;
+			char wide;
+			char utf8;
 			struct string *string;
 		};
 
diff --git a/ident-list.h b/ident-list.h
index f51f2048f5ca..3340f11641fa 100644
--- a/ident-list.h
+++ b/ident-list.h
@@ -54,8 +54,9 @@ IDENT(static_assert);
 IDENT(thread_local);
 IDENT(true);
 
-/* Special case for L'\t' */
+/* Special case for L'\t' and u8'a' */
 IDENT(L);
+IDENT(u8);
 
 /* Extended gcc identifiers */
 IDENT(asm); IDENT_RESERVED(__asm); IDENT_RESERVED(__asm__);
diff --git a/pre-process.c b/pre-process.c
index 428b96f990fb..9ebf09cffbdd 100644
--- a/pre-process.c
+++ b/pre-process.c
@@ -507,12 +507,19 @@ static enum token_type combine(struct token *left, struct token *right, char *p)
 		return TOKEN_ERROR;
 
 	if (t1 == TOKEN_IDENT && left->ident == &L_ident) {
-		if (t2 >= TOKEN_CHAR && t2 < TOKEN_WIDE_CHAR)
+		if (t2 >= TOKEN_CHAR && t2 <= TOKEN_CHAR_EMBEDDED_3)
 			return t2 + TOKEN_WIDE_CHAR - TOKEN_CHAR;
 		if (t2 == TOKEN_STRING)
 			return TOKEN_WIDE_STRING;
 	}
 
+	if (t1 == TOKEN_IDENT && left->ident == &u8_ident) {
+		if (t2 >= TOKEN_CHAR && t2 <= TOKEN_CHAR_EMBEDDED_3)
+			return t2 + TOKEN_UTF8_CHAR - TOKEN_CHAR;
+		if (t2 == TOKEN_STRING)
+			return TOKEN_UTF8_STRING;
+	}
+
 	if (t2 != TOKEN_IDENT && t2 != TOKEN_NUMBER && t2 != TOKEN_SPECIAL)
 		return TOKEN_ERROR;
 
@@ -592,12 +599,15 @@ static int merge(struct token *left, struct token *right)
 
 	case TOKEN_WIDE_CHAR:
 	case TOKEN_WIDE_STRING:
+	case TOKEN_UTF8_CHAR:
+	case TOKEN_UTF8_STRING:
 		token_type(left) = res;
 		left->pos.noexpand = 0;
 		left->string = right->string;
 		return 1;
 
 	case TOKEN_WIDE_CHAR_EMBEDDED_0 ... TOKEN_WIDE_CHAR_EMBEDDED_3:
+	case TOKEN_UTF8_CHAR_EMBEDDED_0 ... TOKEN_UTF8_CHAR_EMBEDDED_3:
 		token_type(left) = res;
 		left->pos.noexpand = 0;
 		memcpy(left->embedded, right->embedded, 4);
@@ -620,7 +630,9 @@ static struct token *dup_token(const struct token *token, struct position *strea
 	alloc->pos.stream = pos.stream;
 	alloc->pos.line = pos.line;
 	alloc->pos.pos = pos.pos;
-	if (token_type(alloc) == TOKEN_STRING || token_type(alloc) == TOKEN_WIDE_STRING)
+	if (token_type(alloc) == TOKEN_STRING ||
+	    token_type(alloc) == TOKEN_WIDE_STRING ||
+	    token_type(alloc) == TOKEN_UTF8_STRING)
 		token->string->immutable = 1;
 	return alloc;	
 }
@@ -2165,12 +2177,15 @@ static int token_different(struct token *t1, struct token *t2)
 		break;
 	case TOKEN_CHAR_EMBEDDED_0 ... TOKEN_CHAR_EMBEDDED_3:
 	case TOKEN_WIDE_CHAR_EMBEDDED_0 ... TOKEN_WIDE_CHAR_EMBEDDED_3:
+	case TOKEN_UTF8_CHAR_EMBEDDED_0 ... TOKEN_UTF8_CHAR_EMBEDDED_3:
 		different = memcmp(t1->embedded, t2->embedded, 4);
 		break;
 	case TOKEN_CHAR:
 	case TOKEN_WIDE_CHAR:
+	case TOKEN_UTF8_CHAR:
 	case TOKEN_STRING:
-	case TOKEN_WIDE_STRING: {
+	case TOKEN_WIDE_STRING:
+	case TOKEN_UTF8_STRING: {
 		struct string *s1, *s2;
 
 		s1 = t1->string;
diff --git a/token.h b/token.h
index 8c8a496bee89..b421f166f1f7 100644
--- a/token.h
+++ b/token.h
@@ -94,8 +94,14 @@ enum token_type {
 	TOKEN_WIDE_CHAR_EMBEDDED_1,
 	TOKEN_WIDE_CHAR_EMBEDDED_2,
 	TOKEN_WIDE_CHAR_EMBEDDED_3,
+	TOKEN_UTF8_CHAR,
+	TOKEN_UTF8_CHAR_EMBEDDED_0,
+	TOKEN_UTF8_CHAR_EMBEDDED_1,
+	TOKEN_UTF8_CHAR_EMBEDDED_2,
+	TOKEN_UTF8_CHAR_EMBEDDED_3,
 	TOKEN_STRING,
 	TOKEN_WIDE_STRING,
+	TOKEN_UTF8_STRING,
 	TOKEN_SPECIAL,
 	TOKEN_STREAMBEGIN,
 	TOKEN_STREAMEND,
diff --git a/tokenize.c b/tokenize.c
index b0d4059c8782..4ac40e9e1506 100644
--- a/tokenize.c
+++ b/tokenize.c
@@ -152,12 +152,16 @@ const char *show_string(const struct string *string)
 	return buffer;
 }
 
-static const char *show_char(const char *s, size_t len, char prefix, char delim)
+static const char *show_char(const char *s, size_t len, const char *prefix,
+			     char delim)
 {
-	static char buffer[MAX_STRING + 4];
+	static char buffer[MAX_STRING + 8];
 	char *p = buffer;
-	if (prefix)
-		*p++ = prefix;
+	if (prefix) {
+		size_t plen = strlen(prefix);
+		memcpy(p, prefix, plen);
+		p += plen;
+	}
 	*p++ = delim;
 	memcpy(p, s, len);
 	p += len;
@@ -166,13 +170,17 @@ static const char *show_char(const char *s, size_t len, char prefix, char delim)
 	return buffer;
 }
 
-static const char *quote_char(const char *s, size_t len, char prefix, char delim)
+static const char *quote_char(const char *s, size_t len, const char *prefix,
+			      char delim)
 {
-	static char buffer[2*MAX_STRING + 6];
+	static char buffer[2 * MAX_STRING + 8];
 	size_t i;
 	char *p = buffer;
-	if (prefix)
-		*p++ = prefix;
+	if (prefix) {
+		size_t plen = strlen(prefix);
+		memcpy(p, prefix, plen);
+		p += plen;
+	}
 	if (delim == '"')
 		*p++ = '\\';
 	*p++ = delim;
@@ -211,24 +219,35 @@ const char *show_token(const struct token *token)
 	case TOKEN_SPECIAL:
 		return show_special(token->special);
 
-	case TOKEN_CHAR: 
-		return show_char(token->string->data,
-			token->string->length - 1, 0, '\'');
+	case TOKEN_CHAR:
+		return show_char(token->string->data, token->string->length - 1,
+				 NULL, '\'');
 	case TOKEN_CHAR_EMBEDDED_0 ... TOKEN_CHAR_EMBEDDED_3:
 		return show_char(token->embedded,
-			token_type(token) - TOKEN_CHAR, 0, '\'');
-	case TOKEN_WIDE_CHAR: 
-		return show_char(token->string->data,
-			token->string->length - 1, 'L', '\'');
+				 token_type(token) - TOKEN_CHAR, NULL, '\'');
+	case TOKEN_WIDE_CHAR:
+		return show_char(token->string->data, token->string->length - 1,
+				 "L", '\'');
 	case TOKEN_WIDE_CHAR_EMBEDDED_0 ... TOKEN_WIDE_CHAR_EMBEDDED_3:
 		return show_char(token->embedded,
-			token_type(token) - TOKEN_WIDE_CHAR, 'L', '\'');
-	case TOKEN_STRING: 
-		return show_char(token->string->data,
-			token->string->length - 1, 0, '"');
-	case TOKEN_WIDE_STRING: 
-		return show_char(token->string->data,
-			token->string->length - 1, 'L', '"');
+				 token_type(token) - TOKEN_WIDE_CHAR, "L",
+				 '\'');
+	case TOKEN_UTF8_CHAR:
+		return show_char(token->string->data, token->string->length - 1,
+				 "u8", '\'');
+	case TOKEN_UTF8_CHAR_EMBEDDED_0 ... TOKEN_UTF8_CHAR_EMBEDDED_3:
+		return show_char(token->embedded,
+				 token_type(token) - TOKEN_UTF8_CHAR, "u8",
+				 '\'');
+	case TOKEN_STRING:
+		return show_char(token->string->data, token->string->length - 1,
+				 NULL, '"');
+	case TOKEN_WIDE_STRING:
+		return show_char(token->string->data, token->string->length - 1,
+				 "L", '"');
+	case TOKEN_UTF8_STRING:
+		return show_char(token->string->data, token->string->length - 1,
+				 "u8", '"');
 
 	case TOKEN_STREAMBEGIN:
 		snprintf(buffer, sizeof(buffer), "<beginning of '%s'>", stream_name(token->pos.stream));
@@ -273,24 +292,35 @@ const char *quote_token(const struct token *token)
 	case TOKEN_SPECIAL:
 		return show_special(token->special);
 
-	case TOKEN_CHAR: 
+	case TOKEN_CHAR:
 		return quote_char(token->string->data,
-			token->string->length - 1, 0, '\'');
+				  token->string->length - 1, NULL, '\'');
 	case TOKEN_CHAR_EMBEDDED_0 ... TOKEN_CHAR_EMBEDDED_3:
 		return quote_char(token->embedded,
-			token_type(token) - TOKEN_CHAR, 0, '\'');
-	case TOKEN_WIDE_CHAR: 
+				  token_type(token) - TOKEN_CHAR, NULL, '\'');
+	case TOKEN_WIDE_CHAR:
 		return quote_char(token->string->data,
-			token->string->length - 1, 'L', '\'');
+				  token->string->length - 1, "L", '\'');
 	case TOKEN_WIDE_CHAR_EMBEDDED_0 ... TOKEN_WIDE_CHAR_EMBEDDED_3:
 		return quote_char(token->embedded,
-			token_type(token) - TOKEN_WIDE_CHAR, 'L', '\'');
-	case TOKEN_STRING: 
+				  token_type(token) - TOKEN_WIDE_CHAR, "L",
+				  '\'');
+	case TOKEN_UTF8_CHAR:
+		return quote_char(token->string->data,
+				  token->string->length - 1, "u8", '\'');
+	case TOKEN_UTF8_CHAR_EMBEDDED_0 ... TOKEN_UTF8_CHAR_EMBEDDED_3:
+		return quote_char(token->embedded,
+				  token_type(token) - TOKEN_UTF8_CHAR, "u8",
+				  '\'');
+	case TOKEN_STRING:
+		return quote_char(token->string->data,
+				  token->string->length - 1, NULL, '"');
+	case TOKEN_WIDE_STRING:
 		return quote_char(token->string->data,
-			token->string->length - 1, 0, '"');
-	case TOKEN_WIDE_STRING: 
+				  token->string->length - 1, "L", '"');
+	case TOKEN_UTF8_STRING:
 		return quote_char(token->string->data,
-			token->string->length - 1, 'L', '"');
+				  token->string->length - 1, "u8", '"');
 	case TOKEN_GNU_KLUDGE:
 		return ",";
 	default:
@@ -979,6 +1009,14 @@ static int get_one_identifier(int c, stream_t *stream)
 				return eat_string(nextchar(stream), stream,
 							TOKEN_WIDE_STRING);
 		}
+		if (len == 2 && buf[0] == 'u' && buf[1] == '8') {
+			if (next == '\'')
+				return eat_string(nextchar(stream), stream,
+						  TOKEN_UTF8_CHAR);
+			else
+				return eat_string(nextchar(stream), stream,
+						  TOKEN_UTF8_STRING);
+		}
 	}
 	hash = ident_hash_end(hash);
 	ident = create_hashed_ident(buf, len, hash);
diff --git a/validation/c23-u8-char-error.c b/validation/c23-u8-char-error.c
new file mode 100644
index 000000000000..da03b604e8cd
--- /dev/null
+++ b/validation/c23-u8-char-error.c
@@ -0,0 +1,16 @@
+static unsigned char e1 = u8'';
+static unsigned char e2 = u8'ab';
+static unsigned char e3 = u8'\u00e9';
+static const void *e4 = u8"a" L"b";
+
+/*
+ * check-name: c23-u8-char-error
+ * check-command: sparse -std=c23 $file
+ *
+ * check-error-start
+c23-u8-char-error.c:1:27: error: empty character constant
+c23-u8-char-error.c:2:27: error: multi-character UTF-8 character constant
+c23-u8-char-error.c:3:27: error: multi-character UTF-8 character constant
+c23-u8-char-error.c:4:25: error: unsupported non-standard concatenation of string literals
+ * check-error-end
+ */
diff --git a/validation/c23-u8-char-pedantic.c b/validation/c23-u8-char-pedantic.c
new file mode 100644
index 000000000000..8c2fbbaeecf6
--- /dev/null
+++ b/validation/c23-u8-char-pedantic.c
@@ -0,0 +1,10 @@
+static int x = u8'a';
+
+/*
+ * check-name: c23-u8-char-pedantic
+ * check-command: sparse -std=c11 -Wpedantic $file
+ *
+ * check-error-start
+c23-u8-char-pedantic.c:1:16: warning: UTF-8 character constants are a C23 feature
+ * check-error-end
+ */
diff --git a/validation/c23-u8-char-warn.c b/validation/c23-u8-char-warn.c
new file mode 100644
index 000000000000..2ccf459e01f3
--- /dev/null
+++ b/validation/c23-u8-char-warn.c
@@ -0,0 +1,14 @@
+static unsigned char w1 = u8'\x100';
+static const char *w2 = u8"test";
+
+/*
+ * check-name: c23-u8-char-warn
+ * check-command: sparse -std=c23 $file
+ *
+ * check-error-start
+c23-u8-char-warn.c:1:27: warning: hex escape sequence out of range
+c23-u8-char-warn.c:2:25: warning: incorrect type in initializer (different signedness)
+c23-u8-char-warn.c:2:25:    expected char const *static [toplevel] w2
+c23-u8-char-warn.c:2:25:    got unsigned char *
+ * check-error-end
+ */
diff --git a/validation/c23-u8-char.c b/validation/c23-u8-char.c
new file mode 100644
index 000000000000..60e0c2a02e36
--- /dev/null
+++ b/validation/c23-u8-char.c
@@ -0,0 +1,37 @@
+static_assert(sizeof(u8'a') == 1, "sizeof u8 char");
+static_assert(u8'a' == 97, "value u8 char");
+static_assert(u8'\0' == 0, "u8 nul");
+static_assert(u8'\xff' == 255, "u8 0xff is unsigned");
+static_assert(u8'\u0041' == 65, "u8 UCN ASCII");
+static_assert(_Generic(u8'a', unsigned char: 1, char: 0, int: 0, default: 0) == 1,
+	      "type u8 char");
+
+static_assert(sizeof(u8"hello") == 6, "sizeof u8 string");
+static_assert(sizeof(u8"\u00e9") == 3, "sizeof 2-byte UTF-8 UCN string");
+static_assert(sizeof(u8"\U0001F600") == 5, "sizeof 4-byte UTF-8 UCN string");
+static_assert(sizeof(u8"ab" "cd") == 5, "concat u8 + narrow");
+static_assert(sizeof("ab" u8"cd") == 5, "concat narrow + u8");
+static_assert(_Generic(u8"hello", unsigned char *: 1, char *: 0, default: 0) == 1,
+	      "u8 string decays to unsigned char *");
+static_assert(_Generic("ab" u8"cd", unsigned char *: 1, char *: 0, default: 0) == 1,
+	      "narrow + u8 string decays to unsigned char *");
+
+static const unsigned char c = u8'z';
+static const unsigned char *s = u8"test";
+static char s1[] = u8"abc";
+static unsigned char s2[] = u8"abc";
+static signed char s3[] = u8"abc";
+
+#define CAT(a, b) a##b
+static const unsigned char c2 = CAT(u8, 'x');
+static const unsigned char *s_cat = CAT(u8, "yz");
+
+static int use_vars(void)
+{
+	return c + s[0] + s1[0] + s2[0] + s3[0] + c2 + s_cat[0];
+}
+
+/*
+ * check-name: c23-u8-char
+ * check-command: sparse -std=c23 -pedantic $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 ` [PATCH 51/73] c23: Support enumerations with fixed underlying types Bart Van Assche
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 ` Bart Van Assche [this message]
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-58-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