/lib/checkclass.cpp
C++ | 2176 lines | 1606 code | 284 blank | 286 comment | 895 complexity | 13d8f48107fe58632eb3097737fd9d82 MD5 | raw file
Possible License(s): GPL-3.0
Large files files are truncated, but you can click here to view the full file
- /*
- * Cppcheck - A tool for static C/C++ code analysis
- * Copyright (C) 2007-2014 Daniel Marjam채ki and Cppcheck team.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- //---------------------------------------------------------------------------
- #include "checkclass.h"
- #include "tokenize.h"
- #include "token.h"
- #include "errorlogger.h"
- #include "symboldatabase.h"
- #include <string>
- #include <algorithm>
- #include <cctype>
- //---------------------------------------------------------------------------
- // Register CheckClass..
- namespace {
- CheckClass instance;
- const char * getFunctionTypeName(
- Function::Type type)
- {
- switch (type) {
- case Function::eConstructor:
- return "constructor";
- case Function::eCopyConstructor:
- return "copy constructor";
- case Function::eMoveConstructor:
- return "move constructor";
- case Function::eDestructor:
- return "destructor";
- case Function::eFunction:
- return "function";
- case Function::eOperatorEqual:
- return "operator=";
- }
- return "";
- }
- inline bool isPureWithoutBody(Function const & func)
- {
- return func.isPure && !func.hasBody;
- }
- }
- //---------------------------------------------------------------------------
- CheckClass::CheckClass(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger)
- : Check(myName(), tokenizer, settings, errorLogger),
- symbolDatabase(tokenizer?tokenizer->getSymbolDatabase():nullptr)
- {
- }
- //---------------------------------------------------------------------------
- // ClassCheck: Check that all class constructors are ok.
- //---------------------------------------------------------------------------
- void CheckClass::constructors()
- {
- const bool style = _settings->isEnabled("style");
- const bool warnings = _settings->isEnabled("warning");
- if (!style && !warnings)
- return;
- const std::size_t classes = symbolDatabase->classAndStructScopes.size();
- for (std::size_t i = 0; i < classes; ++i) {
- const Scope * scope = symbolDatabase->classAndStructScopes[i];
- // There are no constructors.
- if (scope->numConstructors == 0 && style) {
- // If there is a private variable, there should be a constructor..
- std::list<Variable>::const_iterator var;
- for (var = scope->varlist.begin(); var != scope->varlist.end(); ++var) {
- if (var->isPrivate() && !var->isStatic() && !Token::Match(var->nameToken(), "%varid% ; %varid% =", var->declarationId()) &&
- (!var->isClass() || (var->type() && var->type()->needInitialization == Type::True))) {
- noConstructorError(scope->classDef, scope->className, scope->classDef->str() == "struct");
- break;
- }
- }
- }
- if (!warnings)
- continue;
- // #3196 => bailout if there are nested unions
- // TODO: handle union variables better
- {
- bool bailout = false;
- for (std::list<Scope *>::const_iterator it = scope->nestedList.begin(); it != scope->nestedList.end(); ++it) {
- const Scope * const nestedScope = *it;
- if (nestedScope->type == Scope::eUnion) {
- bailout = true;
- break;
- }
- }
- if (bailout)
- continue;
- }
- std::list<Function>::const_iterator func;
- std::vector<Usage> usage(scope->varlist.size());
- for (func = scope->functionList.begin(); func != scope->functionList.end(); ++func) {
- if (!func->hasBody || !(func->isConstructor() ||
- func->type == Function::eOperatorEqual))
- continue;
- // Mark all variables not used
- clearAllVar(usage);
- std::list<const Function *> callstack;
- initializeVarList(*func, callstack, &(*scope), usage);
- // Check if any variables are uninitialized
- std::list<Variable>::const_iterator var;
- unsigned int count = 0;
- for (var = scope->varlist.begin(); var != scope->varlist.end(); ++var, ++count) {
- // check for C++11 initializer
- if (var->hasDefault()) {
- usage[count].init = true;
- continue;
- }
- if (usage[count].assign || usage[count].init || var->isStatic())
- continue;
- if (var->isConst() && func->isOperator) // We can't set const members in assignment operator
- continue;
- // Check if this is a class constructor
- if (!var->isPointer() && var->isClass() && func->type == Function::eConstructor) {
- // Unknown type so assume it is initialized
- if (!var->type())
- continue;
- // Known type that doesn't need initialization or
- // known type that has member variables of an unknown type
- else if (var->type()->needInitialization != Type::True)
- continue;
- }
- // Check if type can't be copied
- if (!var->isPointer() && var->typeScope()) {
- if (func->type == Function::eMoveConstructor) {
- if (canNotMove(var->typeScope()))
- continue;
- } else {
- if (canNotCopy(var->typeScope()))
- continue;
- }
- }
- bool inconclusive = false;
- // Don't warn about unknown types in copy constructors since we
- // don't know if they can be copied or not..
- if (!var->isPointer() &&
- !(var->type() && var->type()->needInitialization != Type::True) &&
- (func->type == Function::eCopyConstructor || func->type == Function::eOperatorEqual)) {
- if (!var->typeStartToken()->isStandardType()) {
- if (_settings->inconclusive)
- inconclusive = true;
- else
- continue;
- }
- }
- // It's non-static and it's not initialized => error
- if (func->type == Function::eOperatorEqual) {
- const Token *operStart = func->arg;
- bool classNameUsed = false;
- for (const Token *operTok = operStart; operTok != operStart->link(); operTok = operTok->next()) {
- if (operTok->str() == scope->className) {
- classNameUsed = true;
- break;
- }
- }
- if (classNameUsed)
- operatorEqVarError(func->token, scope->className, var->name(), inconclusive);
- } else if (func->access != Private) {
- const Scope *varType = var->typeScope();
- if (!varType || varType->type != Scope::eUnion) {
- if (func->type == Function::eConstructor &&
- func->nestedIn && (func->nestedIn->numConstructors - func->nestedIn->numCopyOrMoveConstructors) > 1 &&
- func->argCount() == 0 && func->functionScope &&
- func->arg && func->arg->link()->next() == func->functionScope->classStart &&
- func->functionScope->classStart->link() == func->functionScope->classStart->next()) {
- // don't warn about user defined default constructor when there are other constructors
- if (_settings->inconclusive)
- uninitVarError(func->token, scope->className, var->name(), true);
- } else
- uninitVarError(func->token, scope->className, var->name(), inconclusive);
- }
- }
- }
- }
- }
- }
- void CheckClass::copyconstructors()
- {
- if (!_settings->isEnabled("style"))
- return;
- const std::size_t classes = symbolDatabase->classAndStructScopes.size();
- for (std::size_t i = 0; i < classes; ++i) {
- const Scope * scope = symbolDatabase->classAndStructScopes[i];
- std::map<unsigned int, const Token*> allocatedVars;
- for (std::list<Function>::const_iterator func = scope->functionList.begin(); func != scope->functionList.end(); ++func) {
- if (func->type == Function::eConstructor && func->functionScope) {
- const Token* tok = func->functionScope->classDef->linkAt(1);
- for (const Token* const end = func->functionScope->classStart; tok != end; tok = tok->next()) {
- if (Token::Match(tok, "%var% ( new|malloc|g_malloc|g_try_malloc|realloc|g_realloc|g_try_realloc")) {
- const Variable* var = tok->variable();
- if (var && var->isPointer() && var->scope() == &*scope)
- allocatedVars[tok->varId()] = tok;
- }
- }
- for (const Token* const end = func->functionScope->classEnd; tok != end; tok = tok->next()) {
- if (Token::Match(tok, "%var% = new|malloc|g_malloc|g_try_malloc|realloc|g_realloc|g_try_realloc")) {
- const Variable* var = tok->variable();
- if (var && var->isPointer() && var->scope() == &*scope)
- allocatedVars[tok->varId()] = tok;
- }
- }
- }
- }
- std::set<const Token*> copiedVars;
- const Token* copyCtor = 0;
- for (std::list<Function>::const_iterator func = scope->functionList.begin(); func != scope->functionList.end(); ++func) {
- if (func->type == Function::eCopyConstructor) {
- copyCtor = func->tokenDef;
- if (func->functionScope) {
- const Token* tok = func->tokenDef->linkAt(1)->next();
- if (tok->str()==":") {
- tok=tok->next();
- while (Token::Match(tok, "%var% (")) {
- if (allocatedVars.find(tok->varId()) != allocatedVars.end()) {
- if (tok->varId() && Token::Match(tok->tokAt(2), "%var% . %var% )"))
- copiedVars.insert(tok);
- else if (!Token::Match(tok->tokAt(2), "%any% )"))
- allocatedVars.erase(tok->varId()); // Assume memory is allocated
- }
- tok = tok->linkAt(1)->tokAt(2);
- }
- }
- for (tok=func->functionScope->classStart; tok!=func->functionScope->classEnd; tok=tok->next()) {
- if (Token::Match(tok, "%var% = new|malloc|g_malloc|g_try_malloc|realloc|g_realloc|g_try_realloc")) {
- allocatedVars.erase(tok->varId());
- } else if (Token::Match(tok, "%var% = %var% . %var% ;") && allocatedVars.find(tok->varId()) != allocatedVars.end()) {
- copiedVars.insert(tok);
- }
- }
- } else // non-copyable or implementation not seen
- allocatedVars.clear();
- break;
- }
- }
- if (!copyCtor) {
- if (!allocatedVars.empty() && scope->definedType->derivedFrom.empty()) // TODO: Check if base class is non-copyable
- noCopyConstructorError(scope->classDef, scope->className, scope->type == Scope::eStruct);
- } else {
- if (!copiedVars.empty()) {
- for (std::set<const Token*>::const_iterator it = copiedVars.begin(); it != copiedVars.end(); ++it) {
- copyConstructorShallowCopyError(*it, (*it)->str());
- }
- }
- // throw error if count mismatch
- /* FIXME: This doesn't work. See #4154
- for (std::map<unsigned int, const Token*>::const_iterator i = allocatedVars.begin(); i != allocatedVars.end(); ++i) {
- copyConstructorMallocError(copyCtor, i->second, i->second->str());
- }
- */
- }
- }
- }
- /* This doesn't work. See #4154
- void CheckClass::copyConstructorMallocError(const Token *cctor, const Token *alloc, const std::string& varname)
- {
- std::list<const Token*> callstack;
- callstack.push_back(cctor);
- callstack.push_back(alloc);
- reportError(callstack, Severity::warning, "copyCtorNoAllocation", "Copy constructor does not allocate memory for member '" + varname + "' although memory has been allocated in other constructors.");
- }
- */
- void CheckClass::copyConstructorShallowCopyError(const Token *tok, const std::string& varname)
- {
- reportError(tok, Severity::style, "copyCtorPointerCopying", "Value of pointer '" + varname + "', which points to allocated memory, is copied in copy constructor instead of allocating new memory.");
- }
- void CheckClass::noCopyConstructorError(const Token *tok, const std::string &classname, bool isStruct)
- {
- // The constructor might be intentionally missing. Therefore this is not a "warning"
- reportError(tok, Severity::style, "noCopyConstructor",
- "'" + std::string(isStruct ? "struct" : "class") + " " + classname +
- "' does not have a copy constructor which is recommended since the class contains a pointer to allocated memory.");
- }
- bool CheckClass::canNotCopy(const Scope *scope)
- {
- std::list<Function>::const_iterator func;
- bool constructor = false;
- bool publicAssign = false;
- bool publicCopy = false;
- for (func = scope->functionList.begin(); func != scope->functionList.end(); ++func) {
- if (func->isConstructor())
- constructor = true;
- if ((func->type == Function::eCopyConstructor) &&
- func->access == Public)
- publicCopy = true;
- else if (func->type == Function::eOperatorEqual && func->access == Public)
- publicAssign = true;
- }
- return constructor && !(publicAssign || publicCopy);
- }
- bool CheckClass::canNotMove(const Scope *scope)
- {
- std::list<Function>::const_iterator func;
- bool constructor = false;
- bool publicAssign = false;
- bool publicCopy = false;
- bool publicMove = false;
- for (func = scope->functionList.begin(); func != scope->functionList.end(); ++func) {
- if (func->isConstructor())
- constructor = true;
- if ((func->type == Function::eCopyConstructor) &&
- func->access == Public)
- publicCopy = true;
- else if ((func->type == Function::eMoveConstructor) &&
- func->access == Public)
- publicMove = true;
- else if (func->type == Function::eOperatorEqual && func->access == Public)
- publicAssign = true;
- }
- return constructor && !(publicAssign || publicCopy || publicMove);
- }
- void CheckClass::assignVar(const std::string &varname, const Scope *scope, std::vector<Usage> &usage)
- {
- std::list<Variable>::const_iterator var;
- unsigned int count = 0;
- for (var = scope->varlist.begin(); var != scope->varlist.end(); ++var, ++count) {
- if (var->name() == varname) {
- usage[count].assign = true;
- return;
- }
- }
- }
- void CheckClass::initVar(const std::string &varname, const Scope *scope, std::vector<Usage> &usage)
- {
- std::list<Variable>::const_iterator var;
- unsigned int count = 0;
- for (var = scope->varlist.begin(); var != scope->varlist.end(); ++var, ++count) {
- if (var->name() == varname) {
- usage[count].init = true;
- return;
- }
- }
- }
- void CheckClass::assignAllVar(std::vector<Usage> &usage)
- {
- for (std::size_t i = 0; i < usage.size(); ++i)
- usage[i].assign = true;
- }
- void CheckClass::clearAllVar(std::vector<Usage> &usage)
- {
- for (std::size_t i = 0; i < usage.size(); ++i) {
- usage[i].assign = false;
- usage[i].init = false;
- }
- }
- bool CheckClass::isBaseClassFunc(const Token *tok, const Scope *scope)
- {
- // Iterate through each base class...
- for (std::size_t i = 0; i < scope->definedType->derivedFrom.size(); ++i) {
- const Type *derivedFrom = scope->definedType->derivedFrom[i].type;
- // Check if base class exists in database
- if (derivedFrom && derivedFrom->classScope) {
- const std::list<Function>& functionList = derivedFrom->classScope->functionList;
- std::list<Function>::const_iterator func;
- for (func = functionList.begin(); func != functionList.end(); ++func) {
- if (func->tokenDef->str() == tok->str())
- return true;
- }
- }
- // Base class not found so assume it is in it.
- else
- return true;
- }
- return false;
- }
- void CheckClass::initializeVarList(const Function &func, std::list<const Function *> &callstack, const Scope *scope, std::vector<Usage> &usage)
- {
- if (!func.functionScope)
- throw InternalError(0, "Internal Error: Invalid syntax"); // #5702
- bool initList = func.isConstructor();
- const Token *ftok = func.arg->link()->next();
- int level = 0;
- for (; ftok && ftok != func.functionScope->classEnd; ftok = ftok->next()) {
- // Class constructor.. initializing variables like this
- // clKalle::clKalle() : var(value) { }
- if (initList) {
- if (level == 0 && Token::Match(ftok, "%var% {|(")) {
- if (ftok->str() != func.name()) {
- initVar(ftok->str(), scope, usage);
- } else { // c++11 delegate constructor
- const Function *member = ftok->function();
- // member function found
- if (member) {
- // recursive call
- // assume that all variables are initialized
- if (std::find(callstack.begin(), callstack.end(), member) != callstack.end()) {
- /** @todo false negative: just bail */
- assignAllVar(usage);
- return;
- }
- // member function has implementation
- if (member->hasBody) {
- // initialize variable use list using member function
- callstack.push_back(member);
- initializeVarList(*member, callstack, scope, usage);
- callstack.pop_back();
- }
- // there is a called member function, but it has no implementation, so we assume it initializes everything
- else {
- assignAllVar(usage);
- }
- }
- }
- ftok = ftok->next();
- level++;
- } else if (level != 0 && Token::Match(ftok, "%var% =")) // assignment in the initializer: var(value = x)
- assignVar(ftok->str(), scope, usage);
- else if (ftok->str() == "(")
- level++;
- else if (ftok->str() == ")" || ftok->str() == "}")
- level--;
- else if (ftok->str() == "{") {
- if (level == 0)
- initList = false;
- else
- ftok = ftok->link();
- }
- }
- if (initList)
- continue;
- // Variable getting value from stream?
- if (Token::Match(ftok, ">> %var%")) {
- assignVar(ftok->strAt(1), scope, usage);
- }
- // Before a new statement there is "[{};()=[]"
- if (! Token::Match(ftok, "[{};()=[]"))
- continue;
- if (Token::simpleMatch(ftok, "( !"))
- ftok = ftok->next();
- // Using the operator= function to initialize all variables..
- if (Token::Match(ftok->next(), "return| (| * this )| =")) {
- assignAllVar(usage);
- break;
- }
- // Using swap to assign all variables..
- if (func.type == Function::eOperatorEqual && Token::Match(ftok, "[;{}] %var% (") && Token::Match(ftok->linkAt(2), ") . %var% ( *| this ) ;")) {
- assignAllVar(usage);
- break;
- }
- // Calling member variable function?
- if (Token::Match(ftok->next(), "%var% . %var% (")) {
- std::list<Variable>::const_iterator var;
- for (var = scope->varlist.begin(); var != scope->varlist.end(); ++var) {
- if (var->declarationId() == ftok->next()->varId()) {
- /** @todo false negative: we assume function changes variable state */
- assignVar(ftok->next()->str(), scope, usage);
- break;
- }
- }
- ftok = ftok->tokAt(2);
- }
- if (!Token::Match(ftok->next(), "::| %var%") &&
- !Token::Match(ftok->next(), "*| this . %var%") &&
- !Token::Match(ftok->next(), "* %var% =") &&
- !Token::Match(ftok->next(), "( * this ) . %var%"))
- continue;
- // Goto the first token in this statement..
- ftok = ftok->next();
- // skip "return"
- if (ftok->str() == "return")
- ftok = ftok->next();
- // Skip "( * this )"
- if (Token::simpleMatch(ftok, "( * this ) .")) {
- ftok = ftok->tokAt(5);
- }
- // Skip "this->"
- if (Token::simpleMatch(ftok, "this ."))
- ftok = ftok->tokAt(2);
- // Skip "classname :: "
- if (Token::Match(ftok, ":: %var%"))
- ftok = ftok->next();
- while (Token::Match(ftok, "%var% ::"))
- ftok = ftok->tokAt(2);
- // Clearing all variables..
- if (Token::Match(ftok, "::| memset ( this ,")) {
- assignAllVar(usage);
- return;
- }
- // Clearing array..
- else if (Token::Match(ftok, "::| memset ( %var% ,")) {
- if (ftok->str() == "::")
- ftok = ftok->next();
- assignVar(ftok->strAt(2), scope, usage);
- ftok = ftok->linkAt(1);
- continue;
- }
- // Calling member function?
- else if (Token::simpleMatch(ftok, "operator= (") &&
- ftok->previous()->str() != "::") {
- if (ftok->function() && ftok->function()->nestedIn == scope) {
- const Function *member = ftok->function();
- // recursive call
- // assume that all variables are initialized
- if (std::find(callstack.begin(), callstack.end(), member) != callstack.end()) {
- /** @todo false negative: just bail */
- assignAllVar(usage);
- return;
- }
- // member function has implementation
- if (member->hasBody) {
- // initialize variable use list using member function
- callstack.push_back(member);
- initializeVarList(*member, callstack, scope, usage);
- callstack.pop_back();
- }
- // there is a called member function, but it has no implementation, so we assume it initializes everything
- else {
- assignAllVar(usage);
- }
- }
- // using default operator =, assume everything initialized
- else {
- assignAllVar(usage);
- }
- } else if (Token::Match(ftok, "::| %var% (") && ftok->str() != "if") {
- if (ftok->str() == "::")
- ftok = ftok->next();
- // Passing "this" => assume that everything is initialized
- for (const Token *tok2 = ftok->next()->link(); tok2 && tok2 != ftok; tok2 = tok2->previous()) {
- if (tok2->str() == "this") {
- assignAllVar(usage);
- return;
- }
- }
- // check if member function
- if (ftok->function() && ftok->function()->nestedIn == scope &&
- !ftok->function()->isConstructor()) {
- const Function *member = ftok->function();
- // recursive call
- // assume that all variables are initialized
- if (std::find(callstack.begin(), callstack.end(), member) != callstack.end()) {
- assignAllVar(usage);
- return;
- }
- // member function has implementation
- if (member->hasBody) {
- // initialize variable use list using member function
- callstack.push_back(member);
- initializeVarList(*member, callstack, scope, usage);
- callstack.pop_back();
- // Assume that variables that are passed to it are initialized..
- for (const Token *tok2 = ftok; tok2; tok2 = tok2->next()) {
- if (Token::Match(tok2, "[;{}]"))
- break;
- if (Token::Match(tok2, "[(,] &| %var% [,)]")) {
- tok2 = tok2->next();
- if (tok2->str() == "&")
- tok2 = tok2->next();
- assignVar(tok2->str(), scope, usage);
- }
- }
- }
- // there is a called member function, but it has no implementation, so we assume it initializes everything
- else {
- assignAllVar(usage);
- }
- }
- // not member function
- else {
- // could be a base class virtual function, so we assume it initializes everything
- if (!func.isConstructor() && isBaseClassFunc(ftok, scope)) {
- /** @todo False Negative: we should look at the base class functions to see if they
- * call any derived class virtual functions that change the derived class state
- */
- assignAllVar(usage);
- }
- // has friends, so we assume it initializes everything
- if (!scope->definedType->friendList.empty())
- assignAllVar(usage);
- // the function is external and it's neither friend nor inherited virtual function.
- // assume all variables that are passed to it are initialized..
- else {
- for (const Token *tok = ftok->tokAt(2); tok && tok != ftok->next()->link(); tok = tok->next()) {
- if (tok->isName()) {
- assignVar(tok->str(), scope, usage);
- }
- }
- }
- }
- }
- // Assignment of member variable?
- else if (Token::Match(ftok, "%var% =")) {
- assignVar(ftok->str(), scope, usage);
- }
- // Assignment of array item of member variable?
- else if (Token::Match(ftok, "%var% [|.")) {
- const Token *tok2 = ftok;
- while (tok2) {
- if (tok2->strAt(1) == "[")
- tok2 = tok2->next()->link();
- else if (Token::Match(tok2->next(), ". %var%"))
- tok2 = tok2->tokAt(2);
- else
- break;
- }
- if (tok2 && tok2->strAt(1) == "=")
- assignVar(ftok->str(), scope, usage);
- }
- // Assignment of array item of member variable?
- else if (Token::Match(ftok, "* %var% =")) {
- assignVar(ftok->next()->str(), scope, usage);
- } else if (Token::Match(ftok, "* this . %var% =")) {
- assignVar(ftok->strAt(3), scope, usage);
- }
- // The functions 'clear' and 'Clear' are supposed to initialize variable.
- if (Token::Match(ftok, "%var% . clear|Clear (")) {
- assignVar(ftok->str(), scope, usage);
- }
- }
- }
- void CheckClass::noConstructorError(const Token *tok, const std::string &classname, bool isStruct)
- {
- // For performance reasons the constructor might be intentionally missing. Therefore this is not a "warning"
- reportError(tok, Severity::style, "noConstructor",
- "The " + std::string(isStruct ? "struct" : "class") + " '" + classname +
- "' does not have a constructor.\n"
- "The " + std::string(isStruct ? "struct" : "class") + " '" + classname +
- "' does not have a constructor although it has private member variables. "
- "Member variables of builtin types are left uninitialized when the class is "
- "instantiated. That may cause bugs or undefined behavior.");
- }
- void CheckClass::uninitVarError(const Token *tok, const std::string &classname, const std::string &varname, bool inconclusive)
- {
- reportError(tok, Severity::warning, "uninitMemberVar", "Member variable '" + classname + "::" + varname + "' is not initialized in the constructor.", inconclusive);
- }
- void CheckClass::operatorEqVarError(const Token *tok, const std::string &classname, const std::string &varname, bool inconclusive)
- {
- reportError(tok, Severity::warning, "operatorEqVarError", "Member variable '" + classname + "::" + varname + "' is not assigned a value in '" + classname + "::operator='.", inconclusive);
- }
- //---------------------------------------------------------------------------
- // ClassCheck: Use initialization list instead of assignment
- //---------------------------------------------------------------------------
- void CheckClass::initializationListUsage()
- {
- if (!_settings->isEnabled("performance"))
- return;
- const std::size_t functions = symbolDatabase->functionScopes.size();
- for (std::size_t i = 0; i < functions; ++i) {
- const Scope * scope = symbolDatabase->functionScopes[i];
- // Check every constructor
- if (!scope->function || (!scope->function->isConstructor()))
- continue;
- const Scope* owner = scope->functionOf;
- for (const Token* tok = scope->classStart; tok != scope->classEnd; tok = tok->next()) {
- if (Token::Match(tok, "%var% (")) // Assignments might depend on this function call or if/for/while/switch statement from now on.
- break;
- if (Token::Match(tok, "try|do {"))
- break;
- if (tok->varId() && Token::Match(tok, "%var% = %any%")) {
- const Variable* var = tok->variable();
- if (var && var->scope() == owner && !var->isStatic()) {
- bool allowed = true;
- for (const Token* tok2 = tok->tokAt(2); tok2->str() != ";"; tok2 = tok2->next()) {
- if (tok2->varId()) {
- const Variable* var2 = tok2->variable();
- if (var2 && var2->scope() == owner &&
- tok2->strAt(-1)!=".") { // Is there a dependency between two member variables?
- allowed = false;
- break;
- } else if (var2 && (var2->isArray() && var2->isLocal())) { // Can't initialize with a local array
- allowed = false;
- break;
- }
- } else if (tok2->str() == "this") { // 'this' instance is not completely constructed in initialization list
- allowed = false;
- break;
- } else if (Token::Match(tok2, "%var% (") && tok2->strAt(-1) != "." && isMemberFunc(owner, tok2)) { // Member function called?
- allowed = false;
- break;
- }
- }
- if (!allowed)
- continue;
- if (!var->isPointer() && !var->isReference() && (var->type() || var->isStlStringType() || (Token::Match(var->typeStartToken(), "std :: %type% <") && !Token::simpleMatch(var->typeStartToken()->linkAt(3), "> ::"))))
- suggestInitializationList(tok, tok->str());
- }
- }
- }
- }
- }
- void CheckClass::suggestInitializationList(const Token* tok, const std::string& varname)
- {
- reportError(tok, Severity::performance, "useInitializationList", "Variable '" + varname + "' is assigned in constructor body. Consider performing initialization in initialization list.\n"
- "When an object of a class is created, the constructors of all member variables are called consecutively "
- "in the order the variables are declared, even if you don't explicitly write them to the initialization list. You "
- "could avoid assigning '" + varname + "' a value by passing the value to the constructor in the initialization list.");
- }
- //---------------------------------------------------------------------------
- // ClassCheck: Unused private functions
- //---------------------------------------------------------------------------
- static bool checkFunctionUsage(const std::string& name, const Scope* scope)
- {
- if (!scope)
- return true; // Assume it is used, if scope is not seen
- for (std::list<Function>::const_iterator func = scope->functionList.begin(); func != scope->functionList.end(); ++func) {
- if (func->functionScope) {
- if (Token::Match(func->tokenDef, "%var% (")) {
- for (const Token *ftok = func->tokenDef->tokAt(2); ftok && ftok->str() != ")"; ftok = ftok->next()) {
- if (Token::Match(ftok, "= %var% [(,)]") && ftok->strAt(1) == name)
- return true;
- if (ftok->str() == "(")
- ftok = ftok->link();
- }
- }
- for (const Token *ftok = func->functionScope->classDef->linkAt(1); ftok != func->functionScope->classEnd; ftok = ftok->next()) {
- if (ftok->str() == name) // Function used. TODO: Handle overloads
- return true;
- }
- } else if ((func->type != Function::eCopyConstructor &&
- func->type != Function::eOperatorEqual) ||
- func->access != Private) // Assume it is used, if a function implementation isn't seen, but empty private copy constructors and assignment operators are OK
- return true;
- }
- for (std::list<Scope*>::const_iterator i = scope->nestedList.begin(); i != scope->nestedList.end(); ++i) {
- if ((*i)->isClassOrStruct())
- if (checkFunctionUsage(name, *i)) // Check nested classes, which can access private functions of their base
- return true;
- }
- for (std::list<Variable>::const_iterator i = scope->varlist.begin(); i != scope->varlist.end(); ++i) {
- if (i->isStatic()) {
- const Token* tok = Token::findmatch(scope->classEnd, "%varid% =|(|{", i->declarationId());
- if (tok)
- tok = tok->tokAt(2);
- while (tok && tok->str() != ";") {
- if (tok->str() == name && (tok->strAt(-1) == "." || tok->strAt(-2) == scope->className))
- return true;
- tok = tok->next();
- }
- }
- }
- return false; // Unused in this scope
- }
- void CheckClass::privateFunctions()
- {
- if (!_settings->isEnabled("style"))
- return;
- const std::size_t classes = symbolDatabase->classAndStructScopes.size();
- for (std::size_t i = 0; i < classes; ++i) {
- const Scope * scope = symbolDatabase->classAndStructScopes[i];
- // do not check borland classes with properties..
- if (Token::findsimplematch(scope->classStart, "; __property ;", scope->classEnd))
- continue;
- std::list<const Function*> privateFuncs;
- for (std::list<Function>::const_iterator func = scope->functionList.begin(); func != scope->functionList.end(); ++func) {
- // Get private functions..
- if (func->type == Function::eFunction && func->access == Private && !func->isOperator) // TODO: There are smarter ways to check private operator usage
- privateFuncs.push_back(&*func);
- }
- // Bailout for overridden virtual functions of base classes
- if (!scope->definedType->derivedFrom.empty()) {
- // Check virtual functions
- for (std::list<const Function*>::iterator it = privateFuncs.begin(); it != privateFuncs.end();) {
- if ((*it)->isImplicitlyVirtual(true)) // Give true as default value to be returned if we don't see all base classes
- privateFuncs.erase(it++);
- else
- ++it;
- }
- }
- while (!privateFuncs.empty()) {
- const std::string& funcName = privateFuncs.front()->tokenDef->str();
- // Check that all private functions are used
- bool used = checkFunctionUsage(funcName, &*scope); // Usage in this class
- // Check in friend classes
- const std::list<Type::FriendInfo>& friendList = scope->definedType->friendList;
- for (std::list<Type::FriendInfo>::const_iterator it = friendList.begin(); !used && it != friendList.end(); ++it) {
- if (it->type)
- used = checkFunctionUsage(funcName, it->type->classScope);
- else
- used = true; // Assume, it is used if we do not see friend class
- }
- if (!used)
- unusedPrivateFunctionError(privateFuncs.front()->tokenDef, scope->className, funcName);
- privateFuncs.pop_front();
- }
- }
- }
- void CheckClass::unusedPrivateFunctionError(const Token *tok, const std::string &classname, const std::string &funcname)
- {
- reportError(tok, Severity::style, "unusedPrivateFunction", "Unused private function: '" + classname + "::" + funcname + "'");
- }
- //---------------------------------------------------------------------------
- // ClassCheck: Check that memset is not used on classes
- //---------------------------------------------------------------------------
- static const Scope* findFunctionOf(const Scope* scope)
- {
- while (scope) {
- if (scope->type == Scope::eFunction)
- return scope->functionOf;
- scope = scope->nestedIn;
- }
- return 0;
- }
- void CheckClass::checkMemset()
- {
- const std::size_t functions = symbolDatabase->functionScopes.size();
- for (std::size_t i = 0; i < functions; ++i) {
- const Scope * scope = symbolDatabase->functionScopes[i];
- for (const Token *tok = scope->classStart; tok && tok != scope->classEnd; tok = tok->next()) {
- if (Token::Match(tok, "memset|memcpy|memmove ( %any%")) {
- const Token* arg1 = tok->tokAt(2);
- const Token* arg3 = arg1->nextArgument();
- if (arg3)
- arg3 = arg3->nextArgument();
- if (!arg3)
- // weird, shouldn't happen: memset etc should have
- // 3 arguments.
- continue;
- const Token *typeTok = nullptr;
- const Scope *type = nullptr;
- if (Token::Match(arg3, "sizeof ( %type% ) )"))
- typeTok = arg3->tokAt(2);
- else if (Token::Match(arg3, "sizeof ( %type% :: %type% ) )"))
- typeTok = arg3->tokAt(4);
- else if (Token::Match(arg3, "sizeof ( struct %type% ) )"))
- typeTok = arg3->tokAt(3);
- else if (Token::simpleMatch(arg3, "sizeof ( * this ) )") || Token::simpleMatch(arg1, "this ,")) {
- type = findFunctionOf(arg3->scope());
- } else if (Token::Match(arg1, "&|*|%var%")) {
- int derefs = 1;
- for (;; arg1 = arg1->next()) {
- if (arg1->str() == "&")
- derefs--;
- else if (arg1->str() == "*")
- derefs++;
- else
- break;
- }
- const Variable *var = arg1->variable();
- if (var && arg1->strAt(1) == ",") {
- if (var->isPointer()) {
- derefs--;
- if (var->typeEndToken() && Token::simpleMatch(var->typeEndToken()->previous(), "* *")) // Check if it's a pointer to pointer
- derefs--;
- }
- if (var->isArray())
- derefs -= (int)var->dimensions().size();
- if (derefs == 0)
- type = var->typeScope();
- }
- }
- // No type defined => The tokens didn't match
- if (!typeTok && !type)
- continue;
- if (typeTok && typeTok->str() == "(")
- typeTok = typeTok->next();
- if (!type) {
- const Type* t = symbolDatabase->findVariableType(&(*scope), typeTok);
- if (t)
- type = t->classScope;
- }
- if (type) {
- std::list<const Scope *> parsedTypes;
- checkMemsetType(&(*scope), tok, type, false, parsedTypes);
- }
- } else if (tok->variable() && tok->variable()->typeScope() && Token::Match(tok, "%var% = calloc|malloc|realloc|g_malloc|g_try_malloc|g_realloc|g_try_realloc (")) {
- std::list<const Scope *> parsedTypes;
- checkMemsetType(&(*scope), tok->tokAt(2), tok->variable()->typeScope(), true, parsedTypes);
- if (tok->variable()->typeScope()->numConstructors > 0 && _settings->isEnabled("warning"))
- mallocOnClassWarning(tok, tok->strAt(2), tok->variable()->typeScope()->classDef);
- }
- }
- }
- }
- void CheckClass::checkMemsetType(const Scope *start, const Token *tok, const Scope *type, bool allocation, std::list<const Scope *> parsedTypes)
- {
- // If type has been checked there is no need to check it again
- if (std::find(parsedTypes.begin(), parsedTypes.end(), type) != parsedTypes.end())
- return;
- parsedTypes.push_back(type);
- // recursively check all parent classes
- for (std::size_t i = 0; i < type->definedType->derivedFrom.size(); i++) {
- const Type* derivedFrom = type->definedType->derivedFrom[i].type;
- if (derivedFrom && derivedFrom->classScope)
- checkMemsetType(start, tok, derivedFrom->classScope, allocation, parsedTypes);
- }
- // Warn if type is a class that contains any virtual functions
- std::list<Function>::const_iterator func;
- for (func = type->functionList.begin(); func != type->functionList.end(); ++func) {
- if (func->isVirtual) {
- if (allocation)
- mallocOnClassError(tok, tok->str(), type->classDef, "virtual method");
- else
- memsetError(tok, tok->str(), "virtual method", type->classDef->str());
- }
- }
- // Warn if type is a class or struct that contains any std::* variables
- std::list<Variable>::const_iterator var;
- for (var = type->varlist.begin(); var != type->varlist.end(); ++var) {
- if (var->isReference() && !var->isStatic()) {
- memsetErrorReference(tok, tok->str(), type->classDef->str());
- continue;
- }
- // don't warn if variable static or const, pointer or reference
- if (!var->isStatic() && !var->isConst() && !var->isPointer()) {
- const Token *tok1 = var->typeStartToken();
- const Scope *typeScope = var->typeScope();
- // check for std:: type
- if (var->isStlType() && tok1->strAt(2) != "array" && !_settings->library.podtype(tok1->strAt(2)))
- if (allocation)
- mallocOnClassError(tok, tok->str(), type->classDef, "'std::" + tok1->strAt(2) + "'");
- else
- memsetError(tok, tok->str(), "'std::" + tok1->strAt(2) + "'", type->classDef->str());
- // check for known type
- else if (typeScope && typeScope != type)
- checkMemsetType(start, tok, typeScope, allocation, parsedTypes);
- // check for float
- else if (tok->str() == "memset" && var->isFloatingType() && _settings->isEnabled("portability"))
- memsetErrorFloat(tok, type->classDef->str());
- }
- }
- }
- void CheckClass::mallocOnClassWarning(const Token* tok, const std::string &memfunc, const Token* classTok)
- {
- std::list<const Token *> toks;
- toks.push_back(tok);
- toks.push_back(classTok);
- reportError(toks, Severity::warning, "mallocOnClassWarning",
- "Memory for class instance allocated with " + memfunc + "(), but class provides constructors.\n"
- "Memory for class instance allocated with " + memfunc + "(), but class provides constructors. This is unsafe, "
- "since no constructor is called and class members remain uninitialized. Consider using 'new' instead.");
- }
- void CheckClass::mallocOnClassError(const Token* tok, const std::string &memfunc, const Token* classTok, const std::string &classname)
- {
- std::list<const Token *> toks;
- toks.push_back(tok);
- toks.push_back(classTok);
- reportError(toks, Severity::error, "mallocOnClassError",
- "Memory for class instance allocated with " + memfunc + "(), but class contains a " + classname + ".\n"
- "Memory for class instance allocated with " + memfunc + "(), but class a " + classname + ". This is unsafe, "
- "since no constructor is called and class members remain uninitialized. Consider using 'new' instead.");
- }
- void CheckClass::memsetError(const Token *tok, const std::string &memfunc, const std::string &classname, const std::string &type)
- {
- reportError(tok, Severity::error, "memsetClass",
- "Using '" + memfunc + "' on " + type + " that contains a " + classname + ".\n"
- "Using '" + memfunc + "' on " + type + " that contains a " + classname + " is unsafe, because constructor, destructor "
- "and copy operator calls are omitted. These are necessary for this non-POD type to ensure that a valid object "
- "is created.");
- }
- void CheckClass::memsetErrorReference(const Token *tok, const std::string &memfunc, const std::string &type)
- {
- reportError(tok, Severity::error, "memsetClassReference", "Using '" + memfunc + "' on " + type + " that contains a reference.");
- }
- void CheckClass::memsetErrorFloat(const Token *tok, const std::string &type)
- {
- reportError(tok, Severity::portability, "memsetClassFloat", "Using memset() on " + type + " which contains a floating point number.\n"
- "Using memset() on " + type + " which contains a floating point number. This is not portable because memset() sets each byte of a block of memory to a specific value and"
- " the actual representation of a floating-point value is implementation defined.");
- }
- //---------------------------------------------------------------------------
- // ClassCheck: "void operator=(" and "const type & operator=("
- //---------------------------------------------------------------------------
- void CheckClass::operatorEq()
- {
- if (!_settings->isEnabled("style"))
- return;
- const std::size_t classes = symbolDatabase->classAndStructScopes.size();
- for (std::size_t i = 0; i < classes; ++i) {
- const Scope * scope = symbolDatabase->classAndStructScopes[i];
- std::list<Function>::const_iterator func;
- for (func = scope->functionList.begin(); func != scope->functionList.end(); ++func) {
- if (func->type == Function::eOperatorEqual && func->access != Private) {
- // skip "deleted" functions - cannot be called anyway
- if (func->isDelete)
- continue;
- // use definition for check so we don't have to deal with qualification
- if (!(Token::Match(func->retDef, "%type% &") && func->retDef->str() == scope->className)) {
- // make sure we really have a copy assignment operator
- if (Token::Match(func->tokenDef->tokAt(2), "const| %var% &")) {
- if (func->tokenDef->strAt(2) == "const" &&
- func->tokenDef->strAt(3) == scope->className)
- operatorEqReturnError(func->retDef, scope->className);
- else if (func->tokenDef->strAt(2) == scope->c…
Large files files are truncated, but you can click here to view the full file