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 45/73] c23: Add support for digit separators in numeric literals
Date: Mon,  9 Nov 2026 12:32:31 -0800	[thread overview]
Message-ID: <20261109203259.362845-46-bvanassche@acm.org> (raw)
In-Reply-To: <20261109203259.362845-1-bvanassche@acm.org>

Allow single quotes (') between digits in preprocessing numbers and
strip them before parsing integer and floating-point values in
expression.c to support C23 digit separators (WG14 N2626) and emit a
pedantic warning in pre-C23 modes.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 expression.c                               | 165 +++++++++++++++++++++
 tokenize.c                                 |  35 ++++-
 validation/c23-digit-separators-error.c    |  36 +++++
 validation/c23-digit-separators-pedantic.c |  12 ++
 validation/c23-digit-separators.c          |  29 ++++
 5 files changed, 275 insertions(+), 2 deletions(-)
 create mode 100644 validation/c23-digit-separators-error.c
 create mode 100644 validation/c23-digit-separators-pedantic.c
 create mode 100644 validation/c23-digit-separators.c

diff --git a/expression.c b/expression.c
index da2c14b458db..1b04d977a2d5 100644
--- a/expression.c
+++ b/expression.c
@@ -258,6 +258,157 @@ static unsigned long long parse_num(const char *nptr, char **end)
 	return strtoull(nptr, end, 0);
 }
 
+static int is_digit_for_base(int c, int base)
+{
+	if (base == 2)
+		return c == '0' || c == '1';
+	if (base == 8)
+		return c >= '0' && c <= '7';
+	if (base == 16)
+		return isxdigit((unsigned char)c);
+	return isdigit((unsigned char)c);
+}
+
+static int validate_digit_separators(struct expression *expr, const char *str)
+{
+	enum {
+		NUM_INT,
+		NUM_FRAC,
+		NUM_EXP_SIGN,
+		NUM_EXP,
+		NUM_SUFFIX,
+	} state = NUM_INT;
+	int is_hex = (str[0] == '0' && (str[1] == 'x' || str[1] == 'X'));
+	int is_bin = (str[0] == '0' && (str[1] == 'b' || str[1] == 'B'));
+	int is_oct = (str[0] == '0' && !is_hex && !is_bin &&
+		      strchr(str, '.') == NULL && strchr(str, 'e') == NULL &&
+		      strchr(str, 'E') == NULL);
+	int base = is_hex ? 16 : is_bin ? 2 : is_oct ? 8 : 10;
+	int in_digits = 0;
+	int i = (is_hex || is_bin) ? 2 : 0;
+
+	for (; str[i]; i++) {
+		char c = str[i];
+
+		if (c == '\'') {
+			char prev = i > 0 ? str[i - 1] : 0;
+			char next = str[i + 1];
+
+			if (prev == '\'' || next == '\'') {
+				sparse_error(expr->pos,
+					     "adjacent digit separators");
+				return 0;
+			}
+			if (i == 2 && (is_hex || is_bin)) {
+				sparse_error(
+					expr->pos,
+					"digit separator after base indicator");
+				return 0;
+			}
+			if (prev == '.' || next == '.') {
+				sparse_error(
+					expr->pos,
+					"digit separator adjacent to decimal point");
+				return 0;
+			}
+			if (state == NUM_EXP_SIGN ||
+			    (state != NUM_SUFFIX &&
+			     ((is_hex && (next == 'p' || next == 'P')) ||
+			      (!is_hex && (next == 'e' || next == 'E')))) ||
+			    (!in_digits && state == NUM_EXP)) {
+				sparse_error(
+					expr->pos,
+					"digit separator adjacent to exponent");
+				return 0;
+			}
+			if (state == NUM_SUFFIX || !in_digits ||
+			    !is_digit_for_base(next, base)) {
+				sparse_error(
+					expr->pos,
+					"digit separator outside digit sequence");
+				return 0;
+			}
+			in_digits = 0;
+			continue;
+		}
+
+		switch (state) {
+		case NUM_INT:
+			if (is_digit_for_base(c, base)) {
+				in_digits = 1;
+			} else if (c == '.' && (base == 10 || base == 16)) {
+				state = NUM_FRAC;
+				in_digits = 0;
+			} else if ((base == 10 && (c == 'e' || c == 'E')) ||
+				   (base == 16 && (c == 'p' || c == 'P'))) {
+				state = NUM_EXP_SIGN;
+				base = 10;
+				in_digits = 0;
+			} else {
+				state = NUM_SUFFIX;
+				in_digits = 0;
+			}
+			break;
+		case NUM_FRAC:
+			if (is_digit_for_base(c, base)) {
+				in_digits = 1;
+			} else if ((base == 10 && (c == 'e' || c == 'E')) ||
+				   (base == 16 && (c == 'p' || c == 'P'))) {
+				state = NUM_EXP_SIGN;
+				base = 10;
+				in_digits = 0;
+			} else {
+				state = NUM_SUFFIX;
+				in_digits = 0;
+			}
+			break;
+		case NUM_EXP_SIGN:
+			state = NUM_EXP;
+			if (c == '+' || c == '-') {
+				in_digits = 0;
+			} else if (isdigit((unsigned char)c)) {
+				in_digits = 1;
+			} else {
+				state = NUM_SUFFIX;
+				in_digits = 0;
+			}
+			break;
+		case NUM_EXP:
+			if (isdigit((unsigned char)c)) {
+				in_digits = 1;
+			} else {
+				state = NUM_SUFFIX;
+				in_digits = 0;
+			}
+			break;
+		case NUM_SUFFIX:
+			break;
+		}
+	}
+	return 1;
+}
+
+static const char *strip_digit_separators(const char *str)
+{
+	size_t len;
+	char *buf, *dst;
+
+	if (!strchr(str, '\''))
+		return str;
+
+	len = strlen(str);
+	buf = __alloc_bytes(len + 1);
+	dst = buf;
+
+	while (*str) {
+		if (*str != '\'')
+			*dst++ = *str;
+		str++;
+	}
+	*dst = '\0';
+	return buf;
+}
+
 static void get_number_value(struct expression *expr, struct token *token)
 {
 	const char *str = token->number;
@@ -268,6 +419,19 @@ static void get_number_value(struct expression *expr, struct token *token)
 	int try_unsigned = 1;
 	int bits;
 
+	if (strchr(str, '\'')) {
+		if (!validate_digit_separators(expr, str)) {
+			expr->type = EXPR_VALUE;
+			expr->flags = CEF_SET_ICE;
+			expr->ctype = &bad_ctype;
+			return;
+		}
+		if (standard < STANDARD_C23 && Wpedantic)
+			warning(expr->pos,
+				"ISO C does not support digit separators before C23");
+		str = strip_digit_separators(str);
+	}
+
 	errno = 0;
 	value = parse_num(str, &end);
 	if (end == str)
@@ -385,6 +549,7 @@ Float:
 Enoint:
 	sparse_error(expr->pos, "constant %s is not a valid number", show_token(token));
 	expr->type = EXPR_VALUE;
+	expr->flags = CEF_SET_ICE;
 	expr->value = 0;
 	expr->ctype = &int_ctype;
 }
diff --git a/tokenize.c b/tokenize.c
index 76b0278925ed..b0d4059c8782 100644
--- a/tokenize.c
+++ b/tokenize.c
@@ -50,6 +50,7 @@ typedef struct {
 	int fd, offset, size;
 	int pos, line, nr;
 	int newline, whitespace;
+	int pushed_char;
 	struct token **tokenlist;
 	struct token *token;
 	unsigned char *buffer;
@@ -446,8 +447,14 @@ got_eof:
  */
 static inline int nextchar(stream_t *stream)
 {
-	int offset = stream->offset;
+	int offset;
 
+	if (stream->pushed_char != -2) {
+		int c = stream->pushed_char;
+		stream->pushed_char = -2;
+		return c;
+	}
+	offset = stream->offset;
 	if (offset < stream->size) {
 		int c = stream->buffer[offset++];
 		static const char special[256] = {
@@ -462,6 +469,19 @@ static inline int nextchar(stream_t *stream)
 	return nextchar_slow(stream);
 }
 
+static int peekchar(stream_t *stream)
+{
+	if (stream->pushed_char != -2)
+		return stream->pushed_char;
+	if (stream->offset < stream->size) {
+		int c = stream->buffer[stream->offset];
+		if (c != '\r' && c != '\\')
+			return c;
+	}
+	stream->pushed_char = nextchar_slow(stream);
+	return stream->pushed_char;
+}
+
 struct token eof_token_entry;
 
 static struct token *mark_eof(stream_t *stream)
@@ -558,8 +578,18 @@ static int get_one_number(int c, int next, stream_t *stream)
 	*p++ = c;
 	for (;;) {
 		long class =  cclass[next + 1];
-		if (!(class & (Dot | Digit | Letter)))
+		if (!(class & (Dot | Digit | Letter))) {
+			if (next == '\'') {
+				int after = peekchar(stream);
+				if (cclass[after + 1] & (Digit | Letter)) {
+					if (p != buffer_end)
+						*p++ = next;
+					next = nextchar(stream);
+					continue;
+				}
+			}
 			break;
+		}
 		if (p != buffer_end)
 			*p++ = next;
 		next = nextchar(stream);
@@ -987,6 +1017,7 @@ static struct token *setup_stream(stream_t *stream, int idx, int fd,
 	stream->offset = 0;
 	stream->size = buf_size;
 	stream->buffer = buf;
+	stream->pushed_char = -2;
 
 	begin = alloc_token(stream);
 	token_type(begin) = TOKEN_STREAMBEGIN;
diff --git a/validation/c23-digit-separators-error.c b/validation/c23-digit-separators-error.c
new file mode 100644
index 000000000000..10ddeffcd99c
--- /dev/null
+++ b/validation/c23-digit-separators-error.c
@@ -0,0 +1,36 @@
+int a = 1''0;
+int b = 0x'1;
+int b2 = 0b'1;
+int c = 1'u;
+int c2 = 123'u;
+double d = 1.'0;
+double e = 1'.';
+double f = 1e'0;
+double g = 1'e0;
+double h = 1e+'0;
+float i = 0x1p'0f;
+float j = 0x1.0p1'f;
+float k = 1.0'f;
+
+/*
+ * check-name: c23-digit-separators-error
+ * check-command: sparse -std=c23 -Wno-decl $file
+ *
+ * check-error-start
+c23-digit-separators-error.c:1:10: error: Expected ; at end of declaration
+c23-digit-separators-error.c:1:10: error: got ''
+c23-digit-separators-error.c:2:9: error: digit separator after base indicator
+c23-digit-separators-error.c:3:10: error: digit separator after base indicator
+c23-digit-separators-error.c:4:9: error: digit separator outside digit sequence
+c23-digit-separators-error.c:5:10: error: digit separator outside digit sequence
+c23-digit-separators-error.c:6:12: error: digit separator adjacent to decimal point
+c23-digit-separators-error.c:7:13: error: Expected ; at end of declaration
+c23-digit-separators-error.c:7:13: error: got '.'
+c23-digit-separators-error.c:8:12: error: digit separator adjacent to exponent
+c23-digit-separators-error.c:9:12: error: digit separator adjacent to exponent
+c23-digit-separators-error.c:10:12: error: digit separator adjacent to exponent
+c23-digit-separators-error.c:11:11: error: digit separator adjacent to exponent
+c23-digit-separators-error.c:12:11: error: digit separator outside digit sequence
+c23-digit-separators-error.c:13:11: error: digit separator outside digit sequence
+ * check-error-end
+ */
diff --git a/validation/c23-digit-separators-pedantic.c b/validation/c23-digit-separators-pedantic.c
new file mode 100644
index 000000000000..d1f374d805e1
--- /dev/null
+++ b/validation/c23-digit-separators-pedantic.c
@@ -0,0 +1,12 @@
+int a = 1'000;
+double b = 1'0.5;
+
+/*
+ * check-name: c23-digit-separators-pedantic
+ * check-command: sparse -std=c17 -Wpedantic -Wno-decl $file
+ *
+ * check-error-start
+c23-digit-separators-pedantic.c:1:9: warning: ISO C does not support digit separators before C23
+c23-digit-separators-pedantic.c:2:12: warning: ISO C does not support digit separators before C23
+ * check-error-end
+ */
diff --git a/validation/c23-digit-separators.c b/validation/c23-digit-separators.c
new file mode 100644
index 000000000000..65af74822cb6
--- /dev/null
+++ b/validation/c23-digit-separators.c
@@ -0,0 +1,29 @@
+#if 0
+0' '
+1'+'2
+#endif
+
+#if 1'000 + 2'000 == 3'000
+int pp = 1;
+#endif
+
+_Static_assert(1'000'000 == 1000000, "dec");
+_Static_assert(0xdead'beef == 0xdeadbeef, "hex");
+_Static_assert(0XCAFE'BABE == 0xCAFEBABE, "hex upper");
+_Static_assert(0b1010'0101 == 0xa5, "bin");
+_Static_assert(0B1111'0000 == 0xf0, "bin upper");
+_Static_assert(0'123'456 == 0123456, "oct");
+_Static_assert(1'000ULL == 1000ULL, "ull");
+
+static int a = 1'000'000;
+static unsigned int b = 0xdead'beef;
+static int c = 0b1010'0101;
+static int d = 0'123'456;
+static double e = 1'000.000'1;
+static double f = 1'2.3'4e1'0;
+static float g = 0x1'a.b'cp1'0f;
+
+/*
+ * check-name: c23-digit-separators
+ * check-command: sparse -std=c23 -Wno-decl $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 ` Bart Van Assche [this message]
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 ` [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-46-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