[RFC PATCH] pre-process: add __VA_OPT__ support
Eric Zhang <[email protected]> Tue, 24 Feb 2026 21:29:50 -0800
| Newsgroups | org.kernel.vger.linux-sparse |
|---|---|
| Message-ID | <[email protected]> |
Add __VA_OPT__ support (C23 6.10.5) including some tests. At expansion time, substitute() checks whether the variadic argument is empty: if so, skip the region between the markers; otherwise, process the enclosed tokens normally. Signed-off-by: Eric Zhang <[email protected]> --- Discussed this with Chris during lunch yesterday and got curious about the problem, so made a few attempts. Introducing TOKEN_VA_OPT_START/END feels a bit like an anti-pattern, but I couldn't find a cleaner way to handle it without new token types. Happy to hear suggestions. Note: this does not handle the __COUNTER__ side-effect issue (arguments under __VA_OPT__ are expanded even when discarded). ident-list.h | 1 + pre-process.c | 132 ++++++++++++++++++++++++ token.h | 2 + tokenize.c | 6 ++ validation/preprocessor/va-opt-errors.c | 38 +++++++ validation/preprocessor/va-opt.c | 66 ++++++++++++ 6 files changed, 245 insertions(+) create mode 100644 validation/preprocessor/va-opt-errors.c create mode 100644 validation/preprocessor/va-opt.c diff --git a/ident-list.h b/ident-list.h index 3c08e8ca..556d4050 100644 --- a/ident-list.h +++ b/ident-list.h @@ -65,6 +65,7 @@ IDENT(c_generic_selections); IDENT(c_static_assert); __IDENT(pragma_ident, "__pragma__", 0); __IDENT(__VA_ARGS___ident, "__VA_ARGS__", 0); +__IDENT(__VA_OPT___ident, "__VA_OPT__", 0); __IDENT(__func___ident, "__func__", 0); __IDENT(__FUNCTION___ident, "__FUNCTION__", 0); __IDENT(__PRETTY_FUNCTION___ident, "__PRETTY_FUNCTION__", 0); diff --git a/pre-process.c b/pre-process.c index 4e322855..ec4c7b98 100644 --- a/pre-process.c +++ b/pre-process.c @@ -643,13 +643,37 @@ static struct token **substitute(struct token **list, const struct token *body, struct position *base_pos = &(*list)->pos; int *count; enum {Normal, Placeholder, Concat} state = Normal; + int va_opt_ws = 0; for (; !eof_token(body); body = body->next) { struct token *added, *arg; struct token **tail; const struct token *t; + struct arg *va; + int is_empty; switch (token_type(body)) { + case TOKEN_VA_OPT_START: + va = &args[body->argnum]; + is_empty = (!va->arg || eof_token(va->arg)) && + (!va->expanded || eof_token(va->expanded)); + if (is_empty) { + /* empty varargs: skip to end marker */ + while (token_type(body) != TOKEN_VA_OPT_END) + body = body->next; + if (state == Concat) + state = Normal; + else + state = Placeholder; + continue; + } + /* non-empty: skip marker, transfer whitespace */ + va_opt_ws = body->pos.whitespace; + continue; + + case TOKEN_VA_OPT_END: + continue; + case TOKEN_GNU_KLUDGE: /* * GNU kludge: if we had <comma>##<vararg>, behaviour @@ -728,6 +752,10 @@ static struct token **substitute(struct token **list, const struct token *body, if (tail != &added->next) list = tail; } else { + if (va_opt_ws) { + added->pos.whitespace = va_opt_ws; + va_opt_ws = 0; + } *list = added; list = tail; } @@ -747,6 +775,8 @@ static int expand(struct token **list, struct symbol *sym) int nargs = sym->arglist ? sym->arglist->count.normal : 0; struct arg args[nargs]; + memset(args, 0, sizeof(args)); + if (expanding->tainted) { token->pos.noexpand = 1; return 1; @@ -1019,6 +1049,7 @@ static int token_different(struct token *t1, struct token *t2) case TOKEN_UNTAINT: case TOKEN_CONCAT: case TOKEN_GNU_KLUDGE: + case TOKEN_VA_OPT_END: different = 0; break; case TOKEN_NUMBER: @@ -1030,6 +1061,7 @@ static int token_different(struct token *t1, struct token *t2) case TOKEN_MACRO_ARGUMENT: case TOKEN_QUOTED_ARGUMENT: case TOKEN_STR_ARGUMENT: + case TOKEN_VA_OPT_START: different = t1->argnum != t2->argnum; break; case TOKEN_CHAR_EMBEDDED_0 ... TOKEN_CHAR_EMBEDDED_3: @@ -1291,21 +1323,94 @@ Econcat: return NULL; } +static int find_vararg_index(struct token *arglist) +{ + struct token *p; + int nr = 0; + + if (!arglist) + return -1; + for (p = arglist->next; !eof_token(p); p = p->next->next, nr++) { + if (p->next->count.vararg) + return nr; + } + return -1; +} + static struct token *parse_expansion(struct token *expansion, struct token *arglist, struct ident *name) { struct token *token = expansion; struct token **p; + struct token *va_opt_start = NULL; + int in_va_opt = 0; + int va_opt_paren_depth = 0; + int vararg_index = find_vararg_index(arglist); if (match_op(token, SPECIAL_HASHHASH)) goto Econcat; for (p = &expansion; !eof_token(token); p = &token->next, token = *p) { + /* Handle __VA_OPT__(...) */ + if (token_type(token) == TOKEN_IDENT && token->ident == &__VA_OPT___ident) { + struct token *next = token->next; + + if (vararg_index < 0) + goto Eva_opt_nonva; + if (in_va_opt) + goto Eva_opt_nested; + if (!match_op(next, '(')) + goto Eva_opt_paren; + + /* Convert __VA_OPT__ token to TOKEN_VA_OPT_START */ + token_type(token) = TOKEN_VA_OPT_START; + token->argnum = vararg_index; + + /* Remove the '(' token from the list */ + token->next = next->next; + __free_token(next); + + /* C23: ## cannot be first token inside __VA_OPT__ */ + if (match_op(token->next, SPECIAL_HASHHASH)) + goto Eva_opt_hashhash; + + va_opt_start = token; + in_va_opt = 1; + va_opt_paren_depth = 0; + continue; + } + + /* Track parentheses inside __VA_OPT__(...) */ + if (in_va_opt) { + if (match_op(token, '(')) { + va_opt_paren_depth++; + } else if (match_op(token, ')')) { + if (va_opt_paren_depth == 0) { + /* C23: ## cannot be last inside __VA_OPT__ */ + if (token_type(va_opt_start) == TOKEN_CONCAT) + goto Eva_opt_hashhash; + /* This is the closing ) of __VA_OPT__ */ + token_type(token) = TOKEN_VA_OPT_END; + in_va_opt = 0; + continue; + } + va_opt_paren_depth--; + } + } + if (match_op(token, '#')) { token = handle_hash(p, arglist); if (!token) return NULL; } if (match_op(token->next, SPECIAL_HASHHASH)) { + /* C23: ## cannot be last inside __VA_OPT__ */ + if (in_va_opt && va_opt_paren_depth == 0) { + struct token *t = token->next; + while (match_op(t, SPECIAL_HASHHASH)) + t = t->next; + if (match_op(t, ')')) + goto Eva_opt_hashhash; + } token = handle_hashhash(token, arglist); if (!token) return NULL; @@ -1314,7 +1419,15 @@ static struct token *parse_expansion(struct token *expansion, struct token *argl } if (token_type(token) == TOKEN_ERROR) goto Earg; + if (in_va_opt) + va_opt_start = token; } + + if (in_va_opt) { + sparse_error(expansion->pos, "unterminated __VA_OPT__"); + return NULL; + } + token = alloc_token(&expansion->pos); token_type(token) = TOKEN_UNTAINT; token->ident = name; @@ -1328,8 +1441,21 @@ Econcat: Earg: sparse_error(token->pos, "too many instances of argument in body"); return NULL; +Eva_opt_nonva: + sparse_error(token->pos, "__VA_OPT__ can only appear in the expansion of a variadic macro"); + return NULL; +Eva_opt_nested: + sparse_error(token->pos, "__VA_OPT__ may not be nested"); + return NULL; +Eva_opt_paren: + sparse_error(token->pos, "__VA_OPT__ must be followed by '('"); + return NULL; +Eva_opt_hashhash: + sparse_error(token->pos, "'##' cannot appear at either end of __VA_OPT__"); + return NULL; } + static int do_define(struct position pos, struct token *token, struct ident *name, struct token *arglist, struct token *expansion, int attr) { @@ -2316,6 +2442,12 @@ static void dump_macro(struct symbol *sym) case TOKEN_CONCAT: printf("##"); break; + case TOKEN_VA_OPT_START: + printf("__VA_OPT__("); + break; + case TOKEN_VA_OPT_END: + printf(")"); + break; case TOKEN_STR_ARGUMENT: printf("#"); /* fall-through */ diff --git a/token.h b/token.h index 9000e0cb..8e05672b 100644 --- a/token.h +++ b/token.h @@ -104,6 +104,8 @@ enum token_type { TOKEN_QUOTED_ARGUMENT, TOKEN_CONCAT, TOKEN_GNU_KLUDGE, + TOKEN_VA_OPT_START, + TOKEN_VA_OPT_END, TOKEN_UNTAINT, TOKEN_ARG_COUNT, TOKEN_IF, diff --git a/tokenize.c b/tokenize.c index 54ea348c..44b128b7 100644 --- a/tokenize.c +++ b/tokenize.c @@ -237,6 +237,12 @@ const char *show_token(const struct token *token) sprintf(buffer, "<end of '%s'>", stream_name(token->pos.stream)); return buffer; + case TOKEN_VA_OPT_START: + return "__VA_OPT__("; + + case TOKEN_VA_OPT_END: + return ")"; + case TOKEN_UNTAINT: sprintf(buffer, "<untaint>"); return buffer; diff --git a/validation/preprocessor/va-opt-errors.c b/validation/preprocessor/va-opt-errors.c new file mode 100644 index 00000000..392b272b --- /dev/null +++ b/validation/preprocessor/va-opt-errors.c @@ -0,0 +1,38 @@ +/* + * __VA_OPT__ error cases (C23 6.10.5) + * + * Constraints from C23 (N3220 6.10.5): + * - __VA_OPT__ shall only occur in the replacement-list of a + * function-like macro that uses the ellipsis notation. + * - __VA_OPT__ shall not appear within its own replacement tokens. + * - ## shall not appear at either end of __VA_OPT__(). + */ + +/* non-variadic macro */ +#define NONVAR(x) __VA_OPT__(,) + +/* nested __VA_OPT__ */ +#define NESTED(...) __VA_OPT__(__VA_OPT__(x)) + +/* not followed by ( */ +#define NOPAREN(...) __VA_OPT__ x + +/* ## at start */ +#define HASH_START(...) __VA_OPT__(## x) + +/* ## at end */ +#define HASH_END(...) __VA_OPT__(x ##) + +/* + * check-name: __VA_OPT__ errors (C23) + * check-command: sparse -E $file + * check-output-ignore + * + * check-error-start +preprocessor/va-opt-errors.c:12:19: error: __VA_OPT__ can only appear in the expansion of a variadic macro +preprocessor/va-opt-errors.c:15:32: error: __VA_OPT__ may not be nested +preprocessor/va-opt-errors.c:18:22: error: __VA_OPT__ must be followed by '(' +preprocessor/va-opt-errors.c:21:25: error: '##' cannot appear at either end of __VA_OPT__ +preprocessor/va-opt-errors.c:24:34: error: '##' cannot appear at either end of __VA_OPT__ + * check-error-end + */ diff --git a/validation/preprocessor/va-opt.c b/validation/preprocessor/va-opt.c new file mode 100644 index 00000000..52814fc2 --- /dev/null +++ b/validation/preprocessor/va-opt.c @@ -0,0 +1,66 @@ +/* + * __VA_OPT__ support (C23 6.10.5) + */ + +/* Basic: comma insertion */ +#define A(x, ...) x __VA_OPT__(,) __VA_ARGS__ +A(1) +A(1, 2) +A(1, 2, 3) + +/* Multiple tokens inside __VA_OPT__ */ +#define B(x, ...) x __VA_OPT__(+ __VA_ARGS__ + 0) +B(1) +B(1, 2) + +/* Empty __VA_OPT__ content (just controls comma) */ +#define C(...) start __VA_OPT__(, __VA_ARGS__) end +C() +C(a) +C(a, b) + +/* __VA_OPT__ with stringify */ +#define D(x, ...) x __VA_OPT__(, #__VA_ARGS__) +D(1) +D(1, hello world) + +/* Named varargs with __VA_OPT__ */ +#define E(x, args...) x __VA_OPT__(,) args +E(1) +E(1, 2) + +/* Empty __VA_OPT__() */ +#define F(...) prefix __VA_OPT__() suffix +F() +F(1) + +/* default_gfp() pattern from the kernel */ +#define __default_gfp(a,...) a +#define default_gfp(...) __default_gfp(__VA_ARGS__ __VA_OPT__(,) 999) +default_gfp() +default_gfp(42) + +/* + * check-name: __VA_OPT__ support (C23) + * check-command: sparse -E $file + * + * check-output-start + +1 +1 , 2 +1 , 2, 3 +1 +1 + 2 + 0 +start end +start , a end +start , a, b end +1 +1 , "hello world" +1 +1 , 2 +prefix suffix +prefix suffix +999 +42 + * check-output-end + */ -- 2.43.0