From 663d816daa2025d9a733c2e1e0dc7ebc40e0f197 Mon Sep 17 00:00:00 2001 From: Leander Schulten Date: Wed, 30 Sep 2026 20:53:07 +0200 Subject: [PATCH 1/2] Fix #15079 FP syntaxError for C-style cast before scope operator The check added in a294079e6 (#15060) reports every `)` followed by `::` unless the `(` belongs to a control flow keyword or decltype. That also rejects valid code like `(HWND)::SendMessage(...)` or placement new `new (p) ::N::T`, so whole translation units are skipped. Only report when the parenthesis follows a non-keyword name, which is the unknown macro case the check was added for. Co-Authored-By: Claude Opus 5.5 --- lib/tokenize.cpp | 6 ++++-- test/testtokenize.cpp | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index 394a0e42add..c4923f8ccee 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -9177,9 +9177,11 @@ void Tokenizer::findGarbageCode() const if (!Token::Match(tok->next(), "%name%|*|~") || (tok->next()->isKeyword() && !Token::Match(tok->next(), "new|delete|operator"))) syntaxError(tok); if (Token::simpleMatch(tok->tokAt(-1), ")")) { + // NAME(...):: => NAME is most likely an unknown macro + // other cases are valid, e.g. (void)::f(), return (T)::x, new (p) ::T, decltype(x)::type const Token* const prev = tok->linkAt(-1)->tokAt(-1); - if (!Token::Match(prev, "%name% (") || (!prev->isControlFlowKeyword() && prev->str() != "decltype")) { - if (prev && prev->isUpperCaseName()) + if (Token::Match(prev, "%name% (") && !prev->isKeyword()) { + if (prev->isUpperCaseName()) unknownMacroError(prev); else syntaxError(tok); diff --git a/test/testtokenize.cpp b/test/testtokenize.cpp index fca4b9db613..e0d63f404e3 100644 --- a/test/testtokenize.cpp +++ b/test/testtokenize.cpp @@ -8135,6 +8135,22 @@ class TestTokenizer : public TestFixture { " *p = -*p;\n" "}\n")); + ASSERT_NO_THROW(tokenizeAndStringify("void f(void* h) {\n" // cast before global scope operator + " (void)::CloseHandle(h);\n" + " int i = (int)::GetTickCount();\n" + " g((unsigned char)::toupper(i));\n" + "}\n")); + ASSERT_NO_THROW(tokenizeAndStringify("int f(int i) {\n" + " return (int)::abs(i);\n" + "}\n")); + ASSERT_NO_THROW(tokenizeAndStringify("namespace N { struct T {}; }\n" + "void f(void* p) {\n" + " new (p) ::N::T;\n" + "}\n")); + ASSERT_NO_THROW(tokenizeAndStringify("struct S { using type = int; };\n" + "S s;\n" + "decltype(s)::type i;\n")); + ignore_errout(); ASSERT_EQUALS(";", tokenizeAndStringify("typedef std::size_t size_t;\n")); // #14809 From eb5b4d9122be52856af5658619538acbfaa37199 Mon Sep 17 00:00:00 2001 From: Leander Schulten Date: Sat, 3 Oct 2026 17:22:41 +0200 Subject: [PATCH 2/2] Keep decltype exception for standards where it is no keyword Co-Authored-By: Claude Opus 5.5 --- lib/tokenize.cpp | 2 +- test/testtokenize.cpp | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index c4923f8ccee..68456e69191 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -9180,7 +9180,7 @@ void Tokenizer::findGarbageCode() const // NAME(...):: => NAME is most likely an unknown macro // other cases are valid, e.g. (void)::f(), return (T)::x, new (p) ::T, decltype(x)::type const Token* const prev = tok->linkAt(-1)->tokAt(-1); - if (Token::Match(prev, "%name% (") && !prev->isKeyword()) { + if (Token::Match(prev, "%name% (") && !prev->isKeyword() && prev->str() != "decltype") { // decltype is no keyword before C++11 if (prev->isUpperCaseName()) unknownMacroError(prev); else diff --git a/test/testtokenize.cpp b/test/testtokenize.cpp index e0d63f404e3..5b14e8c1da3 100644 --- a/test/testtokenize.cpp +++ b/test/testtokenize.cpp @@ -8135,7 +8135,7 @@ class TestTokenizer : public TestFixture { " *p = -*p;\n" "}\n")); - ASSERT_NO_THROW(tokenizeAndStringify("void f(void* h) {\n" // cast before global scope operator + ASSERT_NO_THROW(tokenizeAndStringify("void f(void* h) {\n" // #15079 " (void)::CloseHandle(h);\n" " int i = (int)::GetTickCount();\n" " g((unsigned char)::toupper(i));\n" @@ -8150,6 +8150,12 @@ class TestTokenizer : public TestFixture { ASSERT_NO_THROW(tokenizeAndStringify("struct S { using type = int; };\n" "S s;\n" "decltype(s)::type i;\n")); + { + const Settings s = settingsBuilder().cpp(Standards::CPP03).build(); + ASSERT_NO_THROW(tokenizeAndStringify("struct S { typedef int type; };\n" + "S s;\n" + "decltype(s)::type i;\n", s)); + } ignore_errout();