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 52/73] c23: Support standard [[]] attribute syntax
Date: Mon,  9 Nov 2026 12:32:38 -0800	[thread overview]
Message-ID: <20261109203259.362845-53-bvanassche@acm.org> (raw)
In-Reply-To: <20261109203259.362845-1-bvanassche@acm.org>

Parse C23 '[[]]' attribute specifier sequences across declarations,
declarators, types, structs, enums, parameters, labels, and statements
per ISO C23 6.7.12:
- Map standard attributes (nodiscard, maybe_unused, deprecated,
  fallthrough, noreturn, _Noreturn, reproducible, unsequenced) and
  gnu::/__gnu__:: scoped attributes to their Sparse modifiers, while
  ignoring unknown vendor-scoped attributes cleanly.
- Support '[[]]' attributes after declarator identifiers and after
  function parameter lists without misapplying function modifiers to
  pointer declarators.
- Distinguish attributed case and default labels from named goto labels
  inside switch statements, and warn under -Wpedantic in pre-C23 modes.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 ident-list.h                         |   2 +
 parse.c                              | 275 +++++++++++++++++++++++----
 symbol.h                             |   7 +-
 validation/attr_in_parameter.c       |   2 +
 validation/c23-attributes-error.c    |  20 ++
 validation/c23-attributes-pedantic.c |  10 +
 validation/c23-attributes.c          | 149 +++++++++++++++
 validation/c23-param-attribute.c     |  28 +++
 8 files changed, 448 insertions(+), 45 deletions(-)
 create mode 100644 validation/c23-attributes-error.c
 create mode 100644 validation/c23-attributes-pedantic.c
 create mode 100644 validation/c23-attributes.c
 create mode 100644 validation/c23-param-attribute.c

diff --git a/ident-list.h b/ident-list.h
index 46e6cd9e4f61..bc5df947913a 100644
--- a/ident-list.h
+++ b/ident-list.h
@@ -65,6 +65,8 @@ IDENT_RESERVED(__sizeof_ptr__);
 IDENT_RESERVED(__builtin_types_compatible_p);
 IDENT_RESERVED(__builtin_offsetof);
 IDENT_RESERVED(__label__);
+IDENT(gnu);
+IDENT(__gnu__);
 
 /* Preprocessor idents.  Direct use of __IDENT avoids mentioning the keyword
  * itself by name, preventing these tokens from expanding when compiling
diff --git a/parse.c b/parse.c
index 48793dd51671..13b8cf5d43c1 100644
--- a/parse.c
+++ b/parse.c
@@ -50,6 +50,13 @@ 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 inline bool match_c23_attribute(struct token *token)
+{
+	return match_op(token, '[') && match_op(token->next, '[');
+}
+static struct token *c23_attribute_specifier(struct token *token,
+					     struct decl_state *ctx);
+static struct token *skip_c23_attributes(struct token *token);
 static struct token *skip_attributes(struct token *token);
 
 typedef struct token *declarator_t(struct token *, struct symbol *, struct decl_state *);
@@ -563,6 +570,7 @@ static struct init_keyword {
 	D("noderef",		&attr_mod_op,		.mods = MOD_NODEREF),
 	D("safe",		&attr_mod_op,		.mods = MOD_SAFE),
 	D("unused",		&attr_mod_op,		.mods = MOD_UNUSED),
+	D("maybe_unused",	&attr_mod_op,		.mods = MOD_UNUSED),
 	D("externally_visible",	&attr_mod_op,		.mods = MOD_EXT_VISIBLE),
 	D("force",		&attr_force_op),
 	D("bitwise",		&attr_bitwise_op,	.mods = MOD_BITWISE),
@@ -570,9 +578,14 @@ static struct init_keyword {
 	D("designated_init",	&designated_init_op),
 	D("transparent_union",	&transparent_union_op),
 	D("noreturn",		&attr_fun_op,		.mods = MOD_NORETURN),
+	D("_Noreturn",		&attr_fun_op,		.mods = MOD_NORETURN),
 	D("pure",		&attr_fun_op,		.mods = MOD_PURE),
 	A("const",		&attr_fun_op,		.mods = MOD_PURE),
 	D("gnu_inline",		&attr_fun_op,		.mods = MOD_GNU_INLINE),
+	D("nodiscard",		&ignore_attr_op),
+	D("fallthrough",	&ignore_attr_op),
+	D("unsequenced",	&attr_fun_op,		.mods = MOD_PURE),
+	D("reproducible",	&attr_fun_op,		.mods = MOD_PURE),
 
 	/* Modes */
 	D("mode",		&mode_op),
@@ -1666,22 +1679,29 @@ static struct token *attribute_transparent_union(struct token *token, struct sym
 	return token;
 }
 
-static struct token *recover_unknown_attribute(struct token *token)
+static struct token *skip_parens(struct token *token)
 {
-	struct expression *expr = NULL;
-
-	if (Wunknown_attribute)
-		warning(token->pos, "unknown attribute '%s'", show_ident(token->ident));
-	token = token->next;
 	if (match_op(token, '(')) {
-		if (match_op(token->next, ')'))
-			token = token->next->next;
-		else
-			token = parens_expression(token, &expr, "in attribute");
+		int depth = 1;
+		token = token->next;
+		while (depth && !eof_token(token)) {
+			if (match_op(token, '('))
+				depth++;
+			else if (match_op(token, ')'))
+				depth--;
+			token = token->next;
+		}
 	}
 	return token;
 }
 
+static struct token *recover_unknown_attribute(struct token *token)
+{
+	if (Wunknown_attribute)
+		warning(token->pos, "unknown attribute '%s'", show_ident(token->ident));
+	return skip_parens(token->next);
+}
+
 static struct token *attribute_specifier(struct token *token, struct symbol *sym, struct decl_state *ctx)
 {
 	token = expect(token, '(', "after attribute");
@@ -1863,7 +1883,11 @@ struct symbol *ctype_integer(int size, int want_unsigned)
 
 static struct token *handle_qualifiers(struct token *t, struct decl_state *ctx)
 {
-	while (token_type(t) == TOKEN_IDENT) {
+	while (token_type(t) == TOKEN_IDENT || match_c23_attribute(t)) {
+		if (match_c23_attribute(t)) {
+			t = c23_attribute_specifier(t, ctx);
+			continue;
+		}
 		struct symbol *s = lookup_keyword(t->ident, NS_TYPEDEF);
 		if (!s || !s->op)
 			break;
@@ -1882,7 +1906,11 @@ static struct token *declaration_specifiers(struct token *token, struct decl_sta
 	int class = CInt;
 	int rank = 0;
 
-	while (token_type(token) == TOKEN_IDENT) {
+	while (token_type(token) == TOKEN_IDENT || match_c23_attribute(token)) {
+		if (match_c23_attribute(token)) {
+			token = c23_attribute_specifier(token, ctx);
+			continue;
+		}
 		struct symbol *s = lookup_symbol(token->ident,
 						 NS_TYPEDEF | NS_SYMBOL);
 		if (!s || !(s->namespace & NS_TYPEDEF))
@@ -2040,16 +2068,22 @@ static bool match_attribute(struct token *token)
 
 static struct token *skip_attribute(struct token *token)
 {
-	token = token->next;
-	if (match_op(token, '(')) {
-		int depth = 1;
-		token = token->next;
-		while (depth && !eof_token(token)) {
-			if (token_type(token) == TOKEN_SPECIAL) {
-				if (token->special == '(')
-					depth++;
-				else if (token->special == ')')
-					depth--;
+	return skip_parens(token->next);
+}
+
+static struct token *skip_c23_attributes(struct token *token)
+{
+	while (match_c23_attribute(token)) {
+		token = token->next->next;
+		while (!eof_token(token)) {
+			if (match_op(token, ']') &&
+			    match_op(token->next, ']')) {
+				token = token->next->next;
+				break;
+			}
+			if (match_op(token, '(')) {
+				token = skip_parens(token);
+				continue;
 			}
 			token = token->next;
 		}
@@ -2057,27 +2091,113 @@ static struct token *skip_attribute(struct token *token)
 	return token;
 }
 
-static struct token *skip_attributes(struct token *token)
+static struct token *c23_attribute_specifier(struct token *token,
+					     struct decl_state *ctx)
 {
-	while (match_attribute(token)) {
-		token = expect(token->next, '(', "after attribute");
-		token = expect(token, '(', "after attribute");
-		while (token_type(token) == TOKEN_IDENT) {
-			token = skip_attribute(token);
-			if (!match_op(token, ','))
+	if (standard < STANDARD_C23 && Wpedantic)
+		warning(token->pos, "C23 attribute syntax is a C23 extension");
+
+	token = expect(token, '[', "in attribute specifier");
+	token = expect(token, '[', "in attribute specifier");
+
+	while (!match_op(token, ']') && !eof_token(token)) {
+		struct ident *prefix = NULL;
+		struct ident *attr_ident = NULL;
+		struct token *id_tok = NULL;
+
+		if (match_op(token, ',')) {
+			token = token->next;
+			continue;
+		}
+
+		if (token_type(token) != TOKEN_IDENT) {
+			sparse_error(token->pos, "expected attribute name");
+			break;
+		}
+
+		id_tok = token;
+		attr_ident = token->ident;
+		token = token->next;
+
+		if (match_op(token, ':') && match_op(token->next, ':')) {
+			prefix = attr_ident;
+			token = token->next->next;
+			if (token_type(token) != TOKEN_IDENT) {
+				sparse_error(
+					token->pos,
+					"expected attribute name after '::'");
 				break;
+			}
+			id_tok = token;
+			attr_ident = token->ident;
 			token = token->next;
 		}
-		token = expect(token, ')', "after attribute");
-		token = expect(token, ')', "after attribute");
+
+		if (!prefix || prefix == &gnu_ident ||
+		    prefix == &__gnu___ident) {
+			struct symbol *attr =
+				lookup_keyword(attr_ident, NS_KEYWORD);
+			if (attr && attr->op && attr->op->attribute)
+				token = attr->op->attribute(token, attr, ctx);
+			else {
+				if (Wunknown_attribute)
+					warning(id_tok->pos,
+						"unknown attribute '%s'",
+						show_ident(attr_ident));
+				token = skip_parens(token);
+			}
+		} else {
+			/* Other vendor prefixes (e.g. clang::...): ignore */
+			token = skip_parens(token);
+		}
+
+		if (match_op(token, ','))
+			token = token->next;
+		else if (match_op(token, ']'))
+			break;
+		else {
+			sparse_error(token->pos,
+				     "expected ']' or ',' after attribute");
+			break;
+		}
+	}
+
+	token = expect(token, ']', "at end of attribute specifier");
+	token = expect(token, ']', "at end of attribute specifier");
+	return token;
+}
+
+static struct token *skip_attributes(struct token *token)
+{
+	for (;;) {
+		if (match_attribute(token)) {
+			token = expect(token->next, '(', "after attribute");
+			token = expect(token, '(', "after attribute");
+			while (token_type(token) == TOKEN_IDENT) {
+				token = skip_attribute(token);
+				if (!match_op(token, ','))
+					break;
+				token = token->next;
+			}
+			token = expect(token, ')', "after attribute");
+			token = expect(token, ')', "after attribute");
+		} else if (match_c23_attribute(token)) {
+			token = skip_c23_attributes(token);
+		} else {
+			break;
+		}
 	}
 	return token;
 }
 
 static struct token *handle_attributes(struct token *token, struct decl_state *ctx)
 {
-	while (match_attribute(token))
-		token = attribute_specifier(token->next, NULL, ctx);
+	while (match_attribute(token) || match_c23_attribute(token)) {
+		if (match_attribute(token))
+			token = attribute_specifier(token->next, NULL, ctx);
+		else
+			token = c23_attribute_specifier(token, ctx);
+	}
 	return token;
 }
 
@@ -2115,10 +2235,10 @@ static enum kind which_func(struct token *token,
 			    struct ident **n,
 			    int prefer_abstract)
 {
-	struct token *next = token->next;
+	struct token *next = skip_attributes(token->next);
 
 	if (token_type(next) == TOKEN_IDENT) {
-		if (lookup_type(next))
+		if (lookup_type(next) || next != token->next)
 			return Proto;
 		/* identifier list not in definition; complain */
 		if (prefer_abstract)
@@ -2160,6 +2280,24 @@ static struct token *direct_declarator(struct token *token, struct decl_state *c
 	if (ctx->ident && token_type(token) == TOKEN_IDENT) {
 		*ctx->ident = token->ident;
 		token = token->next;
+		if (match_c23_attribute(token)) {
+			struct decl_state attr = { };
+			token = handle_attributes(token, &attr);
+			ctx->ident_modifiers |= (attr.ctype.modifiers & MOD_DECLARE) |
+						attr.f_modifiers;
+			if (attr.cleanup)
+				ctx->cleanup = attr.cleanup;
+			if (attr.packed)
+				ctx->packed = attr.packed;
+			if (attr.transparent_union)
+				ctx->transparent_union = attr.transparent_union;
+			if (attr.designated_init)
+				ctx->designated_init = attr.designated_init;
+			if (attr.ctype.alignment > ctx->ctype.alignment)
+				ctx->ctype.alignment = attr.ctype.alignment;
+			attr.ctype.modifiers &= ~MOD_DECLARE;
+			apply_ctype(token->pos, &ctx->ctype, &attr.ctype);
+		}
 	} else if (match_op(token, '(') &&
 	    is_nested(token, &next, ctx->prefer_abstract)) {
 		struct symbol *base_type = ctype->base_type;
@@ -2191,16 +2329,24 @@ static struct token *direct_declarator(struct token *token, struct decl_state *c
 			token = parameter_type_list(token, fn);
 		token = expect(token, ')', "in function declarator");
 		fn->endpos = token->pos;
+		if (match_c23_attribute(token)) {
+			struct decl_state attr = { };
+			token = handle_attributes(token, &attr);
+			ctype->modifiers |= attr.f_modifiers;
+			apply_ctype(token->pos, ctype, &attr.ctype);
+		}
 		return token;
 	}
 
-	while (match_op(token, '[')) {
+	while (match_op(token, '[') && !match_c23_attribute(token)) {
 		struct symbol *array;
 		array = alloc_indirect_symbol(token->pos, ctype, SYM_ARRAY);
 		token = abstract_array_declarator(token->next, array);
 		token = expect(token, ']', "in abstract_array_declarator");
 		array->endpos = token->pos;
 		ctype = &array->ctype;
+		if (match_c23_attribute(token))
+			token = handle_attributes(token, ctx);
 	}
 	return token;
 }
@@ -2295,6 +2441,7 @@ static struct token *declaration_list(struct token *token, struct symbol_list **
 	for (;;) {
 		struct symbol *decl = alloc_symbol(token->pos, SYM_NODE);
 		ctx.cleanup = NULL;
+		ctx.ident_modifiers = 0;
 		ctx.ident = &decl->ident;
 
 		token = declarator(token, &ctx);
@@ -2302,6 +2449,7 @@ static struct token *declaration_list(struct token *token, struct symbol_list **
 			token = handle_bitfield(token, &ctx);
 
 		token = handle_attributes(token, &ctx);
+		ctx.ctype.modifiers |= ctx.ident_modifiers;
 		apply_modifiers(token->pos, &ctx);
 
 		decl->ctype = ctx.ctype;
@@ -2366,6 +2514,7 @@ static struct token *parameter_declaration(struct token *token, struct symbol *s
 	ctx.ident = &sym->ident;
 	token = declarator(token, &ctx);
 	token = handle_attributes(token, &ctx);
+	ctx.ctype.modifiers |= ctx.ident_modifiers;
 	apply_modifiers(token->pos, &ctx);
 	sym->ctype = ctx.ctype;
 	sym->ctype.modifiers |= decl_modifiers(&ctx);
@@ -2412,6 +2561,8 @@ struct token *typename(struct token *token, struct symbol **p, int *forced)
 		ctx.ctype.base_type = &bad_ctype;
 	}
 	token = declarator(token, &ctx);
+	token = handle_attributes(token, &ctx);
+	ctx.ctype.modifiers |= ctx.ident_modifiers;
 	apply_modifiers(token->pos, &ctx);
 	sym->ctype = ctx.ctype;
 	sym->endpos = token->pos;
@@ -2700,7 +2851,10 @@ static struct token *parse_for_statement(struct token *token, struct statement *
 	syms = NULL;
 	e1 = NULL;
 	/* C99 variable declaration? */
-	if (lookup_type(token)) {
+	struct token *next = token;
+	if (match_c23_attribute(token))
+		next = skip_c23_attributes(token);
+	if (lookup_type(next)) {
 		token = external_declaration(token, &syms, validate_for_loop_decl);
 	} else {
 		token = parse_expression(token, &e1);
@@ -2873,13 +3027,13 @@ static struct token *handle_label_attributes(struct token *token, struct symbol
 	struct decl_state ctx = { };
 
 	token = handle_attributes(token, &ctx);
-	label->label_modifiers = ctx.ctype.modifiers;
+	label->label_modifiers |= ctx.ctype.modifiers;
 	return token;
 }
 
 static bool is_label_token(struct token *token)
 {
-	struct token *p = token;
+	struct token *p = skip_c23_attributes(token);
 	struct symbol *s;
 
 	if (token_type(p) != TOKEN_IDENT)
@@ -2903,6 +3057,8 @@ static struct token *parse_one_label(struct token *token,
 
 	*stmtp = NULL;
 	*is_case = false;
+	if (match_c23_attribute(token))
+		token = handle_attributes(token, &attr_state);
 
 	if (token->ident == &case_ident) {
 		stmt = alloc_statement(token->pos, STMT_CASE);
@@ -2950,7 +3106,7 @@ static struct token *parse_one_label(struct token *token,
 
 static struct token *statement(struct token *token, struct statement **tree)
 {
-	struct statement *stmt = alloc_statement(token->pos, STMT_NONE);
+	struct statement *stmt;
 
 	if (is_label_token(token)) {
 		struct statement *lbl = NULL;
@@ -2971,6 +3127,27 @@ static struct token *statement(struct token *token, struct statement **tree)
 		return statement(token, tree);
 	}
 
+	if (match_c23_attribute(token)) {
+		struct token *next = skip_c23_attributes(token);
+		if (lookup_type(next)) {
+			sparse_error(token->pos, "declaration is not a statement");
+			stmt = alloc_statement(token->pos, STMT_DECLARATION);
+			*tree = stmt;
+			start_block_scope();
+			token = external_declaration(token, &stmt->declaration, NULL);
+			end_block_scope();
+			return token;
+		}
+		struct decl_state attr_state = {};
+		token = handle_attributes(token, &attr_state);
+		if (match_op(token, ';')) {
+			stmt = alloc_statement(token->pos, STMT_NONE);
+			*tree = stmt;
+			return token->next;
+		}
+		return statement(token, tree);
+	}
+	stmt = alloc_statement(token->pos, STMT_NONE);
 	*tree = stmt;
 	if (token_type(token) == TOKEN_IDENT) {
 		struct symbol *s = lookup_keyword(token->ident, NS_KEYWORD);
@@ -3031,6 +3208,7 @@ static struct token * statement_list(struct token *token, struct statement_list
 		struct statement *label_head = NULL;
 		struct statement **label_tail = &label_head;
 		bool last_is_case = false;
+		struct token *next;
 
 		while (is_label_token(token)) {
 			struct statement *lbl = NULL;
@@ -3054,10 +3232,13 @@ static struct token * statement_list(struct token *token, struct statement_list
 				add_statement(list, label_head);
 				break;
 			}
+			next = match_c23_attribute(token) ?
+				       skip_c23_attributes(token) :
+				       token;
 			if (match_ident(token, &_Static_assert_ident) ||
 			    (standard >= STANDARD_C23 &&
 			     match_ident(token, &static_assert_ident)) ||
-			    lookup_type(token)) {
+			    lookup_type(next)) {
 				if (standard < STANDARD_C23 && Wpedantic)
 					warning(token->pos,
 						"a label can only be part of a statement and a declaration is not a statement");
@@ -3081,7 +3262,10 @@ static struct token * statement_list(struct token *token, struct statement_list
 			token = parse_static_assert(token, NULL);
 			continue;
 		}
-		if (lookup_type(token)) {
+		next = token;
+		if (match_c23_attribute(token))
+			next = skip_c23_attributes(token);
+		if (lookup_type(next)) {
 			if (seen_statement) {
 				warning(token->pos, "mixing declarations and code");
 				seen_statement = 0;
@@ -3457,6 +3641,7 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 	struct symbol *decl;
 	struct decl_state ctx = { .ident = &ident };
 	struct ctype saved;
+	unsigned long saved_f_modifiers;
 	struct symbol *base_type;
 	unsigned long mod;
 	int is_typedef;
@@ -3495,9 +3680,11 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 			     "'enum' underlying type may not be specified here");
 
 	saved = ctx.ctype;
+	saved_f_modifiers = ctx.f_modifiers;
 	token = declarator(token, &ctx);
 	token = handle_asm_name(token, &ctx);
 	token = handle_attributes(token, &ctx);
+	ctx.ctype.modifiers |= ctx.ident_modifiers;
 	apply_modifiers(token->pos, &ctx);
 
 	decl->ctype = ctx.ctype;
@@ -3753,10 +3940,13 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		decl = alloc_symbol(token->pos, SYM_NODE);
 		ctx.ctype = saved;
 		ctx.cleanup = NULL;
+		ctx.ident_modifiers = 0;
+		ctx.f_modifiers = saved_f_modifiers;
 		token = handle_attributes(token, &ctx);
 		token = declarator(token, &ctx);
 		token = handle_asm_name(token, &ctx);
 		token = handle_attributes(token, &ctx);
+		ctx.ctype.modifiers |= ctx.ident_modifiers;
 		apply_modifiers(token->pos, &ctx);
 		decl->ctype = ctx.ctype;
 		decl->ctype.modifiers |= mod;
@@ -3781,6 +3971,7 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		/* Function declarations are automatically extern unless specifically static */
 		base_type = decl->ctype.base_type;
 		if (!is_typedef && base_type && base_type->type == SYM_FN) {
+			decl->ctype.modifiers |= ctx.f_modifiers;
 			if (!(decl->ctype.modifiers & MOD_STATIC))
 				decl->ctype.modifiers |= MOD_EXTERN;
 		}
diff --git a/symbol.h b/symbol.h
index 8035d9d21b3c..a072ac8fc739 100644
--- a/symbol.h
+++ b/symbol.h
@@ -110,6 +110,7 @@ struct decl_state {
 	struct symbol_op *mode;
 	struct expression *cleanup;
 	unsigned long f_modifiers;		// function attributes
+	unsigned long ident_modifiers;		// declarator-id C23 attributes
 	unsigned long storage_class;
 	unsigned char prefer_abstract;
 	unsigned char autotype;
@@ -272,11 +273,11 @@ struct symbol {
 #define MOD_SPECIFIER	MOD_SIGNEDNESS
 #define MOD_IGNORE	(MOD_STORAGE | MOD_ACCESS | MOD_USERTYPE | MOD_EXPLICITLY_SIGNED | MOD_EXT_VISIBLE | MOD_UNUSED | MOD_GNU_INLINE | MOD_CONSTEXPR)
 #define MOD_QUALIFIER	(MOD_CONST | MOD_VOLATILE | MOD_RESTRICT)
-#define MOD_PTRINHERIT	(MOD_QUALIFIER | MOD_ATOMIC | MOD_NODEREF | MOD_NORETURN | MOD_NOCAST)
-/* modifiers preserved by typeof() operator */
-#define MOD_TYPEOF	(MOD_QUALIFIER | MOD_ATOMIC | MOD_NOCAST | MOD_SPECIFIER)
 /* modifiers for function attributes */
 #define MOD_FUN_ATTR	(MOD_PURE|MOD_NORETURN)
+#define MOD_PTRINHERIT	(MOD_QUALIFIER | MOD_ATOMIC | MOD_NODEREF | MOD_FUN_ATTR | MOD_NOCAST)
+/* modifiers preserved by typeof() operator */
+#define MOD_TYPEOF	(MOD_QUALIFIER | MOD_ATOMIC | MOD_NOCAST | MOD_SPECIFIER)
 /* like cvr-qualifiers but 'reversed' (OK: source <= target) */
 #define MOD_REV_QUAL	(MOD_PURE|MOD_NORETURN)
 /* do not warn when these are duplicated */
diff --git a/validation/attr_in_parameter.c b/validation/attr_in_parameter.c
index 1b104ea43ec5..33d8b85aa5cb 100644
--- a/validation/attr_in_parameter.c
+++ b/validation/attr_in_parameter.c
@@ -7,6 +7,8 @@ static void g(int A *x)
 	f(x, x);
 	p = q;
 }
+static void h(__attribute__((unused)) int x);
+
 /*
  * check-name: attribute after ( in direct-declarator
  */
diff --git a/validation/c23-attributes-error.c b/validation/c23-attributes-error.c
new file mode 100644
index 000000000000..a78086311c1c
--- /dev/null
+++ b/validation/c23-attributes-error.c
@@ -0,0 +1,20 @@
+[[nodiscard] static int unclosed;
+[[gnu::]] static int missing_scoped;
+
+static void test_if_attr_decl(int c)
+{
+	if (c)
+		[[maybe_unused]] int z = 3;
+}
+
+/*
+ * check-name: c23-attributes-error
+ * check-command: sparse -std=c23 $file
+ *
+ * check-error-start
+c23-attributes-error.c:1:14: error: Expected ] at end of attribute specifier
+c23-attributes-error.c:1:14: error: got static
+c23-attributes-error.c:2:8: error: expected attribute name after '::'
+c23-attributes-error.c:7:17: error: declaration is not a statement
+ * check-error-end
+ */
diff --git a/validation/c23-attributes-pedantic.c b/validation/c23-attributes-pedantic.c
new file mode 100644
index 000000000000..224f36f5bdf3
--- /dev/null
+++ b/validation/c23-attributes-pedantic.c
@@ -0,0 +1,10 @@
+[[maybe_unused]] static int x;
+
+/*
+ * check-name: C23 [[]] standard attribute syntax pedantic warning
+ * check-command: sparse -std=c11 -Wpedantic $file
+ *
+ * check-error-start
+c23-attributes-pedantic.c:1:1: warning: C23 attribute syntax is a C23 extension
+ * check-error-end
+ */
diff --git a/validation/c23-attributes.c b/validation/c23-attributes.c
new file mode 100644
index 000000000000..8b53ce06d0cf
--- /dev/null
+++ b/validation/c23-attributes.c
@@ -0,0 +1,149 @@
+[[maybe_unused]] static int unused_var;
+
+[[nodiscard]] static int get_val(void)
+{
+	return 42;
+}
+
+[[deprecated("use new_func instead")]] static void old_func(void)
+{
+}
+
+[[noreturn]] static void fatal(void)
+{
+	for (;;) {
+	}
+}
+
+[[_Noreturn]] static void fatal2(void)
+{
+	for (;;) {
+	}
+}
+
+static int pure_add(int a, int b) [[unsequenced]]
+{
+	return a + b;
+}
+
+static int pure_mul(int a, int b) [[reproducible]]
+{
+	return a * b;
+}
+
+static int (*fp_unseq)(int, int) [[unsequenced]] = pure_add;
+typedef int (*plain_int_fp_t)(int);
+typedef int (__attribute__((__pure__)) *repro_int_fp_t)(int);
+static int plain_inc(int a)
+{
+	return a + 1;
+}
+static int repro_inc(int a) [[reproducible]]
+{
+	return a + 1;
+}
+static int (*get_plain_fp(void) [[reproducible]])(int)
+{
+	return plain_inc;
+}
+static int (*get_repro_fp(void))(int) [[reproducible]]
+{
+	return repro_inc;
+}
+static_assert([typeof(&get_plain_fp)] ==
+	      [plain_int_fp_t (__attribute__((__pure__)) *)(void)],
+	      "inner [[]] applies to outer function");
+static_assert([typeof(get_plain_fp())] == [plain_int_fp_t],
+	      "inner [[]] does not leak to returned function pointer");
+static_assert([typeof(&get_repro_fp)] == [repro_int_fp_t (*)(void)],
+	      "outer [[]] does not leak to outer function");
+static_assert([typeof(get_repro_fp())] == [repro_int_fp_t],
+	      "outer [[]] applies to returned function pointer");
+
+struct [[deprecated]] S {
+	[[maybe_unused]] int field;
+};
+
+enum [[deprecated]] E {
+	ITEM1 [[deprecated]],
+	ITEM2,
+};
+
+typedef int T_attr_label;
+
+static void param_fn([[maybe_unused]] int a, int b [[maybe_unused]],
+		     [[maybe_unused]] int)
+{
+	(void)a;
+	(void)b;
+}
+
+struct [[gnu::packed]] PackedStruct {
+	char c;
+	int i;
+};
+static_assert(sizeof(struct PackedStruct) == sizeof(char) + sizeof(int),
+	      "packed");
+
+struct [[gnu::aligned(16)]] AlignedStruct {
+	int x;
+};
+static_assert(alignof(struct AlignedStruct) == 16, "align");
+
+[[gnu::aligned(16)]] static int aligned_var;
+
+[[maybe_unused]] [[nodiscard]] static int multi_attr(void)
+{
+	return 0;
+}
+
+[[maybe_unused, nodiscard]] static int comma_attr(void)
+{
+	return 0;
+}
+
+[[]] static int empty_attr;
+[[
+	,
+]] static int empty_attr2;
+
+static int test_statements(int val)
+{
+	[[maybe_unused]] int x = val;
+	if (x < 0)
+		goto lbl1;
+	switch (x) {
+	case 1:
+		x++;
+		[[fallthrough]];
+	case 2:
+		return x;
+	[[maybe_unused]] case 3:
+		return 3;
+	[[maybe_unused]] default:
+		break;
+	}
+
+lbl1:
+	[[]];
+[[maybe_unused]] lbl2:
+	[[maybe_unused]] int y = 10;
+	if (!y)
+		goto T_attr_label;
+[[maybe_unused]] T_attr_label:
+	return y;
+}
+
+[[clang::annotate_type("ptr_attr", void *)]] static int *vendor_attr_var;
+[[vendor::custom(struct foo, (1 + 2))]] static int vendor_attr_var2;
+
+static void fn_after_ident [[deprecated]] (void)
+{
+}
+
+static int arr_after_ident [[maybe_unused]] [4];
+
+/*
+ * check-name: C23 [[]] standard attribute syntax
+ * check-command: sparse -std=c23 $file
+ */
diff --git a/validation/c23-param-attribute.c b/validation/c23-param-attribute.c
new file mode 100644
index 000000000000..a89c025ed002
--- /dev/null
+++ b/validation/c23-param-attribute.c
@@ -0,0 +1,28 @@
+/*
+ * Check that function declarators properly parse parameter lists
+ * where parameters have standard C23 attributes.
+ */
+
+static void f1([[maybe_unused]] int x)
+{
+}
+
+static int f2([[maybe_unused]] int a, [[maybe_unused]] int b)
+{
+	return a;
+}
+
+static void (*fp)([[maybe_unused]] int);
+
+static void test_call(void)
+{
+	f1(10);
+	f2(1, 2);
+	fp = f1;
+	fp(20);
+}
+
+/*
+ * check-name: C23 parameter attributes
+ * 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 ` [PATCH 51/73] c23: Support enumerations with fixed underlying types Bart Van Assche
2026-11-09 20:32 ` Bart Van Assche [this message]
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-53-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