From 931925819c4b2b759f180cd8c7ac9a9fbb5e6494 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/3] 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 6e468ad524d..559a2af1e39 100644 --- a/test/testtokenize.cpp +++ b/test/testtokenize.cpp @@ -270,6 +270,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); @@ -4164,6 +4168,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 ac97629d112..af7caa1639f 100644 --- a/test/testunusedvar.cpp +++ b/test/testunusedvar.cpp @@ -72,6 +72,7 @@ class TestUnusedVar : public TestFixture { TEST_CASE(structmember27); // #13367 TEST_CASE(structmember28); TEST_CASE(structmember29); // #14075 + TEST_CASE(structmember30); // #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); @@ -2023,6 +2024,15 @@ class TestUnusedVar : public TestFixture { ASSERT_EQUALS("[test.cpp:4:56]: (style) struct member 'S::storage' is never used. [unusedStructMember]\n", errout_str()); } + void structmember30() { // #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 381d5d37c3aee89fb7009c0f421612587666c466 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/3] fix #14130 --- lib/checkclass.cpp | 2 +- lib/checkunusedvar.cpp | 2 +- lib/symboldatabase.cpp | 9 ++++----- lib/tokenize.cpp | 18 +++++++++++++++++- 4 files changed, 23 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 55145f1207c..d0aabfb8a97 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 9c6db66a4c6..869ca3354a3 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -9542,7 +9542,23 @@ void Tokenizer::simplifyCPPAttribute() Token* head = skipCPPOrAlignAttribute(tok)->next(); while (isCPPAttribute(head) || isAlignAttribute(head)) head = skipCPPOrAlignAttribute(head)->next(); - 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)) From 48d9b73806dc097d3dd95d0d78411c7e7f4675cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Sun, 28 Sep 2025 21:57:48 +0200 Subject: [PATCH 3/3] explicit match MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Marjamäki --- lib/tokenize.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index 869ca3354a3..2486b9f8b42 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -9549,7 +9549,7 @@ void Tokenizer::simplifyCPPAttribute() else if (Token::Match(tok->previous(), "%name%") && Token::Match(tok->link(), "] [;={]")) { tok->previous()->isAttributeMaybeUnused(true); } else { - if (Token::simpleMatch(head->next(), "[")) { + if (Token::Match(head, "auto|*|&|&& [")) { // c++17 structured binding head = head->next(); const Token *end = head->link(); for (head = head->next(); end && head != end; head = head->next()) {