From 1914bb9ba19834c937aae57b35e65936e6685ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Fri, 12 Sep 2025 11:36:03 +0200 Subject: [PATCH 1/2] add tests --- test/testtokenize.cpp | 45 ++++++++++++++++++++++++++++++++++++++++++ test/testunusedvar.cpp | 10 ++++++++++ 2 files changed, 55 insertions(+) diff --git a/test/testtokenize.cpp b/test/testtokenize.cpp index aaed28f4de6..dceb4975132 100644 --- a/test/testtokenize.cpp +++ b/test/testtokenize.cpp @@ -273,6 +273,10 @@ class TestTokenizer : public TestFixture { TEST_CASE(functionAttributeListBefore); TEST_CASE(functionAttributeListAfter); + TEST_CASE(cppMaybeUnusedBefore); + TEST_CASE(cppMaybeUnusedAfter); + TEST_CASE(cppMaybeUnusedStructuredBinding); + TEST_CASE(splitTemplateRightAngleBrackets); TEST_CASE(cpp03template1); @@ -4193,6 +4197,47 @@ class TestTokenizer : public TestFixture { ASSERT(func8 && func8->isAttributeNoreturn() && func8->isAttributePure() && func8->isAttributeNothrow() && func8->isAttributeConst()); } + void cppMaybeUnusedBefore() { + const char code[] = "[[maybe_unused]] int var {};"; + const char expected[] = "int var { } ;"; + + SimpleTokenizer tokenizer(settings0, *this); + ASSERT(tokenizer.tokenize(code)); + + ASSERT_EQUALS(expected, tokenizer.tokens()->stringifyList(nullptr, false)); + + const Token *var = Token::findsimplematch(tokenizer.tokens(), "var"); + ASSERT(var && var->isAttributeMaybeUnused()); + } + + void cppMaybeUnusedAfter() { + const char code[] = "int var [[maybe_unused]] {};"; + const char expected[] = "int var { } ;"; + + SimpleTokenizer tokenizer(settings0, *this); + ASSERT(tokenizer.tokenize(code)); + + ASSERT_EQUALS(expected, tokenizer.tokens()->stringifyList(nullptr, false)); + + const Token *var = Token::findsimplematch(tokenizer.tokens(), "var"); + ASSERT(var && var->isAttributeMaybeUnused()); + } + + void cppMaybeUnusedStructuredBinding() { + const char code[] = "[[maybe_unused]] auto [var1, var2] = f();"; + const char expected[] = "auto [ var1 , var2 ] = f ( ) ;"; + + SimpleTokenizer tokenizer(settings0, *this); + ASSERT(tokenizer.tokenize(code)); + + ASSERT_EQUALS(expected, tokenizer.tokens()->stringifyList(nullptr, false)); + + const Token *var1 = Token::findsimplematch(tokenizer.tokens(), "var1"); + ASSERT(var1 && var1->isAttributeMaybeUnused()); + const Token *var2 = Token::findsimplematch(tokenizer.tokens(), "var2"); + ASSERT(var2 && var2->isAttributeMaybeUnused()); + } + void splitTemplateRightAngleBrackets() { { diff --git a/test/testunusedvar.cpp b/test/testunusedvar.cpp index 0d7e4b00ae2..98fe81aa552 100644 --- a/test/testunusedvar.cpp +++ b/test/testunusedvar.cpp @@ -73,6 +73,7 @@ class TestUnusedVar : public TestFixture { TEST_CASE(structmember28); TEST_CASE(structmember29); // #14075 TEST_CASE(structmember30); // #14131 + TEST_CASE(structmember31); // #14130 TEST_CASE(structmember_macro); TEST_CASE(structmember_template_argument); // #13887 - do not report that member used in template argument is unused TEST_CASE(classmember); @@ -2035,6 +2036,15 @@ class TestUnusedVar : public TestFixture { ASSERT_EQUALS("", errout_str()); } + void structmember31() { // #14130 + checkStructMemberUsage("struct S\n" + "{\n" + " [[maybe_unused]] int i1{};\n" + " int i2 [[maybe_unused]] {};\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + } + void structmember_macro() { checkStructMemberUsageP("#define S(n) struct n { int a, b, c; };\n" "S(unused);\n"); From 27014b1ec96fb47cf07c4c3bc20259f2e3ea4bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Fri, 12 Sep 2025 11:02:50 +0200 Subject: [PATCH 2/2] fix #14130 --- lib/checkclass.cpp | 2 +- lib/checkunusedvar.cpp | 2 +- lib/symboldatabase.cpp | 9 ++++----- lib/tokenize.cpp | 20 +++++++++++++++++++- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/lib/checkclass.cpp b/lib/checkclass.cpp index 5a6469ae8fb..bf2ff16613c 100644 --- a/lib/checkclass.cpp +++ b/lib/checkclass.cpp @@ -1338,7 +1338,7 @@ void CheckClass::privateFunctions() while (!privateFuncs.empty()) { const auto& pf = privateFuncs.front(); - if (pf->retDef && pf->retDef->isAttributeMaybeUnused()) { + if (pf->token->isAttributeMaybeUnused()) { privateFuncs.pop_front(); continue; } diff --git a/lib/checkunusedvar.cpp b/lib/checkunusedvar.cpp index d98996d2337..177c962c1cf 100644 --- a/lib/checkunusedvar.cpp +++ b/lib/checkunusedvar.cpp @@ -1606,7 +1606,7 @@ void CheckUnusedVar::checkStructMemberUsage() if (isInherited && !var.isPrivate()) continue; - if (!var.nameToken() || var.nameToken()->isAttributeUnused() || var.nameToken()->isAnonymous()) + if (!var.nameToken() || var.nameToken()->isAttributeUnused() || var.nameToken()->isAttributeMaybeUnused() || var.nameToken()->isAnonymous()) continue; if (mTokenizer->isVarUsedInTemplate(var.declarationId())) diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index 04b87f050f3..5ffec262ba1 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -2357,8 +2357,11 @@ void Variable::evaluate(const Settings& settings) const Library & lib = settings.library; bool isContainer = false; - if (mNameToken) + if (mNameToken) { setFlag(fIsArray, arrayDimensions(settings, isContainer)); + setFlag(fIsMaybeUnused, mNameToken->isAttributeMaybeUnused()); + } + if (mTypeStartToken) setValueType(ValueType::parseDecl(mTypeStartToken,settings)); @@ -2395,10 +2398,6 @@ void Variable::evaluate(const Settings& settings) setFlag(fIsReference, true); // Set also fIsReference } - if (tok->isAttributeMaybeUnused()) { - setFlag(fIsMaybeUnused, true); - } - if (tok->str() == "<" && tok->link()) tok = tok->link(); else diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index b6ab6d8ce29..9f3e25c7d74 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -9550,9 +9550,27 @@ void Tokenizer::simplifyCPPAttribute() Token* head = skipCPPOrAlignAttribute(tok)->next(); while (isCPPAttribute(head) || isAlignAttribute(head)) head = skipCPPOrAlignAttribute(head)->next(); + if (!head) syntaxError(tok); - head->isAttributeMaybeUnused(true); + + while (Token::Match(head->next(), "%name%|*|&|&&|const|static|inline|volatile")) + head = head->next(); + if (Token::Match(head, "%name%") && !Token::Match(head, "auto [")) + head->isAttributeMaybeUnused(true); + else if (Token::Match(tok->previous(), "%name%") && Token::Match(tok->link(), "] [;={]")) { + tok->previous()->isAttributeMaybeUnused(true); + } else { + if (Token::simpleMatch(head->next(), "[")) { + head = head->next(); + const Token *end = head->link(); + for (head = head->next(); end && head != end; head = head->next()) { + if (Token::Match(head, "%name%")) { + head->isAttributeMaybeUnused(true); + } + } + } + } } else if (Token::findsimplematch(tok->tokAt(2), "unused", tok->link())) { Token* head = skipCPPOrAlignAttribute(tok)->next(); while (isCPPAttribute(head) || isAlignAttribute(head))