Hi Andrew! Thank you for the feedback. I incorporated your suggestions to my patch, please find it attached to this e-mail. Regarding your suggestions about the code style/whitespace/indentation: I have copied the code that implements __VA_OPT__ and adapted it, it appears that it also has similar issues. Maybe it would be worthwhile to fix it too.
>From 2583c56b143dc36954824d8f88d3308edbb64315 Mon Sep 17 00:00:00 2001 From: Kamila Szewczyk <[email protected]> Date: Thu, 15 Jan 2026 19:25:02 +0100 Subject: [PATCH] libcpp: partially implement proposal wg14-n3792, __VA_COUNT__ This patch partially implements the proposal wg14-n3792, available here: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3792.pdf This proposal supersedes PR gcc/33877, and the patch effectively fixes the aforementioned ticket. Based on the implementation of __VA_OPT__, I have added the preprocessor built-in __VA_COUNT__ that expands to the number of variadic macro arugments after full macro replacement. The patch has been tested on x86_64-pc-linux-gnu. -- With Valediction, Kamila Szewczyk (https://iczelia.net) --- .../c-c++-common/cpp/va-count-error.c | 10 + gcc/testsuite/c-c++-common/cpp/va-count.c | 35 ++ libcpp/ChangeLog | 4 + libcpp/identifiers.cc | 2 + libcpp/include/cpplib.h | 3 + libcpp/init.cc | 58 +-- libcpp/internal.h | 1 + libcpp/lex.cc | 24 ++ libcpp/macro.cc | 358 +++++++++++++++++- libcpp/pch.cc | 1 + 10 files changed, 464 insertions(+), 32 deletions(-) create mode 100644 gcc/testsuite/c-c++-common/cpp/va-count-error.c create mode 100644 gcc/testsuite/c-c++-common/cpp/va-count.c diff --git a/gcc/testsuite/c-c++-common/cpp/va-count-error.c b/gcc/testsuite/c-c++-common/cpp/va-count-error.c new file mode 100644 index 00000000000..258ebf9f251 --- /dev/null +++ b/gcc/testsuite/c-c++-common/cpp/va-count-error.c @@ -0,0 +1,10 @@ +/* { dg-do preprocess }*/ +/* { dg-options "-std=c2y" { target c } } */ + +#define ERRB __VA_COUNT__ /* { dg-warning "must be followed by an open parenthesis" } */ +#define ERRD(x) +ERRD(__VA_COUNT__(23)) /* Should be OK. */ +#define ERRX __VA_COUNT__ , /* { dg-warning "must be followed by an open parenthesis" } */ +#define ERRY __VA_COUNT__( /* { dg-warning "unterminated" } */ +#define a __VA_COUNT__ /* { dg-warning "must be followed by an open parenthesis" } */ +a(b) \ No newline at end of file diff --git a/gcc/testsuite/c-c++-common/cpp/va-count.c b/gcc/testsuite/c-c++-common/cpp/va-count.c new file mode 100644 index 00000000000..95579eb2310 --- /dev/null +++ b/gcc/testsuite/c-c++-common/cpp/va-count.c @@ -0,0 +1,35 @@ +/* { dg-do compile } */ +/* { dg-options "-std=c2y" { target c } } */ + +#define SA(expr, val) _Static_assert((expr) == (val), #expr " != " #val) + +static const int x = __VA_COUNT__(1, 2, 3); // int x = 3; +static const int y = __VA_COUNT__(); // int y = 0; +#define MACRO(...) __VA_COUNT__(__VA_ARGS__) +static const int z = MACRO(a, b, c, d); // int z = 4; +static const int w = MACRO(); // int w = 0; +static const int a = MACRO(a, , c, d); // int a = 4; +static const int b = MACRO(a, (, c, )d); // int b = 2; +#define MACRO2(...) __VA_COUNT__(__VA_COUNT__( __VA_ARGS__ )) +static const int c = MACRO2(a, b); // int c = 1; +static const int d = MACRO2(); // int d = 1; + +SA(x, 3); +SA(y, 0); +SA(z, 4); +SA(w, 0); +SA(a, 4); +SA(b, 2); +SA(c, 1); +SA(d, 1); + +#if __VA_COUNT__(1,2,3) != 3 +# error bad __VA_COUNT__(1,2,3) +#endif + + +int +main () +{ + return x + y + z + w + a + b + c + d; // 3 + 0 + 4 + 0 + 4 + 2 + 1 + 1 = 15 +} diff --git a/libcpp/ChangeLog b/libcpp/ChangeLog index d7c13b76663..dfd77129dae 100644 --- a/libcpp/ChangeLog +++ b/libcpp/ChangeLog @@ -1,3 +1,7 @@ +2026-01-15 Kamila Szewczyk <[email protected]> + + * libcpp: partially implement proposal wg14-n3792, __VA_COUNT__. + 2026-01-09 Ben Boeckel <[email protected]> * configure: Regenerate. diff --git a/libcpp/identifiers.cc b/libcpp/identifiers.cc index 0b56c276483..f79c8f34bec 100644 --- a/libcpp/identifiers.cc +++ b/libcpp/identifiers.cc @@ -80,6 +80,8 @@ _cpp_init_hashtable (cpp_reader *pfile, cpp_hash_table *table, s->n__VA_ARGS__->flags |= NODE_DIAGNOSTIC; s->n__VA_OPT__ = cpp_lookup (pfile, DSC("__VA_OPT__")); s->n__VA_OPT__->flags |= NODE_DIAGNOSTIC; + s->n__VA_COUNT__ = cpp_lookup (pfile, DSC("__VA_COUNT__")); + s->n__VA_COUNT__->flags |= NODE_DIAGNOSTIC; /* __has_include{,_next} are inited in cpp_init_builtins. */ } diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h index bc0d7714e96..e3d004c75ca 100644 --- a/libcpp/include/cpplib.h +++ b/libcpp/include/cpplib.h @@ -545,6 +545,9 @@ struct cpp_options /* Nonzero for C++20 __VA_OPT__ feature. */ unsigned char va_opt; + /* Nonzero for C2y N3792 feature __VA_COUNT__. */ + unsigned char va_count; + /* Nonzero for the '::' token. */ unsigned char scope; diff --git a/libcpp/init.cc b/libcpp/init.cc index a480d1d4a26..bcaced7462e 100644 --- a/libcpp/init.cc +++ b/libcpp/init.cc @@ -114,6 +114,7 @@ struct lang_flags unsigned int embed : 1; unsigned int imaginary_constants : 1; unsigned int low_ucns : 1; + unsigned int va_count : 1; }; static const struct lang_flags lang_defaults[] = { @@ -124,34 +125,34 @@ static const struct lang_flags lang_defaults[] = { c c n x c d s i l l l c s r l o o d l d d l d t f b m u 9 + u i 1 i t g i i i s e i i p p f i e i i u a a e a c 9 + m d 1 d d r t t t t p g t t e p t f r m c l l d g n */ - /* GNUC89 */ { 0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0 }, - /* GNUC99 */ { 1,0,1,1,0,0,0,1,1,1,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0 }, - /* GNUC11 */ { 1,0,1,1,1,0,0,1,1,1,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0 }, - /* GNUC17 */ { 1,0,1,1,1,0,0,1,1,1,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0 }, - /* GNUC23 */ { 1,0,1,1,1,1,0,1,1,1,0,1,1,0,1,1,1,1,0,1,1,0,0,0,1,1,0,1 }, - /* GNUC2Y */ { 1,0,1,1,1,1,0,1,1,1,0,1,1,0,1,1,1,1,0,1,1,1,0,1,1,1,1,1 }, - /* STDC89 */ { 0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - /* STDC94 */ { 0,0,0,0,0,0,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - /* STDC99 */ { 1,0,1,1,0,0,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - /* STDC11 */ { 1,0,1,1,1,0,1,1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - /* STDC17 */ { 1,0,1,1,1,0,1,1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - /* STDC23 */ { 1,0,1,1,1,1,1,1,1,0,0,1,1,0,1,1,1,1,0,1,1,0,0,0,1,1,0,1 }, - /* STDC2Y */ { 1,0,1,1,1,1,1,1,1,0,0,1,1,0,1,1,1,1,0,1,1,1,0,1,1,1,1,1 }, - /* GNUCXX */ { 0,1,1,1,0,1,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,1 }, - /* CXX98 */ { 0,1,0,1,0,1,1,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,1 }, - /* GNUCXX11 */ { 1,1,1,1,1,1,0,1,1,1,1,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,1 }, - /* CXX11 */ { 1,1,0,1,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,1 }, - /* GNUCXX14 */ { 1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,1,1,0,0,0,0,0,0,0,1,0,0,1 }, - /* CXX14 */ { 1,1,0,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,0,0,0,0,0,0,1,0,0,1 }, - /* GNUCXX17 */ { 1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,0,1,0,0,1 }, - /* CXX17 */ { 1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,0,0,0,0,0,0,0,1,0,0,1 }, - /* GNUCXX20 */ { 1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,0,1,0,0,1 }, - /* CXX20 */ { 1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,0,1,0,0,1 }, - /* GNUCXX23 */ { 1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,0,0,1 }, - /* CXX23 */ { 1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,0,0,1 }, - /* GNUCXX26 */ { 1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,1,0,1 }, - /* CXX26 */ { 1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,1,0,1 }, - /* ASM */ { 0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } + /* GNUC89 */ { 0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0 }, + /* GNUC99 */ { 1,0,1,1,0,0,0,1,1,1,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0 }, + /* GNUC11 */ { 1,0,1,1,1,0,0,1,1,1,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0 }, + /* GNUC17 */ { 1,0,1,1,1,0,0,1,1,1,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0 }, + /* GNUC23 */ { 1,0,1,1,1,1,0,1,1,1,0,1,1,0,1,1,1,1,0,1,1,0,0,0,1,1,0,1,0 }, + /* GNUC2Y */ { 1,0,1,1,1,1,0,1,1,1,0,1,1,0,1,1,1,1,0,1,1,1,0,1,1,1,1,1,1 }, + /* STDC89 */ { 0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + /* STDC94 */ { 0,0,0,0,0,0,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + /* STDC99 */ { 1,0,1,1,0,0,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + /* STDC11 */ { 1,0,1,1,1,0,1,1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + /* STDC17 */ { 1,0,1,1,1,0,1,1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + /* STDC23 */ { 1,0,1,1,1,1,1,1,1,0,0,1,1,0,1,1,1,1,0,1,1,0,0,0,1,1,0,1,0 }, + /* STDC2Y */ { 1,0,1,1,1,1,1,1,1,0,0,1,1,0,1,1,1,1,0,1,1,1,0,1,1,1,1,1,1 }, + /* GNUCXX */ { 0,1,1,1,0,1,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,1,0 }, + /* CXX98 */ { 0,1,0,1,0,1,1,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0 }, + /* GNUCXX11 */ { 1,1,1,1,1,1,0,1,1,1,1,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,1,0 }, + /* CXX11 */ { 1,1,0,1,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0 }, + /* GNUCXX14 */ { 1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,1,1,0,0,0,0,0,0,0,1,0,0,1,0 }, + /* CXX14 */ { 1,1,0,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0 }, + /* GNUCXX17 */ { 1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0 }, + /* CXX17 */ { 1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,0,0,0,0,0,0,0,1,0,0,1,0 }, + /* GNUCXX20 */ { 1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0 }, + /* CXX20 */ { 1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0 }, + /* GNUCXX23 */ { 1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,0,0,1,0 }, + /* CXX23 */ { 1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,0,0,1,0 }, + /* GNUCXX26 */ { 1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,1,0,1,0 }, + /* CXX26 */ { 1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,1,0,1,0 }, + /* ASM */ { 0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } }; /* Sets internal flags correctly for a given language. */ @@ -190,6 +191,7 @@ cpp_set_lang (cpp_reader *pfile, enum c_lang lang) CPP_OPTION (pfile, embed) = l->embed; CPP_OPTION (pfile, imaginary_constants) = l->imaginary_constants; CPP_OPTION (pfile, low_ucns) = l->low_ucns; + CPP_OPTION (pfile, va_count) = l->va_count; } /* Initialize library global state. */ diff --git a/libcpp/internal.h b/libcpp/internal.h index 017e980a964..a7c0979a936 100644 --- a/libcpp/internal.h +++ b/libcpp/internal.h @@ -306,6 +306,7 @@ struct spec_nodes cpp_hashnode *n_false; /* C++ keyword false */ cpp_hashnode *n__VA_ARGS__; /* C99 vararg macros */ cpp_hashnode *n__VA_OPT__; /* C++ vararg macros */ + cpp_hashnode *n__VA_COUNT__; /* C2y N3792 vararg macros */ enum {M_EXPORT, M_MODULE, M_IMPORT, M__IMPORT, M_HWM}; diff --git a/libcpp/lex.cc b/libcpp/lex.cc index df278534eee..26dfdff1fd5 100644 --- a/libcpp/lex.cc +++ b/libcpp/lex.cc @@ -2146,6 +2146,26 @@ forms_identifier_p (cpp_reader *pfile, int first, return false; } +/* Helper function to issue error about improper __VA_COUNT__ use. */ +static void +maybe_va_count_error (cpp_reader *pfile) +{ + if (CPP_PEDANTIC (pfile) && !CPP_OPTION (pfile, va_count)) + { + /* __VA_COUNT__ should not be accepted at all, but allow it in + system headers. */ + if (!_cpp_in_system_header (pfile)) + { + if (CPP_OPTION (pfile, cplusplus)) + cpp_pedwarning (pfile, CPP_W_PEDANTIC, + "%<__VA_COUNT__%> is not standardized or proposed for C++"); + else + cpp_pedwarning (pfile, CPP_W_PEDANTIC, + "%<__VA_COUNT__%> is not available until C2y"); + } + } +} + /* Helper function to issue error about improper __VA_OPT__ use. */ static void maybe_va_opt_error (cpp_reader *pfile) @@ -2214,6 +2234,10 @@ identifier_diagnostics_on_lex (cpp_reader *pfile, cpp_hashnode *node) if (node == pfile->spec_nodes.n__VA_OPT__) maybe_va_opt_error (pfile); + /* __VA_COUNT__ checks */ + if (node == pfile->spec_nodes.n__VA_COUNT__) + maybe_va_count_error (pfile); + /* For -Wc++-compat, warn about use of C++ named operators. */ if (node->flags & NODE_WARN_OPERATOR) cpp_warning (pfile, CPP_W_CXX_OPERATOR_NAMES, diff --git a/libcpp/macro.cc b/libcpp/macro.cc index c29a34302b6..eff6b5ca83b 100644 --- a/libcpp/macro.cc +++ b/libcpp/macro.cc @@ -93,6 +93,9 @@ struct macro_arg_saved_data { static const char *vaopt_paste_error = N_("'##' cannot appear at either end of __VA_OPT__"); +static const char *vacount_paren_error = + N_("%<__VA_COUNT__%> must be followed by an open parenthesis"); + static void expand_arg (cpp_reader *, macro_arg *); /* A class for tracking __VA_OPT__ state while iterating over a @@ -276,6 +279,152 @@ class vaopt_state { update_type m_update; }; +/* A class for tracking __VA_COUNT__ state while iterating over a + sequence of tokens. This is used during macro definition (to + validate structure) and during expansion (to count arguments and + replace the invocation). */ +class vacount_state { + public: + enum update_type + { + ERROR, + DROP, + INCLUDE, + BEGIN, + END + }; + + vacount_state (cpp_reader *pfile) + : m_pfile (pfile) + { + } + + /* Given a token, update the state of this tracker and return an + update_type indicating whether the token should be included in + the output stream. For __VA_COUNT__, tokens of the invocation + are always dropped, and END indicates that a replacement token + should be emitted. */ + update_type + dupdate (const cpp_token *token) + { + /* Start recognizing a __VA_COUNT__ invocation only when we're not + already inside one. Nested __VA_COUNT__ tokens inside the operand + list are just ordinary tokens and must not reset state or error. */ + if (m_state == 0 + && token->type == CPP_NAME + && token->val.node.node == m_pfile->spec_nodes.n__VA_COUNT__) { + m_state = 1; + m_location = token->src_loc; + m_stringify = (token->flags & STRINGIFY_ARG) != 0; + m_flags = token->flags & (PREV_WHITE | PREV_FALLTHROUGH); + m_paren_depth = 0; + m_count = 0; + m_started = false; + m_paste_left = false; + return BEGIN; + } + else if (m_state == 1) + { + /* Require a following '(' (definition-time constraint). */ + if (token->type != CPP_OPEN_PAREN) + { + cpp_error_at (m_pfile, CPP_DL_ERROR, m_location, + vacount_paren_error); + m_state = 0; + return ERROR; + } + m_state = 2; + /* Drop the '(' from output: the whole invocation is replaced. */ + return DROP; + } + else if (m_state >= 2) + { + /* Drop padding/comments inside the invocation, they should not + affect argument splitting. */ + if (token->type == CPP_PADDING + || token->type == CPP_COMMENT) + return DROP; + + /* First significant token after '(' determines empty invocation. */ + if (!m_started) + { + if (token->type == CPP_CLOSE_PAREN) + { + /* __VA_COUNT__() => 0. */ + m_paste_left = (token->flags & PASTE_LEFT) != 0; + m_state = 0; + m_count = 0; + return END; + } + /* Non-empty invocation => at least one argument (possibly empty). */ + m_started = true; + m_count = 1; + } + + if (token->type == CPP_OPEN_PAREN) + { + ++m_paren_depth; + return DROP; + } + else if (token->type == CPP_CLOSE_PAREN) + { + if (m_paren_depth-- == 0) + { + /* Final ')'. */ + m_paste_left = (token->flags & PASTE_LEFT) != 0; + m_state = 0; + return END; + } + return DROP; + } + else if (token->type == CPP_COMMA) + { + if (m_paren_depth == 0) + ++m_count; + return DROP; + } + else if (token->type == CPP_EOF + || (token->type == CPP_HASH && (token->flags & BOL))) + { + cpp_error_at (m_pfile, CPP_DL_ERROR, m_location, + "unterminated %<__VA_COUNT__%>"); + m_state = 0; + m_count = 0; + return ERROR; + } + + return DROP; + } + + return INCLUDE; + } + + bool completed () + { + if (m_state != 0) + cpp_error_at (m_pfile, CPP_DL_ERROR, m_location, + "unterminated %<__VA_COUNT__%>"); + return m_state == 0; + } + + bool stringify () const { return m_stringify; } + unsigned long count () const { return m_count; } + bool paste_left () const { return m_paste_left; } + location_t location () const { return m_location; } + unsigned flags () const { return m_flags; } + + private: + cpp_reader *m_pfile; + bool m_stringify = false; + int m_state = 0; + unsigned m_paren_depth = 0; + unsigned long m_count = 0; + bool m_started = false; + bool m_paste_left = false; + location_t m_location = 0; + unsigned m_flags = 0; +}; + /* Macro expansion. */ static cpp_macro *get_deferred_or_lazy_macro (cpp_reader *, cpp_hashnode *, @@ -312,7 +461,6 @@ static const cpp_token **arg_token_ptr_at (const macro_arg *, size_t, enum macro_arg_token_kind, location_t **virt_location); - static void macro_arg_token_iter_init (macro_arg_token_iter *, bool, enum macro_arg_token_kind, const macro_arg *, @@ -895,6 +1043,179 @@ builtin_macro (cpp_reader *pfile, cpp_hashnode *node, return 1; } +/* Try to expand __VA_COUNT__(...) at the current point in the token stream. + NAME is the __VA_COUNT__ token we just read. NAME_VIRT_LOC is the + virtual location associated to NAME (as returned by cpp_get_token_1). + + On success, pushes a context containing a single token (either a + preprocessing-number token or, for #__VA_COUNT__, a string token) + and returns 1. If there is no following '(', returns 0 and leaves + the token stream unchanged (as per function-like macro rules). */ +static int +maybe_expand_va_count (cpp_reader *pfile, const cpp_token *name, + location_t name_virt_loc) +{ + const cpp_token *token, *padding = NULL; + location_t virt_loc = 0; + unsigned long m = 0; + bool stringify = (name->flags & STRINGIFY_ARG) != 0; + bool paste_left = false; + unsigned out_flags = name->flags & (PREV_WHITE | PREV_FALLTHROUGH); + + /* Parse like funlike_invocation_p/collect_args: inhibit expansion while + recognizing and consuming the parenthesized argument list. */ + int saved_prevent_expansion = pfile->state.prevent_expansion; + int saved_keep_tokens = pfile->keep_tokens; + int saved_parsing_args = pfile->state.parsing_args; + + pfile->state.prevent_expansion++; + pfile->keep_tokens++; + pfile->state.parsing_args = 1; + + /* Search for the opening '(' while preserving intervening padding. */ + for (;;) + { + token = cpp_get_token_1 (pfile, &virt_loc); + if (token->type != CPP_PADDING) + break; + gcc_assert ((token->flags & PREV_WHITE) == 0); + if (padding == NULL + || padding->val.source == NULL + || (!(padding->val.source->flags & PREV_WHITE) + && token->val.source == NULL)) + padding = token; + } + + if (token->type != CPP_OPEN_PAREN) + { + /* Not an invocation: back up and re-insert padding if needed. */ + if (token->type != CPP_EOF || token == &pfile->endarg) + { + _cpp_backup_tokens (pfile, 1); + if (padding) + _cpp_push_token_context (pfile, NULL, padding, 1); + } + pfile->state.parsing_args = saved_parsing_args; + pfile->keep_tokens = saved_keep_tokens; + pfile->state.prevent_expansion = saved_prevent_expansion; + return 0; + } + + /* We are in an invocation. */ + pfile->state.parsing_args = 2; + + /* Use the same counting rule as function-like macro arg parsing: + m = 0 for empty list, else 1 + #top-level commas. */ + { + unsigned paren_depth = 0; + bool started = false; + + for (;;) + { + token = cpp_get_token_1 (pfile, &virt_loc); + + if (token->type == CPP_PADDING || token->type == CPP_COMMENT) + continue; + + if (!started) + { + if (token->type == CPP_CLOSE_PAREN) + { + m = 0; + paste_left = (token->flags & PASTE_LEFT) != 0; + break; + } + started = true; + m = 1; + } + + if (token->type == CPP_OPEN_PAREN) + ++paren_depth; + else if (token->type == CPP_CLOSE_PAREN) + { + if (paren_depth-- == 0) + { + paste_left = (token->flags & PASTE_LEFT) != 0; + break; + } + } + else if (token->type == CPP_COMMA) + { + if (paren_depth == 0) + ++m; + } + else if (token->type == CPP_EOF + || (token->type == CPP_HASH && (token->flags & BOL))) + { + cpp_error (pfile, CPP_DL_ERROR, + "unterminated argument list invoking macro %qs", + "__VA_COUNT__"); + m = 0; + break; + } + } + } + + pfile->state.parsing_args = saved_parsing_args; + pfile->keep_tokens = saved_keep_tokens; + pfile->state.prevent_expansion = saved_prevent_expansion; + + if (m > (unsigned long) LONG_MAX) + { + cpp_error_with_line (pfile, CPP_DL_ERROR, name->src_loc, 0, + "%<__VA_COUNT__%> result %lu is not representable " + "as %<signed long%>", m); + m = 0; + } + + /* Materialize the replacement token. */ + cpp_token *numtok = _cpp_temp_token (pfile); + { + size_t buflen = 3 * sizeof (long) + 2; + uchar *buf = _cpp_unaligned_alloc (pfile, buflen); + numtok->val.str.len = sprintf ((char *) buf, "%ld", (long) m); + numtok->type = CPP_NUMBER; + numtok->val.str.text = buf; + numtok->src_loc = name->src_loc; + numtok->flags = out_flags | (paste_left ? PASTE_LEFT : 0); + } + + const cpp_token *out = numtok; + if (stringify) + { + const cpp_token *arr[1] = { out }; + out = stringify_arg (pfile, arr, 1); + /* Preserve spacing/paste semantics on the string token too. */ + cpp_token *t = _cpp_temp_token (pfile); + *t = *out; + t->flags |= out_flags; + if (paste_left) + t->flags |= PASTE_LEFT; + t->src_loc = name->src_loc; + out = t; + } + + /* Push the token in its own context, like builtin_macro does. */ + if (pfile->context->tokens_kind == TOKENS_KIND_EXTENDED) + { + location_t *virt_locs = NULL; + _cpp_buff *token_buf = tokens_buff_new (pfile, 1, &virt_locs); + const line_map_macro *map = + linemap_enter_macro (pfile->line_table, + pfile->spec_nodes.n__VA_COUNT__, + name_virt_loc, 1); + tokens_buff_add_token (token_buf, virt_locs, out, + name_virt_loc, name->src_loc, map, 0); + push_extended_tokens_context (pfile, pfile->spec_nodes.n__VA_COUNT__, + token_buf, virt_locs, + (const cpp_token **) token_buf->base, 1); + } + else + _cpp_push_token_context (pfile, NULL, out, 1); + + return 1; +} + /* Copies SRC, of length LEN, to DEST, adding backslashes before all backslashes and double quotes. DEST must be of sufficient size. Returns a pointer to the end of the string. */ @@ -3057,6 +3378,24 @@ cpp_get_token_1 (cpp_reader *pfile, location_t *location) node = result->val.node.node; + /* __VA_COUNT__(...) handling, modeled after __VA_OPT__ being a + special reserved identifier: expand only when it is actually + invoked (i.e. followed by '('), using function-like macro + argument parsing rules. */ + if (node == pfile->spec_nodes.n__VA_COUNT__) + { + if (!pfile->state.prevent_expansion + && maybe_expand_va_count (pfile, result, virt_loc)) + { + if (pfile->state.in_directive) + continue; + result = padding_token (pfile, result); + goto out; + } + /* Not an invocation => leave as-is. */ + break; + } + if (node->type == NT_VOID || (result->flags & NO_EXPAND)) break; @@ -3791,9 +4130,10 @@ create_iso_definition (cpp_reader *pfile) if (macro->count > 1 && token[-1].type == CPP_HASH && macro->fun_like) { if (token->type == CPP_MACRO_ARG - || (macro->variadic - && token->type == CPP_NAME - && token->val.node.node == pfile->spec_nodes.n__VA_OPT__)) + || (token->type == CPP_NAME + && (token->val.node.node == pfile->spec_nodes.n__VA_COUNT__ + || (macro->variadic + && token->val.node.node == pfile->spec_nodes.n__VA_OPT__)))) { if (token->flags & PREV_WHITE) token->flags |= SP_PREV_WHITE; @@ -3826,6 +4166,16 @@ create_iso_definition (cpp_reader *pfile) } if (!vaopt_tracker.completed ()) goto out; + /* Validate balanced __VA_COUNT__(...) in macro definitions. */ + { + vacount_state vacount_tracker (pfile); + /* Walk the replacement list tokens already collected. */ + for (unsigned j = 0; j < macro->count; ++j) + if (vacount_tracker.update (¯o->exp.tokens[j]) == vacount_state::ERROR) + goto out; + if (!vacount_tracker.completed ()) + goto out; + } break; } diff --git a/libcpp/pch.cc b/libcpp/pch.cc index b27c299eb3f..04fc53a2942 100644 --- a/libcpp/pch.cc +++ b/libcpp/pch.cc @@ -852,6 +852,7 @@ cpp_read_state (cpp_reader *r, const char *name, FILE *f, s->n_false = cpp_lookup (r, DSC("false")); s->n__VA_ARGS__ = cpp_lookup (r, DSC("__VA_ARGS__")); s->n__VA_OPT__ = cpp_lookup (r, DSC("__VA_OPT__")); + s->n__VA_COUNT__ = cpp_lookup (r, DSC("__VA_COUNT__")); } old_state = r->state; -- 2.51.0
