Skip to content

Commit 255e8a3

Browse files
committed
Fix this instantiation
1 parent 9283a76 commit 255e8a3

4 files changed

Lines changed: 178 additions & 11 deletions

File tree

‎lib/templatesimplifier.cpp‎

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4699,11 +4699,13 @@ void TemplateSimplifier::simplifyTemplates(const std::time_t maxtime)
46994699
Token * tok = it->token();
47004700
tok->deleteNext(2);
47014701
tok->deleteThis();
4702-
} else if (shouldDeferRemoval(*it)) {
4702+
} else if (shouldDeferRemoval(*it) || hasRemainingCalls(*it)) {
47034703
// don't remove the declaration yet: pending type deductions may
47044704
// instantiate this function template again, and during the type
47054705
// deduction rounds the symbol database has references to these
4706-
// tokens. It is removed later by removeDeferredTemplateDeclarations().
4706+
// tokens. It is removed later by removeDeferredTemplateDeclarations()
4707+
// - unless the template is still called without template arguments:
4708+
// those calls refer to the generic definition.
47074709
auto it1 = mTemplateForwardDeclarationsMap.find(it->token());
47084710
if (it1 != mTemplateForwardDeclarationsMap.end())
47094711
deferRemoval(it1->second);
@@ -4871,6 +4873,75 @@ void TemplateSimplifier::rememberDirtyCallSite(Token* nameTok)
48714873
mDirtyCallSites.push_back(nameTok);
48724874
}
48734875

4876+
// The tokens of the given template declarations, from "template" to the end of each
4877+
// declaration
4878+
static std::unordered_set<const Token*> declarationTokens(const std::vector<const Token*>& declarations)
4879+
{
4880+
std::unordered_set<const Token*> tokens;
4881+
for (const Token* declTok : declarations) {
4882+
const Token* end = TemplateSimplifier::findTemplateDeclarationEnd(declTok);
4883+
if (!end)
4884+
continue;
4885+
for (const Token* tok = declTok; tok; tok = tok->next()) {
4886+
tokens.insert(tok);
4887+
if (tok == end)
4888+
break;
4889+
}
4890+
}
4891+
return tokens;
4892+
}
4893+
4894+
// Is the token the name of a plain call "f ( x )" - not a declaration "int f ( int x )"?
4895+
static bool isPlainCall(const Token* tok)
4896+
{
4897+
if (!tok->isName() || tok->isKeyword() || tok->strAt(1) != "(")
4898+
return false;
4899+
// skip the qualification: "N :: f ( x )"
4900+
const Token* prev = tok->previous();
4901+
while (Token::simpleMatch(prev, "::") && prev->previous() && prev->previous()->isName())
4902+
prev = prev->tokAt(-2);
4903+
if (!prev)
4904+
return false;
4905+
// "return f ( x )" is a call, "int f ( int x )" / "T * f ( T t )" is a declaration
4906+
if (Token::Match(prev, "return|case|throw|else|do|co_return|co_yield|co_await|sizeof"))
4907+
return true;
4908+
return !Token::Match(prev, "%name%|*|&|&&|>");
4909+
}
4910+
4911+
// Are there plain calls "name ( x )" of the function template left in the token list,
4912+
// outside the tokens of its declarations? Such a call - in an uninstantiated template,
4913+
// or with arguments whose types are unknown - could not be instantiated and refers to
4914+
// the generic definition, so the definition must not be removed.
4915+
static bool hasRemainingCalls(const TokenList& tokenList,
4916+
const std::string& name,
4917+
const std::unordered_set<const Token*>& declarations)
4918+
{
4919+
for (const Token* tok = tokenList.front(); tok; tok = tok->next()) {
4920+
if (tok->str() == name && declarations.count(tok) == 0 && isPlainCall(tok))
4921+
return true;
4922+
}
4923+
return false;
4924+
}
4925+
4926+
// Is the name token the name of a function template whose calls can be written without
4927+
// template arguments? A member function of a class template ("C < T > :: f") is
4928+
// instantiated with its class, never from a call.
4929+
static bool isDeducibleFunctionTemplateName(const Token* nameTok)
4930+
{
4931+
return nameTok && nameTok->strAt(1) == "(" && !Token::simpleMatch(nameTok->tokAt(-2), "> ::");
4932+
}
4933+
4934+
bool TemplateSimplifier::hasRemainingCalls(const TokenAndName& declaration) const
4935+
{
4936+
if (!declaration.isFunction() || !isDeducibleFunctionTemplateName(declaration.nameToken()))
4937+
return false;
4938+
std::vector<const Token*> declarations{declaration.token()};
4939+
const auto it = mTemplateForwardDeclarationsMap.find(declaration.token());
4940+
if (it != mTemplateForwardDeclarationsMap.cend())
4941+
declarations.push_back(it->second);
4942+
return ::hasRemainingCalls(mTokenList, declaration.name(), declarationTokens(declarations));
4943+
}
4944+
48744945
bool TemplateSimplifier::removeDeferredTemplateDeclarations(SymbolDatabase* symbolDatabase)
48754946
{
48764947
if (mDeferredRemovals.empty())
@@ -4886,18 +4957,27 @@ bool TemplateSimplifier::removeDeferredTemplateDeclarations(SymbolDatabase* symb
48864957
}
48874958
mDeferredRemovals.clear();
48884959

4889-
// all tokens that are going to be removed
4890-
std::unordered_set<const Token*> removedTokens;
4960+
// a function template that is still called without template arguments (the
4961+
// arguments could not be deduced) is kept - the calls refer to the generic definition
4962+
const std::unordered_set<const Token*> deferredTokens(
4963+
declarationTokens(std::vector<const Token*>(declarations.cbegin(), declarations.cend())));
4964+
std::vector<Token*> removedDeclarations;
48914965
for (Token* declTok : declarations) {
4892-
const Token* end = findTemplateDeclarationEnd(declTok);
4893-
if (!end)
4894-
continue;
4895-
for (const Token* tok = declTok; tok; tok = tok->next()) {
4896-
removedTokens.insert(tok);
4897-
if (tok == end)
4898-
break;
4966+
const Token* nameTok = nullptr;
4967+
if (const Token* paramEnd = declTok->next()->findClosingBracket()) {
4968+
const int namepos = getTemplateNamePosition(paramEnd);
4969+
if (namepos > 0)
4970+
nameTok = paramEnd->tokAt(namepos);
48994971
}
4972+
if (isDeducibleFunctionTemplateName(nameTok) && ::hasRemainingCalls(mTokenList, nameTok->str(), deferredTokens))
4973+
continue;
4974+
removedDeclarations.push_back(declTok);
49004975
}
4976+
declarations = std::move(removedDeclarations);
4977+
4978+
// all tokens that are going to be removed
4979+
const std::unordered_set<const Token*> removedTokens(
4980+
declarationTokens(std::vector<const Token*>(declarations.cbegin(), declarations.cend())));
49014981

49024982
// dirty call sites inside the removed declarations are gone
49034983
mDirtyCallSites.erase(std::remove_if(mDirtyCallSites.begin(),

‎lib/templatesimplifier.h‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,14 @@ class CPPCHECKLIB TemplateSimplifier {
448448
*/
449449
void deferRemoval(Token* declTok);
450450

451+
/**
452+
* Are there calls of the function template left in the token list whose template
453+
* arguments could not be deduced ("f ( x )" in an uninstantiated template, or with
454+
* arguments of unknown type)? Those calls refer to the generic definition, so the
455+
* declaration must not be removed after the instantiation.
456+
*/
457+
bool hasRemainingCalls(const TokenAndName& declaration) const;
458+
451459
/**
452460
* Whether removing the given instantiated template declaration must be deferred
453461
* with deferRemoval() instead of removing it immediately.

‎test/testsimplifytemplate.cpp‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ class TestSimplifyTemplate : public TestFixture {
319319
TEST_CASE(templateTypeDeductionOutOfLine); // member template defined out of line, class in a namespace
320320
TEST_CASE(templateTypeDeductionShadowedType); // the deduced type name means another type in the template's scope
321321
TEST_CASE(templateTypeDeductionRecursion); // recursive calls are deduced, not renamed to the current instantiation
322+
TEST_CASE(templateTypeDeductionRemainingCalls); // the generic definition stays while undeduced calls remain
322323
TEST_CASE(templateTypeDeductionEnum); // enum arguments deduce the enum, not its underlying type
323324
TEST_CASE(templateTypeDeductionArray); // array arguments: only the decayed dimension is a pointer
324325
TEST_CASE(templateTypeDeductionConstPointer); // "const T *": the parameter provides the pointee const
@@ -7562,6 +7563,63 @@ class TestSimplifyTemplate : public TestFixture {
75627563
}
75637564
}
75647565

7566+
void templateTypeDeductionRemainingCalls()
7567+
{ // a call whose template arguments can not be deduced - in an uninstantiated class
7568+
// template here - refers to the generic definition: it must not be removed after
7569+
// another call instantiated the template
7570+
{
7571+
const char code[] = "template<class T> int get(const T& t) { return t.count; }\n"
7572+
"template<class U> struct Box { U item; int count; int size() const { return get(*this); } };\n"
7573+
"struct Bag { int count; };\n"
7574+
"int total(const Bag& b) { return get(b); }";
7575+
const char exp[] = "int get<Bag> ( const Bag & t ) ; "
7576+
"template < class T > int get ( const T & t ) { return t . count ; } "
7577+
"template < class U > struct Box { U item ; int count ; int size ( ) const { return get ( * this ) ; } } ; "
7578+
"struct Bag { int count ; } ; "
7579+
"int total ( const Bag & b ) { return get<Bag> ( b ) ; } "
7580+
"int get<Bag> ( const Bag & t ) { return t . count ; }";
7581+
ASSERT_EQUALS(exp, tok(code));
7582+
ASSERT_EQUALS(exp, tok(code, dinit(CheckOptions, $.templateFullRebuild = true)));
7583+
}
7584+
{ // explicit template arguments: same result
7585+
const char code[] = "template<class T> int get(const T& t) { return t.count; }\n"
7586+
"template<class U> struct Box { U item; int count; int size() const { return get(*this); } };\n"
7587+
"struct Bag { int count; };\n"
7588+
"int total(const Bag& b) { return get<Bag>(b); }";
7589+
const char exp[] = "int get<Bag> ( const Bag & t ) ; "
7590+
"template < class T > int get ( const T & t ) { return t . count ; } "
7591+
"template < class U > struct Box { U item ; int count ; int size ( ) const { return get ( * this ) ; } } ; "
7592+
"struct Bag { int count ; } ; "
7593+
"int total ( const Bag & b ) { return get<Bag> ( b ) ; } "
7594+
"int get<Bag> ( const Bag & t ) { return t . count ; }";
7595+
ASSERT_EQUALS(exp, tok(code));
7596+
}
7597+
{ // a forward declaration is kept with the definition
7598+
const char code[] = "template<class T> int get(const T& t);\n"
7599+
"template<class T> int get(const T& t) { return t.count; }\n"
7600+
"template<class U> struct Box { int count; int size() const { return get(*this); } };\n"
7601+
"struct Bag { int count; };\n"
7602+
"int total(const Bag& b) { return get(b); }";
7603+
const std::string result = tok(code);
7604+
ASSERT(result.find("template < class T > int get ( const T & t ) ;") != std::string::npos);
7605+
ASSERT(result.find("template < class T > int get ( const T & t ) { return t . count ; }") != std::string::npos);
7606+
ASSERT(result.find("int get<Bag> ( const Bag & t ) { return t . count ; }") != std::string::npos);
7607+
}
7608+
{ // when all calls are instantiated the generic definition is removed as before; a
7609+
// function with the same name in another class is not a call
7610+
const char code[] = "template<class T> int get(const T& t) { return t.count; }\n"
7611+
"struct Other { int get(int x); };\n"
7612+
"struct Bag { int count; };\n"
7613+
"int total(const Bag& b) { return get(b); }";
7614+
const char exp[] = "int get<Bag> ( const Bag & t ) ; "
7615+
"struct Other { int get ( int x ) ; } ; "
7616+
"struct Bag { int count ; } ; "
7617+
"int total ( const Bag & b ) { return get<Bag> ( b ) ; } "
7618+
"int get<Bag> ( const Bag & t ) { return t . count ; }";
7619+
ASSERT_EQUALS(exp, tok(code));
7620+
}
7621+
}
7622+
75657623
void templateTypeDeductionEnum()
75667624
{
75677625
{ // an enum variable has the ValueType of the underlying integer type - the enum
@@ -7647,10 +7705,14 @@ class TestSimplifyTemplate : public TestFixture {
76477705
" f(a); g(a); f(b); h(b); k(b); f(s.m); f(p->m); f(&a); f(q); f(*b); g(c ? a : a);\n"
76487706
" f(i); h(a); k(a); f(a[0]); h(b[0]); k(q); h(&a[0]); k(&i);\n"
76497707
"}";
7708+
// the generic definitions stay because of the calls that are not deduced
76507709
const char exp[] = "void f<int> ( int & x ) ; "
7710+
"template < class T > void f ( T & x ) { } "
76517711
"template < class T > void g ( const T & x ) { } "
76527712
"void h<int> ( int * x ) ; "
7713+
"template < class T > void h ( T * x ) { } "
76537714
"void k<int*> ( int * x ) ; void k<int**> ( int * * x ) ; "
7715+
"template < class T > void k ( T x ) { } "
76547716
"struct S { int m [ 2 ] ; } ; "
76557717
"void test ( S s , S * p , bool c ) { "
76567718
"int a [ 3 ] ; int b [ 2 ] [ 3 ] ; int * q [ 2 ] ; int i ; "

‎test/testunusedvar.cpp‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ class TestUnusedVar : public TestFixture {
8383
TEST_CASE(structmember35);
8484
TEST_CASE(structmember_macro);
8585
TEST_CASE(structmember_template_argument); // #13887 - do not report that member used in template argument is unused
86+
TEST_CASE(structmember_template_function); // member used through a function template that is instantiated for another type
8687
TEST_CASE(classmember);
8788
TEST_CASE(structmemberStructuredBinding); // #13107
8889

@@ -2125,6 +2126,22 @@ class TestUnusedVar : public TestFixture {
21252126
ASSERT_EQUALS("", errout_str());
21262127
}
21272128

2129+
void structmember_template_function() {
2130+
// the generic get() is still called from the uninstantiated Box: it must not be
2131+
// removed when get<Bag> is instantiated, otherwise Box::count has no use left
2132+
checkStructMemberUsage("template<class T> int get(const T& t) { return t.count; }\n"
2133+
"template<class U> struct Box { U item; int count; int size() const { return get(*this); } };\n"
2134+
"struct Bag { int count; };\n"
2135+
"int total(const Bag& b) { return get(b); }\n");
2136+
ASSERT_EQUALS("", errout_str());
2137+
2138+
checkStructMemberUsage("template<class T> int get(const T& t) { return t.count; }\n"
2139+
"template<class U> struct Box { U item; int count; int size() const { return get(*this); } };\n"
2140+
"struct Bag { int count; };\n"
2141+
"int total(const Bag& b) { return get<Bag>(b); }\n");
2142+
ASSERT_EQUALS("", errout_str());
2143+
}
2144+
21282145
void structmember_template_argument() { // #13887 - False positive
21292146
checkStructMemberUsage("template <class T, int i> struct A{ T buf[i]; }\n"
21302147
"struct B {\n"

0 commit comments

Comments
 (0)