From da3aa83772fc61da69a41fbb7294b7ef3486e868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 23 Sep 2025 04:23:22 +0200 Subject: [PATCH 1/6] Fix #7458 (FP unusedStructMember with struct in union) --- lib/checkunusedvar.cpp | 59 ++++++++++++++++++++++++++++++++++++++++++ test/testunusedvar.cpp | 14 ++++++++++ 2 files changed, 73 insertions(+) diff --git a/lib/checkunusedvar.cpp b/lib/checkunusedvar.cpp index d98996d2337..4a7deffa5ae 100644 --- a/lib/checkunusedvar.cpp +++ b/lib/checkunusedvar.cpp @@ -1599,6 +1599,57 @@ void CheckUnusedVar::checkStructMemberUsage() if (bailout) continue; + // #7458 - if struct is declared inside union and any struct member is used, + // then don't warn about other struct members + bool structInUnionWithUsedMember = false; + if (scope.type == ScopeType::eStruct && scope.nestedIn && scope.nestedIn->type == ScopeType::eUnion) { + for (const Variable &unionVar : scope.varlist) { + for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { + if (tok->variable() == &unionVar && tok != unionVar.nameToken()) { + structInUnionWithUsedMember = true; + break; + } + } + if (structInUnionWithUsedMember) + break; + } + } + + // #7458 - if this is a union and any member is used, don't warn about other members + bool unionWithUsedMember = false; + if (scope.type == ScopeType::eUnion) { + // Check if any direct member of the union is used + for (const Variable &unionVar : scope.varlist) { + for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { + if (tok->variable() == &unionVar && tok != unionVar.nameToken()) { + unionWithUsedMember = true; + break; + } + } + if (unionWithUsedMember) + break; + } + // Also check if any member of nested structs in this union is used + if (!unionWithUsedMember) { + for (const Scope *nestedScope : scope.nestedList) { + if (nestedScope->type == ScopeType::eStruct) { + for (const Variable &structVar : nestedScope->varlist) { + for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { + if (tok->variable() == &structVar && tok != structVar.nameToken()) { + unionWithUsedMember = true; + break; + } + } + if (unionWithUsedMember) + break; + } + if (unionWithUsedMember) + break; + } + } + } + } + for (const Variable &var : scope.varlist) { // only warn for variables without side effects if (!var.typeStartToken()->isStandardType() && !var.isPointer() && !astIsContainer(var.nameToken()) && !isRecordTypeWithoutSideEffects(var.type())) @@ -1612,6 +1663,14 @@ void CheckUnusedVar::checkStructMemberUsage() if (mTokenizer->isVarUsedInTemplate(var.declarationId())) continue; + // Skip reporting unused members if this struct is in a union and any member is used + if (structInUnionWithUsedMember) + continue; + + // Skip reporting unused members if this is a union and any member is used + if (unionWithUsedMember) + continue; + // Check if the struct member variable is used anywhere in the file bool use = false; for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { diff --git a/test/testunusedvar.cpp b/test/testunusedvar.cpp index ac97629d112..589422d53eb 100644 --- a/test/testunusedvar.cpp +++ b/test/testunusedvar.cpp @@ -1409,6 +1409,20 @@ class TestUnusedVar : public TestFixture { ASSERT_EQUALS("[test.cpp:3:9]: (style) union member 'abc::a' is never used. [unusedStructMember]\n" "[test.cpp:4:9]: (style) union member 'abc::b' is never used. [unusedStructMember]\n" "[test.cpp:5:9]: (style) union member 'abc::c' is never used. [unusedStructMember]\n", errout_str()); + + // #7458 - union with anonymous struct should not cause false positive + checkStructMemberUsage("union DoubleInt {\n" + " double asDouble;\n" + " uint64_t asInt;\n" + " struct {\n" + " uint32_t lo, hi;\n" + " } asIntel;\n" + "};\n" + "void f() {\n" + " union DoubleInt di;\n" + " di.asIntel.hi = 3;\n" + "}"); + ASSERT_EQUALS("", errout_str()); } void structmember2() { From b53bc29fe678beae0ec54495a79a20522a11b0c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 23 Sep 2025 04:34:25 +0200 Subject: [PATCH 2/6] tweak; we can warn about unused union members --- lib/checkunusedvar.cpp | 38 -------------------------------------- test/testunusedvar.cpp | 6 ++++-- 2 files changed, 4 insertions(+), 40 deletions(-) diff --git a/lib/checkunusedvar.cpp b/lib/checkunusedvar.cpp index 4a7deffa5ae..e11a939587e 100644 --- a/lib/checkunusedvar.cpp +++ b/lib/checkunusedvar.cpp @@ -1615,41 +1615,6 @@ void CheckUnusedVar::checkStructMemberUsage() } } - // #7458 - if this is a union and any member is used, don't warn about other members - bool unionWithUsedMember = false; - if (scope.type == ScopeType::eUnion) { - // Check if any direct member of the union is used - for (const Variable &unionVar : scope.varlist) { - for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { - if (tok->variable() == &unionVar && tok != unionVar.nameToken()) { - unionWithUsedMember = true; - break; - } - } - if (unionWithUsedMember) - break; - } - // Also check if any member of nested structs in this union is used - if (!unionWithUsedMember) { - for (const Scope *nestedScope : scope.nestedList) { - if (nestedScope->type == ScopeType::eStruct) { - for (const Variable &structVar : nestedScope->varlist) { - for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { - if (tok->variable() == &structVar && tok != structVar.nameToken()) { - unionWithUsedMember = true; - break; - } - } - if (unionWithUsedMember) - break; - } - if (unionWithUsedMember) - break; - } - } - } - } - for (const Variable &var : scope.varlist) { // only warn for variables without side effects if (!var.typeStartToken()->isStandardType() && !var.isPointer() && !astIsContainer(var.nameToken()) && !isRecordTypeWithoutSideEffects(var.type())) @@ -1667,9 +1632,6 @@ void CheckUnusedVar::checkStructMemberUsage() if (structInUnionWithUsedMember) continue; - // Skip reporting unused members if this is a union and any member is used - if (unionWithUsedMember) - continue; // Check if the struct member variable is used anywhere in the file bool use = false; diff --git a/test/testunusedvar.cpp b/test/testunusedvar.cpp index 589422d53eb..ff39d0406e7 100644 --- a/test/testunusedvar.cpp +++ b/test/testunusedvar.cpp @@ -1415,14 +1415,16 @@ class TestUnusedVar : public TestFixture { " double asDouble;\n" " uint64_t asInt;\n" " struct {\n" - " uint32_t lo, hi;\n" + " uint32_t lo, hi;\n" // <- no FP about lo because hi is used " } asIntel;\n" "};\n" "void f() {\n" " union DoubleInt di;\n" " di.asIntel.hi = 3;\n" "}"); - ASSERT_EQUALS("", errout_str()); + ASSERT_EQUALS("[test.cpp:2:12]: (style) union member 'DoubleInt::asDouble' is never used. [unusedStructMember]\n" + "[test.cpp:3:14]: (style) union member 'DoubleInt::asInt' is never used. [unusedStructMember]\n", + errout_str()); } void structmember2() { From 5cff1563d6164d560abce40b55cda20b29a2753b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 23 Sep 2025 14:46:06 +0200 Subject: [PATCH 3/6] format --- test/testunusedvar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testunusedvar.cpp b/test/testunusedvar.cpp index ff39d0406e7..13979f84e69 100644 --- a/test/testunusedvar.cpp +++ b/test/testunusedvar.cpp @@ -1424,7 +1424,7 @@ class TestUnusedVar : public TestFixture { "}"); ASSERT_EQUALS("[test.cpp:2:12]: (style) union member 'DoubleInt::asDouble' is never used. [unusedStructMember]\n" "[test.cpp:3:14]: (style) union member 'DoubleInt::asInt' is never used. [unusedStructMember]\n", - errout_str()); + errout_str()); } void structmember2() { From 564e05015cfa9ae123b4bc836ed6cc81af4b3950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 23 Sep 2025 18:49:33 +0200 Subject: [PATCH 4/6] simplify --- lib/checkunusedvar.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/checkunusedvar.cpp b/lib/checkunusedvar.cpp index e11a939587e..9d0bdb64059 100644 --- a/lib/checkunusedvar.cpp +++ b/lib/checkunusedvar.cpp @@ -1603,15 +1603,11 @@ void CheckUnusedVar::checkStructMemberUsage() // then don't warn about other struct members bool structInUnionWithUsedMember = false; if (scope.type == ScopeType::eStruct && scope.nestedIn && scope.nestedIn->type == ScopeType::eUnion) { - for (const Variable &unionVar : scope.varlist) { - for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { - if (tok->variable() == &unionVar && tok != unionVar.nameToken()) { - structInUnionWithUsedMember = true; - break; - } - } - if (structInUnionWithUsedMember) + for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { + if (tok->variable() && tok->variable()->scope() == scope.nestedIn) { + structInUnionWithUsedMember = true; break; + } } } @@ -1632,7 +1628,6 @@ void CheckUnusedVar::checkStructMemberUsage() if (structInUnionWithUsedMember) continue; - // Check if the struct member variable is used anywhere in the file bool use = false; for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { From 81298310191c1069917d5d92397f692f38a4bc05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 23 Sep 2025 19:10:42 +0200 Subject: [PATCH 5/6] improve --- lib/checkunusedvar.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/checkunusedvar.cpp b/lib/checkunusedvar.cpp index 9d0bdb64059..969367c4fc8 100644 --- a/lib/checkunusedvar.cpp +++ b/lib/checkunusedvar.cpp @@ -1601,14 +1601,19 @@ void CheckUnusedVar::checkStructMemberUsage() // #7458 - if struct is declared inside union and any struct member is used, // then don't warn about other struct members - bool structInUnionWithUsedMember = false; if (scope.type == ScopeType::eStruct && scope.nestedIn && scope.nestedIn->type == ScopeType::eUnion) { + bool structMemberUsed = false; + for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { - if (tok->variable() && tok->variable()->scope() == scope.nestedIn) { - structInUnionWithUsedMember = true; + if (tok->variable() && tok != tok->variable()->nameToken() && tok->variable()->scope() == &scope) { + structMemberUsed = true; break; } } + + // Skip reporting unused members if this struct is in a union and any member is used + if (structMemberUsed) + continue; } for (const Variable &var : scope.varlist) { @@ -1624,10 +1629,6 @@ void CheckUnusedVar::checkStructMemberUsage() if (mTokenizer->isVarUsedInTemplate(var.declarationId())) continue; - // Skip reporting unused members if this struct is in a union and any member is used - if (structInUnionWithUsedMember) - continue; - // Check if the struct member variable is used anywhere in the file bool use = false; for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { From c93cfd70e3cd157ea6b88571beb09a0bdc52f76b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Wed, 24 Sep 2025 20:21:53 +0200 Subject: [PATCH 6/6] startToken --- lib/checkunusedvar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/checkunusedvar.cpp b/lib/checkunusedvar.cpp index 969367c4fc8..76f28dbf2a5 100644 --- a/lib/checkunusedvar.cpp +++ b/lib/checkunusedvar.cpp @@ -1604,7 +1604,7 @@ void CheckUnusedVar::checkStructMemberUsage() if (scope.type == ScopeType::eStruct && scope.nestedIn && scope.nestedIn->type == ScopeType::eUnion) { bool structMemberUsed = false; - for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { + for (const Token *tok = scope.nestedIn->bodyStart; tok; tok = tok->next()) { if (tok->variable() && tok != tok->variable()->nameToken() && tok->variable()->scope() == &scope) { structMemberUsed = true; break;