On Thu, Jan 15, 2026 at 10:51 AM Kamila Szewczyk <[email protected]> wrote: >
A couple of comments, First the commit message should include basically what was in your cover letter. Second you need to add a changelog to this. See https://gcc.gnu.org/contribute.html#patches too. Also this fixes https://gcc.gnu.org/bugzilla/show_bug.cgi?id=33877 so a marker for that should be added. Comments on the patch below: > --- > gcc/testsuite/c-c++-common/cpp/va_count.c | 35 ++ > libcpp/identifiers.cc | 2 + > libcpp/include/cpplib.h | 3 + > libcpp/init.cc | 58 ++-- > libcpp/internal.h | 3 +- > libcpp/lex.cc | 24 ++ > libcpp/macro.cc | 368 +++++++++++++++++++++- > libcpp/pch.cc | 1 + > 8 files changed, 461 insertions(+), 33 deletions(-) > create mode 100644 gcc/testsuite/c-c++-common/cpp/va_count.c > > 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 > +} I see you only test for valid uses __VA_COUNT__. It would be useful to add a few testcases where __VA_COUNT__ would have invalid arguments and such. Like what happens if `__VA_COUNT__(` is at the end of the file or say `__VA_COUNT__ ,` Or `#define a __VA_COUNT__ \n a(b)`? > 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..7f5d08fc379 100644 > --- a/libcpp/internal.h > +++ b/libcpp/internal.h > @@ -263,7 +263,7 @@ struct lexer_state > all directives apart from #define. */ > unsigned char save_comments; > > - /* Nonzero if lexing __VA_ARGS__ and __VA_OPT__ are valid. */ > + /* Nonzero if lexing __VA_ARGS__, __VA_COUNT__ and __VA_OPT__ are valid. > */ > unsigned char va_args_ok; > > /* Nonzero if lexing poisoned identifiers is valid. */ > @@ -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..e0a7152de42 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 standardised or proposed > for C++"); We use the z form of the word rather than the s form; that is standardized. That is the American English form. See https://gcc.gnu.org/codingconventions.html#Spelling which mentions this. > + 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..c7bafb60de0 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,159 @@ 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), > + m_stringify (false), > + m_state (0), > + m_paren_depth (0), > + m_count (0), > + m_started (false), > + m_paste_left (false), > + m_location (0), > + m_flags (0) Might be better to use NSDMI rather than initializing them in the constructor. A lot of GCC started out as C code and then convert to C++98 but then not converted further to C++11/C++14 which is why NSDMI is not used in a lot of GCC code but we should use it more. > + { > + } > + > + /* 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 update (const cpp_token *token) Return type goes on its own line. Also since this is a large method, it most likely should be defined out of the class definition too. > + { > + /* 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__) { { goes on the next line. There might be indention issues too. > + 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 () Same whitespace issue and this function is getting to the size it should be outside of the class defition. > + { > + 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; } These look fine/good. > + > + private: > + cpp_reader *m_pfile; > + bool m_stringify; > + int m_state; > + unsigned m_paren_depth; > + unsigned long m_count; > + bool m_started; > + bool m_paste_left; > + location_t m_location; > + unsigned m_flags; > +}; > + > /* Macro expansion. */ > > static cpp_macro *get_deferred_or_lazy_macro (cpp_reader *, cpp_hashnode *, > @@ -312,7 +468,8 @@ static const cpp_token **arg_token_ptr_at (const > macro_arg *, > size_t, > enum macro_arg_token_kind, > location_t **virt_location); > - > +static int maybe_expand_va_count (cpp_reader *, const cpp_token *, > + location_t); You don't need a forward declaration if the definition is before the first usage. There might be some which are done this way, just this file was not fixed up when that part of the coding style was changed back iin the early 2000s. > static void macro_arg_token_iter_init (macro_arg_token_iter *, bool, > enum macro_arg_token_kind, > const macro_arg *, > @@ -895,6 +1052,180 @@ 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. */ > + { Hmm, why this `{` here? > + 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) I am not even sure this could ever happen as it would require more memory than the machine had to get to this point. > + { > + 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); > + sprintf ((char *) buf, "%ld", (long) m); > + numtok->type = CPP_NUMBER; > + numtok->val.str.text = buf; > + numtok->val.str.len = strlen ((const char *) buf); The other place which the sprintf does: tok->val.str.len = sprintf ((char *) s, "%d", buffer[i]); I think you should do the same instead of doing strlen again on the buf. Thanks, Andrew > + 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 +3388,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 +4140,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 +4176,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 >
