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
+ */
next prev 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