Update symbol database such that the override keyword implies that the function is also virtual (#1907)
* Update symbol database such that the override keyword implies that the function is also virtual * Add test case for implicit override * change isVirtual to hasVirtualSpecifier * fix method documentation for getVirtualFunctionCalls and getFirstVirtualFunctionCallStack * Fix isImplicitlyVirtual to consider the override keyword and document logic * Fix getFirstVirtualFunctionCallStack and getVirtualFunctionCalls to use isImplicitlyVirtual instead of isVirtual so new test case passes
This commit is contained in:
parent
999aa407f4
commit
7e54f989f9
|
@ -1228,7 +1228,7 @@ void CheckClass::checkMemsetType(const Scope *start, const Token *tok, const Sco
|
||||||
|
|
||||||
// Warn if type is a class that contains any virtual functions
|
// Warn if type is a class that contains any virtual functions
|
||||||
for (const Function &func : type->functionList) {
|
for (const Function &func : type->functionList) {
|
||||||
if (func.isVirtual()) {
|
if (func.hasVirtualSpecifier()) {
|
||||||
if (allocation)
|
if (allocation)
|
||||||
mallocOnClassError(tok, tok->str(), type->classDef, "virtual function");
|
mallocOnClassError(tok, tok->str(), type->classDef, "virtual function");
|
||||||
else
|
else
|
||||||
|
@ -1651,9 +1651,9 @@ void CheckClass::virtualDestructor()
|
||||||
if (scope->definedType->derivedFrom.empty()) {
|
if (scope->definedType->derivedFrom.empty()) {
|
||||||
if (printInconclusive) {
|
if (printInconclusive) {
|
||||||
const Function *destructor = scope->getDestructor();
|
const Function *destructor = scope->getDestructor();
|
||||||
if (destructor && !destructor->isVirtual()) {
|
if (destructor && !destructor->hasVirtualSpecifier()) {
|
||||||
for (const Function &func : scope->functionList) {
|
for (const Function &func : scope->functionList) {
|
||||||
if (func.isVirtual()) {
|
if (func.hasVirtualSpecifier()) {
|
||||||
inconclusiveErrors.push_back(destructor);
|
inconclusiveErrors.push_back(destructor);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1736,7 +1736,7 @@ void CheckClass::virtualDestructor()
|
||||||
if (derivedFrom->derivedFrom.empty()) {
|
if (derivedFrom->derivedFrom.empty()) {
|
||||||
virtualDestructorError(derivedFrom->classDef, derivedFrom->name(), derivedClass->str(), false);
|
virtualDestructorError(derivedFrom->classDef, derivedFrom->name(), derivedClass->str(), false);
|
||||||
}
|
}
|
||||||
} else if (!baseDestructor->isVirtual()) {
|
} else if (!baseDestructor->hasVirtualSpecifier()) {
|
||||||
// TODO: This is just a temporary fix, better solution is needed.
|
// TODO: This is just a temporary fix, better solution is needed.
|
||||||
// Skip situations where base class has base classes of its own, because
|
// Skip situations where base class has base classes of its own, because
|
||||||
// some of the base classes might have virtual destructor.
|
// some of the base classes might have virtual destructor.
|
||||||
|
@ -1827,7 +1827,7 @@ void CheckClass::checkConst()
|
||||||
if (func.type != Function::eFunction || !func.hasBody())
|
if (func.type != Function::eFunction || !func.hasBody())
|
||||||
continue;
|
continue;
|
||||||
// don't warn for friend/static/virtual functions
|
// don't warn for friend/static/virtual functions
|
||||||
if (func.isFriend() || func.isStatic() || func.isVirtual())
|
if (func.isFriend() || func.isStatic() || func.hasVirtualSpecifier())
|
||||||
continue;
|
continue;
|
||||||
// get last token of return type
|
// get last token of return type
|
||||||
const Token *previous = func.tokenDef->previous();
|
const Token *previous = func.tokenDef->previous();
|
||||||
|
@ -2369,7 +2369,7 @@ const std::list<const Token *> & CheckClass::getVirtualFunctionCalls(const Funct
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callFunction->isVirtual()) {
|
if (callFunction->isImplicitlyVirtual()) {
|
||||||
if (!callFunction->isPure() && Token::simpleMatch(tok->previous(), "::"))
|
if (!callFunction->isPure() && Token::simpleMatch(tok->previous(), "::"))
|
||||||
continue;
|
continue;
|
||||||
virtualFunctionCalls.push_back(tok);
|
virtualFunctionCalls.push_back(tok);
|
||||||
|
@ -2389,7 +2389,7 @@ void CheckClass::getFirstVirtualFunctionCallStack(
|
||||||
std::list<const Token *> & pureFuncStack)
|
std::list<const Token *> & pureFuncStack)
|
||||||
{
|
{
|
||||||
const Function *callFunction = callToken->function();
|
const Function *callFunction = callToken->function();
|
||||||
if (callFunction->isVirtual() && (!callFunction->isPure() || !callFunction->hasBody())) {
|
if (callFunction->isImplicitlyVirtual() && (!callFunction->isPure() || !callFunction->hasBody())) {
|
||||||
pureFuncStack.push_back(callFunction->tokenDef);
|
pureFuncStack.push_back(callFunction->tokenDef);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -317,7 +317,7 @@ private:
|
||||||
void initializeVarList(const Function &func, std::list<const Function *> &callstack, const Scope *scope, std::vector<Usage> &usage);
|
void initializeVarList(const Function &func, std::list<const Function *> &callstack, const Scope *scope, std::vector<Usage> &usage);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief gives a list of tokens where pure virtual functions are called directly or indirectly
|
* @brief gives a list of tokens where virtual functions are called directly or indirectly
|
||||||
* @param function function to be checked
|
* @param function function to be checked
|
||||||
* @param virtualFunctionCallsMap map of results for already checked functions
|
* @param virtualFunctionCallsMap map of results for already checked functions
|
||||||
* @return list of tokens where pure virtual functions are called
|
* @return list of tokens where pure virtual functions are called
|
||||||
|
@ -327,7 +327,7 @@ private:
|
||||||
std::map<const Function *, std::list<const Token *> > & virtualFunctionCallsMap);
|
std::map<const Function *, std::list<const Token *> > & virtualFunctionCallsMap);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief looks for the first pure virtual function call stack
|
* @brief looks for the first virtual function call stack
|
||||||
* @param virtualFunctionCallsMap map of results obtained from getVirtualFunctionCalls
|
* @param virtualFunctionCallsMap map of results obtained from getVirtualFunctionCalls
|
||||||
* @param callToken token where pure virtual function is called directly or indirectly
|
* @param callToken token where pure virtual function is called directly or indirectly
|
||||||
* @param[in,out] pureFuncStack list to append the stack
|
* @param[in,out] pureFuncStack list to append the stack
|
||||||
|
|
|
@ -1239,7 +1239,7 @@ void CheckOther::checkPassByReference()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if variable could be const
|
// Check if variable could be const
|
||||||
if (!var->scope() || var->scope()->function->isVirtual())
|
if (!var->scope() || var->scope()->function->hasVirtualSpecifier())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (canBeConst(var)) {
|
if (canBeConst(var)) {
|
||||||
|
|
|
@ -1757,7 +1757,7 @@ Function::Function(const Tokenizer *mTokenizer, const Token *tok, const Scope *s
|
||||||
|
|
||||||
// virtual function
|
// virtual function
|
||||||
else if (tok1->str() == "virtual") {
|
else if (tok1->str() == "virtual") {
|
||||||
isVirtual(true);
|
hasVirtualSpecifier(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// static function
|
// static function
|
||||||
|
@ -2695,7 +2695,7 @@ void SymbolDatabase::printOut(const char *title) const
|
||||||
std::cout << " hasBody: " << func->hasBody() << std::endl;
|
std::cout << " hasBody: " << func->hasBody() << std::endl;
|
||||||
std::cout << " isInline: " << func->isInline() << std::endl;
|
std::cout << " isInline: " << func->isInline() << std::endl;
|
||||||
std::cout << " isConst: " << func->isConst() << std::endl;
|
std::cout << " isConst: " << func->isConst() << std::endl;
|
||||||
std::cout << " isVirtual: " << func->isVirtual() << std::endl;
|
std::cout << " hasVirtualSpecifier: " << func->hasVirtualSpecifier() << std::endl;
|
||||||
std::cout << " isPure: " << func->isPure() << std::endl;
|
std::cout << " isPure: " << func->isPure() << std::endl;
|
||||||
std::cout << " isStatic: " << func->isStatic() << std::endl;
|
std::cout << " isStatic: " << func->isStatic() << std::endl;
|
||||||
std::cout << " isStaticLocal: " << func->isStaticLocal() << std::endl;
|
std::cout << " isStaticLocal: " << func->isStaticLocal() << std::endl;
|
||||||
|
@ -2951,8 +2951,8 @@ void SymbolDatabase::printXml(std::ostream &out) const
|
||||||
function->type == Function::eFunction ? "Function" :
|
function->type == Function::eFunction ? "Function" :
|
||||||
"Unknown") << '\"';
|
"Unknown") << '\"';
|
||||||
if (function->nestedIn->definedType) {
|
if (function->nestedIn->definedType) {
|
||||||
if (function->isVirtual())
|
if (function->hasVirtualSpecifier())
|
||||||
out << " isVirtual=\"true\"";
|
out << " hasVirtualSpecifier=\"true\"";
|
||||||
else if (function->isImplicitlyVirtual())
|
else if (function->isImplicitlyVirtual())
|
||||||
out << " isImplicitlyVirtual=\"true\"";
|
out << " isImplicitlyVirtual=\"true\"";
|
||||||
}
|
}
|
||||||
|
@ -3144,14 +3144,16 @@ void Function::addArguments(const SymbolDatabase *symbolDatabase, const Scope *s
|
||||||
|
|
||||||
bool Function::isImplicitlyVirtual(bool defaultVal) const
|
bool Function::isImplicitlyVirtual(bool defaultVal) const
|
||||||
{
|
{
|
||||||
if (isVirtual())
|
if (hasVirtualSpecifier()) //If it has the virtual specifier it's definitely virtual
|
||||||
|
return true;
|
||||||
|
if (hasOverrideSpecifier()) //If it has the override specifier then it's either virtual or not going to compile
|
||||||
return true;
|
return true;
|
||||||
bool foundAllBaseClasses = true;
|
bool foundAllBaseClasses = true;
|
||||||
if (getOverriddenFunction(&foundAllBaseClasses))
|
if (getOverriddenFunction(&foundAllBaseClasses)) //If it overrides a base class's method then it's virtual
|
||||||
return true;
|
return true;
|
||||||
if (foundAllBaseClasses)
|
if (foundAllBaseClasses) //If we've seen all the base classes and none of the above were true then it must not be virtual
|
||||||
return false;
|
return false;
|
||||||
return defaultVal;
|
return defaultVal; //If we can't see all the bases classes then we can't say conclusively
|
||||||
}
|
}
|
||||||
|
|
||||||
const Function *Function::getOverriddenFunction(bool *foundAllBaseClasses) const
|
const Function *Function::getOverriddenFunction(bool *foundAllBaseClasses) const
|
||||||
|
@ -3180,7 +3182,7 @@ const Function * Function::getOverriddenFunctionRecursive(const ::Type* baseType
|
||||||
// check if function defined in base class
|
// check if function defined in base class
|
||||||
for (std::multimap<std::string, const Function *>::const_iterator it = parent->functionMap.find(tokenDef->str()); it != parent->functionMap.end() && it->first == tokenDef->str(); ++it) {
|
for (std::multimap<std::string, const Function *>::const_iterator it = parent->functionMap.find(tokenDef->str()); it != parent->functionMap.end() && it->first == tokenDef->str(); ++it) {
|
||||||
const Function * func = it->second;
|
const Function * func = it->second;
|
||||||
if (func->isVirtual()) { // Base is virtual and of same name
|
if (func->hasVirtualSpecifier()) { // Base is virtual and of same name
|
||||||
const Token *temp1 = func->tokenDef->previous();
|
const Token *temp1 = func->tokenDef->previous();
|
||||||
const Token *temp2 = tokenDef->previous();
|
const Token *temp2 = tokenDef->previous();
|
||||||
bool match = true;
|
bool match = true;
|
||||||
|
|
|
@ -665,7 +665,7 @@ class CPPCHECKLIB Function {
|
||||||
fHasBody = (1 << 0), ///< @brief has implementation
|
fHasBody = (1 << 0), ///< @brief has implementation
|
||||||
fIsInline = (1 << 1), ///< @brief implementation in class definition
|
fIsInline = (1 << 1), ///< @brief implementation in class definition
|
||||||
fIsConst = (1 << 2), ///< @brief is const
|
fIsConst = (1 << 2), ///< @brief is const
|
||||||
fIsVirtual = (1 << 3), ///< @brief is virtual
|
fHasVirtualSpecifier = (1 << 3), ///< @brief does declaration contain 'virtual' specifier
|
||||||
fIsPure = (1 << 4), ///< @brief is pure virtual
|
fIsPure = (1 << 4), ///< @brief is pure virtual
|
||||||
fIsStatic = (1 << 5), ///< @brief is static
|
fIsStatic = (1 << 5), ///< @brief is static
|
||||||
fIsStaticLocal = (1 << 6), ///< @brief is static local
|
fIsStaticLocal = (1 << 6), ///< @brief is static local
|
||||||
|
@ -771,8 +771,8 @@ public:
|
||||||
bool isConst() const {
|
bool isConst() const {
|
||||||
return getFlag(fIsConst);
|
return getFlag(fIsConst);
|
||||||
}
|
}
|
||||||
bool isVirtual() const {
|
bool hasVirtualSpecifier() const {
|
||||||
return getFlag(fIsVirtual);
|
return getFlag(fHasVirtualSpecifier);
|
||||||
}
|
}
|
||||||
bool isPure() const {
|
bool isPure() const {
|
||||||
return getFlag(fIsPure);
|
return getFlag(fIsPure);
|
||||||
|
@ -878,8 +878,8 @@ private:
|
||||||
void isConst(bool state) {
|
void isConst(bool state) {
|
||||||
setFlag(fIsConst, state);
|
setFlag(fIsConst, state);
|
||||||
}
|
}
|
||||||
void isVirtual(bool state) {
|
void hasVirtualSpecifier(bool state) {
|
||||||
setFlag(fIsVirtual, state);
|
setFlag(fHasVirtualSpecifier, state);
|
||||||
}
|
}
|
||||||
void isPure(bool state) {
|
void isPure(bool state) {
|
||||||
setFlag(fIsPure, state);
|
setFlag(fIsPure, state);
|
||||||
|
|
|
@ -4836,7 +4836,7 @@ static void valueFlowFunctionReturn(TokenList *tokenlist, ErrorLogger *errorLogg
|
||||||
&error);
|
&error);
|
||||||
if (!error) {
|
if (!error) {
|
||||||
ValueFlow::Value v(result);
|
ValueFlow::Value v(result);
|
||||||
if (function->isVirtual())
|
if (function->hasVirtualSpecifier())
|
||||||
v.setPossible();
|
v.setPossible();
|
||||||
else
|
else
|
||||||
v.setKnown();
|
v.setKnown();
|
||||||
|
|
|
@ -6828,6 +6828,23 @@ private:
|
||||||
"int A::f() { return 1; }\n");
|
"int A::f() { return 1; }\n");
|
||||||
ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:2]: (warning) Virtual function 'f' is called from constructor 'A()' at line 3. Dynamic binding is not used.\n", errout.str());
|
ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:2]: (warning) Virtual function 'f' is called from constructor 'A()' at line 3. Dynamic binding is not used.\n", errout.str());
|
||||||
|
|
||||||
|
checkVirtualFunctionCall("class A : B {\n"
|
||||||
|
" int f() override;\n"
|
||||||
|
" A() {f();}\n"
|
||||||
|
"};\n"
|
||||||
|
"int A::f() { return 1; }\n");
|
||||||
|
ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:2]: (warning) Virtual function 'f' is called from constructor 'A()' at line 3. Dynamic binding is not used.\n", errout.str());
|
||||||
|
|
||||||
|
checkVirtualFunctionCall("class B {\n"
|
||||||
|
" virtual int f() = 0;\n"
|
||||||
|
"};\n"
|
||||||
|
"class A : B {\n"
|
||||||
|
" int f();\n"
|
||||||
|
" A() {f();}\n"
|
||||||
|
"};\n"
|
||||||
|
"int A::f() { return 1; }\n");
|
||||||
|
ASSERT_EQUALS("[test.cpp:6] -> [test.cpp:5]: (warning) Virtual function 'f' is called from constructor 'A()' at line 6. Dynamic binding is not used.\n", errout.str());
|
||||||
|
|
||||||
checkVirtualFunctionCall("class A\n"
|
checkVirtualFunctionCall("class A\n"
|
||||||
"{\n"
|
"{\n"
|
||||||
" A() { A::f(); }\n"
|
" A() { A::f(); }\n"
|
||||||
|
|
|
@ -4070,7 +4070,7 @@ private:
|
||||||
const Token *f = db ? Token::findsimplematch(tokenizer.tokens(), "get_endpoint_url ( ) const noexcept ;") : nullptr;
|
const Token *f = db ? Token::findsimplematch(tokenizer.tokens(), "get_endpoint_url ( ) const noexcept ;") : nullptr;
|
||||||
ASSERT(f != nullptr);
|
ASSERT(f != nullptr);
|
||||||
ASSERT(f && f->function() && f->function()->token->linenr() == 2);
|
ASSERT(f && f->function() && f->function()->token->linenr() == 2);
|
||||||
ASSERT(f && f->function() && f->function()->isVirtual());
|
ASSERT(f && f->function() && f->function()->hasVirtualSpecifier());
|
||||||
ASSERT(f && f->function() && !f->function()->hasOverrideSpecifier());
|
ASSERT(f && f->function() && !f->function()->hasOverrideSpecifier());
|
||||||
ASSERT(f && f->function() && !f->function()->hasFinalSpecifier());
|
ASSERT(f && f->function() && !f->function()->hasFinalSpecifier());
|
||||||
ASSERT(f && f->function() && f->function()->isConst());
|
ASSERT(f && f->function() && f->function()->isConst());
|
||||||
|
@ -4078,7 +4078,7 @@ private:
|
||||||
f = db ? Token::findsimplematch(tokenizer.tokens(), "get_endpoint_url ( ) const noexcept override final ;") : nullptr;
|
f = db ? Token::findsimplematch(tokenizer.tokens(), "get_endpoint_url ( ) const noexcept override final ;") : nullptr;
|
||||||
ASSERT(f != nullptr);
|
ASSERT(f != nullptr);
|
||||||
ASSERT(f && f->function() && f->function()->token->linenr() == 5);
|
ASSERT(f && f->function() && f->function()->token->linenr() == 5);
|
||||||
ASSERT(f && f->function() && f->function()->isVirtual());
|
ASSERT(f && f->function() && f->function()->hasVirtualSpecifier());
|
||||||
ASSERT(f && f->function() && f->function()->hasOverrideSpecifier());
|
ASSERT(f && f->function() && f->function()->hasOverrideSpecifier());
|
||||||
ASSERT(f && f->function() && f->function()->hasFinalSpecifier());
|
ASSERT(f && f->function() && f->function()->hasFinalSpecifier());
|
||||||
ASSERT(f && f->function() && f->function()->isConst());
|
ASSERT(f && f->function() && f->function()->isConst());
|
||||||
|
|
Loading…
Reference in New Issue