diff --git a/lib/checkunusedvar.cpp b/lib/checkunusedvar.cpp index 2fbe6ac3d87..a01de5a87ca 100644 --- a/lib/checkunusedvar.cpp +++ b/lib/checkunusedvar.cpp @@ -1575,6 +1575,9 @@ void CheckUnusedVar::checkStructMemberUsage() if (isInherited && !var.isPrivate()) continue; + if (mTokenizer->isVarUsedInTemplate(var.declarationId())) + 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/lib/templatesimplifier.cpp b/lib/templatesimplifier.cpp index f15a7821881..1abaaa1ea60 100644 --- a/lib/templatesimplifier.cpp +++ b/lib/templatesimplifier.cpp @@ -1647,6 +1647,12 @@ void TemplateSimplifier::expandTemplate( std::vector newInstantiations; + for (const Token* tok = templateInstantiation.token()->next()->findClosingBracket(); + tok && tok != templateInstantiation.token(); tok = tok->previous()) { + if (tok->isName()) + mUsedVariables[newName].insert(tok->str()); + } + // add forward declarations if (copy && isClass) { templateDeclaration.token()->insertTokenBefore(templateDeclarationToken->strAt(1)); diff --git a/lib/templatesimplifier.h b/lib/templatesimplifier.h index ba0e30e651f..35b8ab04e3a 100644 --- a/lib/templatesimplifier.h +++ b/lib/templatesimplifier.h @@ -54,6 +54,10 @@ class CPPCHECKLIB TemplateSimplifier { return mDump; } + const std::map>& getUsedVariables() const { + return mUsedVariables; + } + /** */ void checkComplicatedSyntaxErrorsInTemplates(); @@ -510,6 +514,8 @@ class CPPCHECKLIB TemplateSimplifier { std::vector mTypesUsedInTemplateInstantiation; std::unordered_map mTemplateNamePos; std::string mDump; + + std::map> mUsedVariables; }; /// @} diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index 8e0b515ca78..fa5ca8e15eb 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -4406,10 +4406,26 @@ static void setVarIdStructMembers(Token *&tok1, tok1 = tok; } +static void addTemplateVarIdUsage(const std::string &tokstr, + const std::map>& templateVarUsage, + const std::unordered_map& variableMap, + std::set& templateVarIdUsage) { + const auto v = templateVarUsage.find(tokstr); + if (v != templateVarUsage.end()) { + for (const std::string& varname: v->second) { + const auto it = variableMap.find(varname); + if (it != variableMap.end()) + templateVarIdUsage.insert(it->second); + } + } +} + static bool setVarIdClassDeclaration(Token* const startToken, VariableMap& variableMap, const nonneg int scopeStartVarId, - std::map>& structMembers) + const std::map>& templateVarUsage, + std::map>& structMembers, + std::set& templateVarIdUsage) { // end of scope const Token* const endToken = startToken->link(); @@ -4476,6 +4492,8 @@ static bool setVarIdClassDeclaration(Token* const startToken, if (it != variableMap.map(false).end()) { tok->varId(it->second); setVarIdStructMembers(tok, structMembers, variableMap.getVarId()); + } else if (tok->str().back() == '>') { + addTemplateVarIdUsage(tok->str(), templateVarUsage, variableMap.map(false), templateVarIdUsage); } } } @@ -4655,7 +4673,9 @@ void Tokenizer::setVarIdPass1() if (!setVarIdClassDeclaration(tok->link(), variableMap, scopeStack.top().startVarid, - structMembers)) { + mTemplateSimplifier->getUsedVariables(), + structMembers, + mTemplateVarIdUsage)) { syntaxError(nullptr); } } @@ -4742,6 +4762,16 @@ void Tokenizer::setVarIdPass1() if (decl) { if (cpp) { + for (const Token* tok3 = tok->next(); tok3->isName(); tok3 = tok3->next()) { + addTemplateVarIdUsage(tok3->str(), + mTemplateSimplifier->getUsedVariables(), + variableMap.map(false), + mTemplateVarIdUsage); + addTemplateVarIdUsage(tok3->str(), + mTemplateSimplifier->getUsedVariables(), + variableMap.map(true), + mTemplateVarIdUsage); + } if (Token *declTypeTok = Token::findsimplematch(tok, "decltype (", tok2)) { for (Token *declTok = declTypeTok->linkAt(1); declTok != declTypeTok; declTok = declTok->previous()) { if (declTok->isName() && !Token::Match(declTok->previous(), "::|.") && variableMap.hasVariable(declTok->str())) @@ -4855,6 +4885,13 @@ void Tokenizer::setVarIdPass1() continue; } + if (tok->str().back() == '>') { + addTemplateVarIdUsage(tok->str(), + mTemplateSimplifier->getUsedVariables(), + variableMap.map(globalNamespace), + mTemplateVarIdUsage); + } + // function declaration inside executable scope? Function declaration is of form: type name "(" args ")" if (scopeStack.top().isExecutable && !scopeStack.top().isStructInit && Token::Match(tok, "%name% [,)[]")) { bool par = false; @@ -6171,6 +6208,12 @@ void Tokenizer::dump(std::ostream &out) const outs += dumpTypedefInfo(); outs += mTemplateSimplifier->dump(); + if (!mTemplateVarIdUsage.empty()) { + outs += " \n"; + for (nonneg int id: mTemplateVarIdUsage) + outs += " \n"; + outs += " \n"; + } out << outs; } diff --git a/lib/tokenize.h b/lib/tokenize.h index 9770ec2d288..150ad6d6fb6 100644 --- a/lib/tokenize.h +++ b/lib/tokenize.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -81,6 +82,10 @@ class CPPCHECKLIB Tokenizer { bool simplifyTokens1(const std::string &configuration, int fileIndex=0); + bool isVarUsedInTemplate(nonneg int id) const { + return mTemplateVarIdUsage.count(id) != 0; + } + private: /** Set variable id */ void setVarId(); @@ -641,6 +646,8 @@ class CPPCHECKLIB Tokenizer { TemplateSimplifier * const mTemplateSimplifier; + std::set mTemplateVarIdUsage; + /** E.g. "A" for code where "#ifdef A" is true. This is used to print additional information in error situations. */ std::string mConfiguration; diff --git a/test/testunusedvar.cpp b/test/testunusedvar.cpp index 5350535d42f..ac23e1e75f4 100644 --- a/test/testunusedvar.cpp +++ b/test/testunusedvar.cpp @@ -71,6 +71,7 @@ class TestUnusedVar : public TestFixture { TEST_CASE(structmember26); // #13345 TEST_CASE(structmember27); // #13367 TEST_CASE(structmember_macro); + TEST_CASE(structmember_template_argument); // #13887 - do not report that member used in template argument is unused TEST_CASE(classmember); TEST_CASE(structmemberStructuredBinding); // #13107 @@ -2003,6 +2004,15 @@ class TestUnusedVar : public TestFixture { ASSERT_EQUALS("", errout_str()); } + void structmember_template_argument() { // #13887 - False positive + checkStructMemberUsage("template struct A{ T buf[i]; }\n" + "struct B {\n" + " constexpr int x = 20;\n" // <- not unused + " A a;\n" // <- unused + "};"); + ASSERT_EQUALS("[test.cpp:4:20]: (style) struct member 'B::a' is never used. [unusedStructMember]\n", errout_str()); + } + void classmember() { checkStructMemberUsage("class C {\n" " int i{};\n"