From 2dd93dff75666d2c0a8f663887ab4f2f396eee07 Mon Sep 17 00:00:00 2001 From: Robert Reif Date: Fri, 19 Aug 2011 14:35:25 -0400 Subject: [PATCH] move unused variable checks from checkother to checkunusedvar --- Makefile | 4 + lib/checkother.cpp | 1486 --------------------------------------- lib/checkother.h | 22 - lib/checkunusedvar.cpp | 1516 ++++++++++++++++++++++++++++++++++++++++ lib/checkunusedvar.h | 113 +++ lib/lib.pri | 2 + test/testunusedvar.cpp | 10 +- 7 files changed, 1640 insertions(+), 1513 deletions(-) create mode 100644 lib/checkunusedvar.cpp create mode 100644 lib/checkunusedvar.h diff --git a/Makefile b/Makefile index db8cbd316..3cd871e27 100644 --- a/Makefile +++ b/Makefile @@ -63,6 +63,7 @@ LIBOBJ = lib/check64bit.o \ lib/checkstl.o \ lib/checkuninitvar.o \ lib/checkunusedfunctions.o \ + lib/checkunusedvar.o \ lib/cppcheck.o \ lib/errorlogger.o \ lib/executionpath.o \ @@ -216,6 +217,9 @@ lib/checkuninitvar.o: lib/checkuninitvar.cpp lib/checkuninitvar.h lib/check.h li lib/checkunusedfunctions.o: lib/checkunusedfunctions.cpp lib/checkunusedfunctions.h lib/check.h lib/token.h lib/tokenize.h lib/settings.h lib/errorlogger.h $(CXX) $(CPPFLAGS) $(CXXFLAGS) ${INCLUDE_FOR_LIB} -c -o lib/checkunusedfunctions.o lib/checkunusedfunctions.cpp +lib/checkunusedvar.o: lib/checkunusedvar.cpp lib/checkunusedvar.h lib/check.h lib/token.h lib/tokenize.h lib/settings.h lib/errorlogger.h lib/mathlib.h lib/symboldatabase.h + $(CXX) $(CPPFLAGS) $(CXXFLAGS) ${INCLUDE_FOR_LIB} -c -o lib/checkunusedvar.o lib/checkunusedvar.cpp + lib/cppcheck.o: lib/cppcheck.cpp lib/cppcheck.h lib/settings.h lib/errorlogger.h lib/checkunusedfunctions.h lib/check.h lib/token.h lib/tokenize.h lib/preprocessor.h lib/path.h lib/timer.h $(CXX) $(CPPFLAGS) $(CXXFLAGS) ${INCLUDE_FOR_LIB} -c -o lib/cppcheck.o lib/cppcheck.cpp diff --git a/lib/checkother.cpp b/lib/checkother.cpp index c126ac883..4d29f5b9b 100644 --- a/lib/checkother.cpp +++ b/lib/checkother.cpp @@ -1321,1391 +1321,6 @@ void CheckOther::memsetZeroBytesError(const Token *tok, const std::string &varna reportError(tok, Severity::warning, "memsetZeroBytes", summary + "\n" + verbose); } -//--------------------------------------------------------------------------- -// Usage of function variables -//--------------------------------------------------------------------------- - -/** - * @brief This class is used to capture the control flow within a function. - */ -class ScopeInfo -{ -public: - ScopeInfo() : _token(NULL), _parent(NULL) { } - ScopeInfo(const Token *token, ScopeInfo *parent_) : _token(token), _parent(parent_) { } - ~ScopeInfo(); - - ScopeInfo *parent() - { - return _parent; - } - ScopeInfo *addChild(const Token *token); - void remove(ScopeInfo *scope); - -private: - const Token *_token; - ScopeInfo *_parent; - std::list _children; -}; - -ScopeInfo::~ScopeInfo() -{ - while (!_children.empty()) - { - delete *_children.begin(); - _children.pop_front(); - } -} - -ScopeInfo *ScopeInfo::addChild(const Token *token) -{ - ScopeInfo *temp = new ScopeInfo(token, this); - - _children.push_back(temp); - - return temp; -} - -void ScopeInfo::remove(ScopeInfo *scope) -{ - std::list::iterator it; - - for (it = _children.begin(); it != _children.end(); ++it) - { - if (*it == scope) - { - delete *it; - _children.erase(it); - break; - } - } -} - -/** - * @brief This class is used create a list of variables within a function. - */ -class Variables -{ -public: - enum VariableType { standard, array, pointer, reference, pointerArray, referenceArray, pointerPointer }; - - /** Store information about variable usage */ - class VariableUsage - { - public: - VariableUsage(const Token *name = 0, - VariableType type = standard, - ScopeInfo *scope = NULL, - bool read = false, - bool write = false, - bool modified = false, - bool allocateMemory = false) : - _name(name), - _type(type), - _scope(scope), - _read(read), - _write(write), - _modified(modified), - _allocateMemory(allocateMemory) - { - } - - /** variable is used.. set both read+write */ - void use() - { - _read = true; - _write = true; - } - - /** is variable unused? */ - bool unused() const - { - return (_read == false && _write == false); - } - - const Token *_name; - VariableType _type; - ScopeInfo *_scope; - bool _read; - bool _write; - bool _modified; // read/modify/write - bool _allocateMemory; - std::set _aliases; - std::set _assignments; - }; - - typedef std::map VariableMap; - - void clear() - { - _varUsage.clear(); - } - VariableMap &varUsage() - { - return _varUsage; - } - void addVar(const Token *name, VariableType type, ScopeInfo *scope, bool write_); - void allocateMemory(unsigned int varid); - void read(unsigned int varid); - void readAliases(unsigned int varid); - void readAll(unsigned int varid); - void write(unsigned int varid); - void writeAliases(unsigned int varid); - void writeAll(unsigned int varid); - void use(unsigned int varid); - void modified(unsigned int varid); - VariableUsage *find(unsigned int varid); - void alias(unsigned int varid1, unsigned int varid2, bool replace); - void erase(unsigned int varid) - { - _varUsage.erase(varid); - } - void eraseAliases(unsigned int varid); - void eraseAll(unsigned int varid); - void clearAliases(unsigned int varid); - -private: - VariableMap _varUsage; -}; - -/** - * Alias the 2 given variables. Either replace the existing aliases if - * they exist or merge them. You would replace an existing alias when this - * assignment is in the same scope as the previous assignment. You might - * merge the aliases when this assignment is in a different scope from the - * previous assignment depending on the relationship of the 2 scopes. - */ -void Variables::alias(unsigned int varid1, unsigned int varid2, bool replace) -{ - VariableUsage *var1 = find(varid1); - VariableUsage *var2 = find(varid2); - - // alias to self - if (varid1 == varid2) - { - if (var1) - var1->use(); - return; - } - - std::set::iterator i; - - if (replace) - { - // remove var1 from all aliases - for (i = var1->_aliases.begin(); i != var1->_aliases.end(); ++i) - { - VariableUsage *temp = find(*i); - - if (temp) - temp->_aliases.erase(var1->_name->varId()); - } - - // remove all aliases from var1 - var1->_aliases.clear(); - } - - // var1 gets all var2s aliases - for (i = var2->_aliases.begin(); i != var2->_aliases.end(); ++i) - { - if (*i != varid1) - var1->_aliases.insert(*i); - } - - // var2 is an alias of var1 - var2->_aliases.insert(varid1); - var1->_aliases.insert(varid2); - - if (var2->_type == Variables::pointer) - var2->_read = true; -} - -void Variables::clearAliases(unsigned int varid) -{ - VariableUsage *usage = find(varid); - - if (usage) - { - // remove usage from all aliases - std::set::iterator i; - - for (i = usage->_aliases.begin(); i != usage->_aliases.end(); ++i) - { - VariableUsage *temp = find(*i); - - if (temp) - temp->_aliases.erase(usage->_name->varId()); - } - - // remove all aliases from usage - usage->_aliases.clear(); - } -} - -void Variables::eraseAliases(unsigned int varid) -{ - VariableUsage *usage = find(varid); - - if (usage) - { - std::set::iterator aliases; - - for (aliases = usage->_aliases.begin(); aliases != usage->_aliases.end(); ++aliases) - erase(*aliases); - } -} - -void Variables::eraseAll(unsigned int varid) -{ - eraseAliases(varid); - erase(varid); -} - -void Variables::addVar(const Token *name, - VariableType type, - ScopeInfo *scope, - bool write_) -{ - if (name->varId() > 0) - _varUsage.insert(std::make_pair(name->varId(), VariableUsage(name, type, scope, false, write_, false))); -} - -void Variables::allocateMemory(unsigned int varid) -{ - VariableUsage *usage = find(varid); - - if (usage) - usage->_allocateMemory = true; -} - -void Variables::read(unsigned int varid) -{ - VariableUsage *usage = find(varid); - - if (usage) - usage->_read = true; -} - -void Variables::readAliases(unsigned int varid) -{ - VariableUsage *usage = find(varid); - - if (usage) - { - std::set::iterator aliases; - - for (aliases = usage->_aliases.begin(); aliases != usage->_aliases.end(); ++aliases) - { - VariableUsage *aliased = find(*aliases); - - if (aliased) - aliased->_read = true; - } - } -} - -void Variables::readAll(unsigned int varid) -{ - VariableUsage *usage = find(varid); - - if (usage) - { - usage->_read = true; - - std::set::iterator aliases; - - for (aliases = usage->_aliases.begin(); aliases != usage->_aliases.end(); ++aliases) - { - VariableUsage *aliased = find(*aliases); - - if (aliased) - aliased->_read = true; - } - } -} - -void Variables::write(unsigned int varid) -{ - VariableUsage *usage = find(varid); - - if (usage) - usage->_write = true; -} - -void Variables::writeAliases(unsigned int varid) -{ - VariableUsage *usage = find(varid); - - if (usage) - { - std::set::iterator aliases; - - for (aliases = usage->_aliases.begin(); aliases != usage->_aliases.end(); ++aliases) - { - VariableUsage *aliased = find(*aliases); - - if (aliased) - aliased->_write = true; - } - } -} - -void Variables::writeAll(unsigned int varid) -{ - VariableUsage *usage = find(varid); - - if (usage) - { - usage->_write = true; - - std::set::iterator aliases; - - for (aliases = usage->_aliases.begin(); aliases != usage->_aliases.end(); ++aliases) - { - VariableUsage *aliased = find(*aliases); - - if (aliased) - aliased->_write = true; - } - } -} - -void Variables::use(unsigned int varid) -{ - VariableUsage *usage = find(varid); - - if (usage) - { - usage->use(); - - std::set::iterator aliases; - - for (aliases = usage->_aliases.begin(); aliases != usage->_aliases.end(); ++aliases) - { - VariableUsage *aliased = find(*aliases); - - if (aliased) - aliased->use(); - } - } -} - -void Variables::modified(unsigned int varid) -{ - VariableUsage *usage = find(varid); - - if (usage) - { - usage->_modified = true; - - std::set::iterator aliases; - - for (aliases = usage->_aliases.begin(); aliases != usage->_aliases.end(); ++aliases) - { - VariableUsage *aliased = find(*aliases); - - if (aliased) - aliased->_modified = true; - } - } -} - -Variables::VariableUsage *Variables::find(unsigned int varid) -{ - if (varid) - { - VariableMap::iterator i = _varUsage.find(varid); - if (i != _varUsage.end()) - return &i->second; - } - return 0; -} - -static int doAssignment(Variables &variables, const Token *tok, bool dereference, ScopeInfo *scope) -{ - int next = 0; - - // a = a + b; - if (Token::Match(tok, "%var% = %var% !!;") && tok->str() == tok->strAt(2)) - { - return 2; - } - - // check for aliased variable - const unsigned int varid1 = tok->varId(); - Variables::VariableUsage *var1 = variables.find(varid1); - - if (var1) - { - Variables::VariableUsage *var2 = 0; - int start = 1; - - // search for '=' - while (tok->tokAt(start)->str() != "=") - start++; - - start++; - - if (Token::Match(tok->tokAt(start), "&| %var%") || - Token::Match(tok->tokAt(start), "( const| struct|union| %type% *| ) &| %var%") || - Token::Match(tok->tokAt(start), "( const| struct|union| %type% *| ) ( &| %var%") || - Token::Match(tok->tokAt(start), "%any% < const| struct|union| %type% *| > ( &| %var%")) - { - unsigned char offset = 0; - unsigned int varid2; - bool addressOf = false; - - if (Token::Match(tok->tokAt(start), "%var% .")) - variables.use(tok->tokAt(start)->varId()); // use = read + write - - // check for C style cast - if (tok->tokAt(start)->str() == "(") - { - if (tok->tokAt(start + 1)->str() == "const") - offset++; - - if (Token::Match(tok->tokAt(start + 1 + offset), "struct|union")) - offset++; - - if (tok->tokAt(start + 2 + offset)->str() == "*") - offset++; - - if (tok->tokAt(start + 3 + offset)->str() == "&") - { - addressOf = true; - next = start + 4 + offset; - } - else if (tok->tokAt(start + 3 + offset)->str() == "(") - { - if (tok->tokAt(start + 4 + offset)->str() == "&") - { - addressOf = true; - next = start + 5 + offset; - } - else - next = start + 4 + offset; - } - else - next = start + 3 + offset; - } - - // check for C++ style cast - else if (tok->tokAt(start)->str().find("cast") != std::string::npos && - tok->tokAt(start + 1)->str() == "<") - { - if (tok->tokAt(start + 2)->str() == "const") - offset++; - - if (Token::Match(tok->tokAt(start + 2 + offset), "struct|union")) - offset++; - - if (tok->tokAt(start + 3 + offset)->str() == "*") - offset++; - - if (tok->tokAt(start + 5 + offset)->str() == "&") - { - addressOf = true; - next = start + 6 + offset; - } - else - next = start + 5 + offset; - } - - // check for var ? ... - else if (Token::Match(tok->tokAt(start), "%var% ?")) - { - next = start; - } - - // no cast - else - { - if (tok->tokAt(start)->str() == "&") - { - addressOf = true; - next = start + 1; - } - else if (tok->tokAt(start)->str() == "new") - return 0; - else - next = start; - } - - // check if variable is local - varid2 = tok->tokAt(next)->varId(); - var2 = variables.find(varid2); - - if (var2) // local variable (alias or read it) - { - if (var1->_type == Variables::pointer) - { - if (dereference) - variables.read(varid2); - else - { - if (addressOf || - var2->_type == Variables::array || - var2->_type == Variables::pointer) - { - bool replace = true; - - // check if variable declared in same scope - if (scope == var1->_scope) - replace = true; - - // not in same scope as declaration - else - { - std::set::iterator assignment; - - // check for an assignment in this scope - assignment = var1->_assignments.find(scope); - - // no other assignment in this scope - if (assignment == var1->_assignments.end()) - { - // nothing to replace - if (var1->_assignments.empty()) - replace = false; - - // this variable has previous assignments - else - { - /** - * @todo determine if existing aliases should be replaced or merged - */ - - replace = false; - } - } - - // assignment in this scope - else - { - // replace when only one other assignment - if (var1->_assignments.size() == 1) - replace = true; - - // otherwise, merge them - else - replace = false; - } - } - - variables.alias(varid1, varid2, replace); - } - else if (tok->tokAt(next + 1)->str() == "?") - { - if (var2->_type == Variables::reference) - variables.readAliases(varid2); - else - variables.read(varid2); - } - } - } - else if (var1->_type == Variables::reference) - { - variables.alias(varid1, varid2, true); - } - else - { - if (var2->_type == Variables::pointer && tok->tokAt(next + 1)->str() == "[") - variables.readAliases(varid2); - - variables.read(varid2); - } - } - else // not a local variable (or an unsupported local variable) - { - if (var1->_type == Variables::pointer && !dereference) - { - // check if variable declaration is in this scope - if (var1->_scope == scope) - variables.clearAliases(varid1); - else - { - std::set::iterator assignment; - - // check for an assignment in this scope - assignment = var1->_assignments.find(scope); - - // no other assignment in this scope - if (assignment == var1->_assignments.end()) - { - /** - * @todo determine if existing aliases should be discarded - */ - } - - // this assignment replaces the last assignment in this scope - else - { - // aliased variables in a larger scope are not supported - // remove all aliases - variables.clearAliases(varid1); - } - } - } - } - } - - var1->_assignments.insert(scope); - } - - // check for alias to struct member - // char c[10]; a.b = c; - else if (Token::Match(tok->tokAt(-2), "%var% .")) - { - if (Token::Match(tok->tokAt(2), "%var%")) - { - unsigned int varid2 = tok->tokAt(2)->varId(); - Variables::VariableUsage *var2 = variables.find(varid2); - - // struct member aliased to local variable - if (var2 && (var2->_type == Variables::array || - var2->_type == Variables::pointer)) - { - // erase aliased variable and all variables that alias it - // to prevent false positives - variables.eraseAll(varid2); - } - } - } - - return next; -} - -static bool nextIsStandardType(const Token *tok) -{ - tok = tok->next(); - - if (tok->str() == "static") - tok = tok->next(); - - return tok->isStandardType(); -} - -static bool nextIsStandardTypeOrVoid(const Token *tok) -{ - tok = tok->next(); - - if (tok->str() == "static") - tok = tok->next(); - - if (tok->str() == "const") - tok = tok->next(); - - return tok->isStandardType() || tok->str() == "void"; -} - -bool CheckOther::isRecordTypeWithoutSideEffects(const Token *tok) -{ - const Variable * var = _tokenizer->getSymbolDatabase()->getVariableFromVarId(tok->varId()); - - // a type that has no side effects (no constructors and no members with constructors) - /** @todo false negative: check base class for side effects */ - /** @todo false negative: check constructors for side effects */ - if (var && var->type() && var->type()->numConstructors == 0 && - (var->type()->varlist.empty() || var->type()->needInitialization == Scope::True) && - var->type()->derivedFrom.empty()) - return true; - - return false; -} - -void CheckOther::functionVariableUsage() -{ - if (!_settings->isEnabled("style")) - return; - - // Parse all executing scopes.. - const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase(); - - std::list::const_iterator scope; - - for (scope = symbolDatabase->scopeList.begin(); scope != symbolDatabase->scopeList.end(); ++scope) - { - // only check functions - if (scope->type != Scope::eFunction) - continue; - - // First token for the current scope.. - const Token *const tok1 = scope->classStart; - - // varId, usage {read, write, modified} - Variables variables; - - // scopes - ScopeInfo scopes; - ScopeInfo *info = &scopes; - - unsigned int indentlevel = 0; - for (const Token *tok = tok1; tok; tok = tok->next()) - { - if (tok->str() == "{") - { - // replace the head node when found - if (indentlevel == 0) - scopes = ScopeInfo(tok, NULL); - // add the new scope - else - info = info->addChild(tok); - ++indentlevel; - } - else if (tok->str() == "}") - { - --indentlevel; - - info = info->parent(); - - if (indentlevel == 0) - break; - } - else if (Token::Match(tok, "struct|union|class {") || - Token::Match(tok, "struct|union|class %type% {|:")) - { - while (tok->str() != "{") - tok = tok->next(); - tok = tok->link(); - if (! tok) - break; - } - - if (Token::Match(tok, "[;{}] asm ( ) ;")) - { - variables.clear(); - break; - } - - // standard type declaration with possible initialization - // int i; int j = 0; static int k; - if (Token::Match(tok, "[;{}] static| %type% %var% ;|=") && - !Token::Match(tok->next(), "return|throw")) - { - tok = tok->next(); - - const bool isStatic = tok->str() == "static"; - if (isStatic) - tok = tok->next(); - - if (tok->isStandardType() || isRecordTypeWithoutSideEffects(tok->next())) - { - variables.addVar(tok->next(), Variables::standard, info, - tok->tokAt(2)->str() == "=" || isStatic); - } - tok = tok->next(); - } - - // standard const type declaration - // const int i = x; - else if (Token::Match(tok, "[;{}] const %type% %var% =")) - { - tok = tok->next()->next(); - - if (tok->isStandardType() || isRecordTypeWithoutSideEffects(tok->next())) - variables.addVar(tok->next(), Variables::standard, info, true); - - tok = tok->next(); - } - - // std::string declaration with possible initialization - // std::string s; std::string s = "string"; - else if (Token::Match(tok, "[;{}] static| std :: string %var% ;|=")) - { - tok = tok->next(); - - const bool isStatic = tok->str() == "static"; - if (isStatic) - tok = tok->next(); - - tok = tok->tokAt(3); - variables.addVar(tok, Variables::standard, info, - tok->next()->str() == "=" || isStatic); - } - - // standard struct type declaration with possible initialization - // struct S s; struct S s = { 0 }; static struct S s; - else if (Token::Match(tok, "[;{}] static| struct %type% %var% ;|=") && - (isRecordTypeWithoutSideEffects(tok->strAt(1) == "static" ? tok->tokAt(4) : tok->tokAt(3)))) - { - tok = tok->next(); - - bool isStatic = tok->str() == "static"; - if (isStatic) - tok = tok->next(); - - tok = tok->next(); - - variables.addVar(tok->next(), Variables::standard, info, - tok->tokAt(2)->str() == "=" || isStatic); - tok = tok->next(); - } - - // standard type declaration and initialization using constructor - // int i(0); static int j(0); - else if (Token::Match(tok, "[;{}] static| %type% %var% ( %any% ) ;") && - nextIsStandardType(tok)) - { - tok = tok->next(); - - if (tok->str() == "static") - tok = tok->next(); - - variables.addVar(tok->next(), Variables::standard, info, true); - - // check if a local variable is used to initialize this variable - if (tok->tokAt(3)->varId() > 0) - variables.readAll(tok->tokAt(3)->varId()); - tok = tok->tokAt(4); - } - - // standard type declaration of array of with possible initialization - // int i[10]; int j[2] = { 0, 1 }; static int k[2] = { 2, 3 }; - else if (Token::Match(tok, "[;{}] static| const| %type% *| %var% [ %any% ] ;|=") && - nextIsStandardType(tok)) - { - tok = tok->next(); - - const bool isStatic = tok->str() == "static"; - if (isStatic) - tok = tok->next(); - - if (tok->str() == "const") - tok = tok->next(); - - if (tok->str() != "return" && tok->str() != "throw") - { - bool isPointer = bool(tok->strAt(1) == "*"); - const Token * const nametok = tok->tokAt(isPointer ? 2 : 1); - - variables.addVar(nametok, isPointer ? Variables::pointerArray : Variables::array, info, - nametok->tokAt(4)->str() == "=" || isStatic); - - // check for reading array size from local variable - if (nametok->tokAt(2)->varId() != 0) - variables.read(nametok->tokAt(2)->varId()); - - // look at initializers - if (Token::simpleMatch(nametok->tokAt(4), "= {")) - { - tok = nametok->tokAt(6); - while (tok->str() != "}") - { - if (Token::Match(tok, "%var%")) - variables.read(tok->varId()); - tok = tok->next(); - } - } - else - tok = nametok->tokAt(3); - } - } - - // pointer or reference declaration with possible initialization - // int * i; int * j = 0; static int * k = 0; - else if (Token::Match(tok, "[;{}] static| const| %type% *|& %var% ;|=")) - { - tok = tok->next(); - - const bool isStatic = tok->str() == "static"; - if (isStatic) - tok = tok->next(); - - if (tok->str() == "const") - tok = tok->next(); - - if (tok->strAt(1) == "::") - tok = tok->tokAt(2); - - if (tok->str() != "return" && tok->str() != "throw") - { - Variables::VariableType type; - - if (tok->next()->str() == "*") - type = Variables::pointer; - else - type = Variables::reference; - - bool written = tok->tokAt(3)->str() == "="; - - variables.addVar(tok->tokAt(2), type, info, written || isStatic); - - int offset = 0; - - // check for assignment - if (written) - offset = doAssignment(variables, tok->tokAt(2), false, info); - - tok = tok->tokAt(2 + offset); - } - } - - // pointer to pointer declaration with possible initialization - // int ** i; int ** j = 0; static int ** k = 0; - else if (Token::Match(tok, "[;{}] static| const| %type% * * %var% ;|=")) - { - tok = tok->next(); - - const bool isStatic = tok->str() == "static"; - if (isStatic) - tok = tok->next(); - - if (tok->str() == "const") - tok = tok->next(); - - if (tok->str() != "return") - { - bool written = tok->tokAt(4)->str() == "="; - - variables.addVar(tok->tokAt(3), Variables::pointerPointer, info, written || isStatic); - - int offset = 0; - - // check for assignment - if (written) - offset = doAssignment(variables, tok->tokAt(3), false, info); - - tok = tok->tokAt(3 + offset); - } - } - - // pointer or reference of struct or union declaration with possible initialization - // struct s * i; struct s * j = 0; static struct s * k = 0; - else if (Token::Match(tok, "[;{}] static| const| struct|union %type% *|& %var% ;|=")) - { - Variables::VariableType type; - - tok = tok->next(); - - const bool isStatic = tok->str() == "static"; - if (isStatic) - tok = tok->next(); - - if (tok->str() == "const") - tok = tok->next(); - - if (tok->strAt(2) == "*") - type = Variables::pointer; - else - type = Variables::reference; - - const bool written = tok->strAt(4) == "="; - - variables.addVar(tok->tokAt(3), type, info, written || isStatic); - - int offset = 0; - - // check for assignment - if (written) - offset = doAssignment(variables, tok->tokAt(3), false, info); - - tok = tok->tokAt(3 + offset); - } - - // pointer or reference declaration with initialization using constructor - // int * i(j); int * k(i); static int * l(i); - else if (Token::Match(tok, "[;{}] static| const| %type% &|* %var% ( %any% ) ;") && - nextIsStandardTypeOrVoid(tok)) - { - Variables::VariableType type; - - tok = tok->next(); - - if (tok->str() == "static") - tok = tok->next(); - - if (tok->str() == "const") - tok = tok->next(); - - if (tok->next()->str() == "*") - type = Variables::pointer; - else - type = Variables::reference; - - unsigned int varid = 0; - - // check for aliased variable - if (Token::Match(tok->tokAt(4), "%var%")) - varid = tok->tokAt(4)->varId(); - - variables.addVar(tok->tokAt(2), type, info, true); - - // check if a local variable is used to initialize this variable - if (varid > 0) - { - Variables::VariableUsage *var = variables.find(varid); - - if (type == Variables::pointer) - { - variables.use(tok->tokAt(4)->varId()); - - if (var && (var->_type == Variables::array || - var->_type == Variables::pointer)) - var->_aliases.insert(tok->varId()); - } - else - { - variables.readAll(tok->tokAt(4)->varId()); - if (var) - var->_aliases.insert(tok->varId()); - } - } - tok = tok->tokAt(5); - } - - // array of pointer or reference declaration with possible initialization - // int * p[10]; int * q[10] = { 0 }; static int * * r[10] = { 0 }; - else if (Token::Match(tok, "[;{}] static| const| %type% *|& %var% [ %any% ] ;|=")) - { - tok = tok->next(); - - const bool isStatic = tok->str() == "static"; - if (isStatic) - tok = tok->next(); - - if (tok->str() == "const") - tok = tok->next(); - - if (tok->str() != "return") - { - variables.addVar(tok->tokAt(2), - tok->next()->str() == "*" ? Variables::pointerArray : Variables::referenceArray, info, - tok->tokAt(6)->str() == "=" || isStatic); - - // check for reading array size from local variable - if (tok->tokAt(4)->varId() != 0) - variables.read(tok->tokAt(4)->varId()); - - tok = tok->tokAt(5); - } - } - - // array of pointer or reference of struct or union declaration with possible initialization - // struct S * p[10]; struct T * q[10] = { 0 }; static struct S * r[10] = { 0 }; - else if (Token::Match(tok, "[;{}] static| const| struct|union %type% *|& %var% [ %any% ] ;|=")) - { - tok = tok->next(); - - const bool isStatic = tok->str() == "static"; - if (isStatic) - tok = tok->next(); - - if (tok->str() == "const") - tok = tok->next(); - - variables.addVar(tok->tokAt(3), - tok->tokAt(2)->str() == "*" ? Variables::pointerArray : Variables::referenceArray, info, - tok->tokAt(7)->str() == "=" || isStatic); - - // check for reading array size from local variable - if (tok->tokAt(5)->varId() != 0) - variables.read(tok->tokAt(5)->varId()); - - tok = tok->tokAt(6); - } - - // Freeing memory (not considered "using" the pointer if it was also allocated in this function) - else if (Token::Match(tok, "free|g_free|kfree|vfree ( %var% )") || - Token::Match(tok, "delete %var% ;") || - Token::Match(tok, "delete [ ] %var% ;")) - { - unsigned int varid = 0; - if (tok->str() != "delete") - { - varid = tok->tokAt(2)->varId(); - tok = tok->tokAt(3); - } - else if (tok->strAt(1) == "[") - { - varid = tok->tokAt(3)->varId(); - tok = tok->tokAt(4); - } - else - { - varid = tok->next()->varId(); - tok = tok->tokAt(2); - } - - Variables::VariableUsage *var = variables.find(varid); - if (var && !var->_allocateMemory) - { - variables.readAll(varid); - } - } - - else if (Token::Match(tok, "return|throw %var%")) - variables.readAll(tok->next()->varId()); - - // assignment - else if (Token::Match(tok, "*| (| ++|--| %var% ++|--| )| =") || - Token::Match(tok, "*| ( const| %type% *| ) %var% =")) - { - bool dereference = false; - bool pre = false; - bool post = false; - - if (tok->str() == "*") - { - dereference = true; - tok = tok->next(); - } - - if (Token::Match(tok, "( const| %type% *| ) %var% =")) - tok = tok->link()->next(); - - else if (tok->str() == "(") - tok = tok->next(); - - if (Token::Match(tok, "++|--")) - { - pre = true; - tok = tok->next(); - } - - if (Token::Match(tok->next(), "++|--")) - post = true; - - const unsigned int varid1 = tok->varId(); - const Token *start = tok; - - tok = tok->tokAt(doAssignment(variables, tok, dereference, info)); - - if (pre || post) - variables.use(varid1); - - if (dereference) - { - Variables::VariableUsage *var = variables.find(varid1); - if (var && var->_type == Variables::array) - variables.write(varid1); - variables.writeAliases(varid1); - variables.read(varid1); - } - else - { - Variables::VariableUsage *var = variables.find(varid1); - if (var && var->_type == Variables::reference) - { - variables.writeAliases(varid1); - variables.read(varid1); - } - // Consider allocating memory separately because allocating/freeing alone does not constitute using the variable - else if (var && var->_type == Variables::pointer && - Token::Match(start, "%var% = new|malloc|calloc|g_malloc|kmalloc|vmalloc")) - { - bool allocate = true; - - if (start->strAt(2) == "new") - { - // is it a user defined type? - if (!start->tokAt(3)->isStandardType()) - { - if (!isRecordTypeWithoutSideEffects(start)) - allocate = false; - } - } - - if (allocate) - variables.allocateMemory(varid1); - else - variables.write(varid1); - } - else if (varid1 && Token::Match(tok, "%varid% .", varid1)) - { - variables.use(varid1); - } - else - { - variables.write(varid1); - } - - Variables::VariableUsage *var2 = variables.find(tok->varId()); - if (var2) - { - if (var2->_type == Variables::reference) - { - variables.writeAliases(tok->varId()); - variables.read(tok->varId()); - } - else if (tok->varId() != varid1 && Token::Match(tok, "%var% .")) - variables.read(tok->varId()); - else if (tok->varId() != varid1 && - var2->_type == Variables::standard && - tok->strAt(-1) != "&") - variables.use(tok->varId()); - } - } - - const Token *equal = tok->next(); - - if (Token::Match(tok->next(), "[ %any% ]")) - equal = tok->tokAt(4); - - // checked for chained assignments - if (tok != start && equal->str() == "=") - { - Variables::VariableUsage *var = variables.find(tok->varId()); - - if (var && var->_type != Variables::reference) - var->_read = true; - - tok = tok->previous(); - } - } - - // assignment - else if (Token::Match(tok, "%var% [") && Token::simpleMatch(tok->next()->link(), "] =")) - { - unsigned int varid = tok->varId(); - const Variables::VariableUsage *var = variables.find(varid); - - if (var) - { - // Consider allocating memory separately because allocating/freeing alone does not constitute using the variable - if (var->_type == Variables::pointer && - Token::Match(tok->next()->link(), "] = new|malloc|calloc|g_malloc|kmalloc|vmalloc")) - { - variables.allocateMemory(varid); - } - else if (var->_type == Variables::pointer || var->_type == Variables::reference) - { - variables.read(varid); - variables.writeAliases(varid); - } - else - variables.writeAll(varid); - } - } - - else if (Token::Match(tok, ">>|& %var%")) - variables.use(tok->next()->varId()); // use = read + write - else if (Token::Match(tok, "[;{}] %var% >>")) - variables.use(tok->next()->varId()); // use = read + write - - // function parameter - else if (Token::Match(tok, "[(,] %var% [")) - variables.use(tok->next()->varId()); // use = read + write - else if (Token::Match(tok, "[(,] %var% [,)]") && tok->previous()->str() != "*") - variables.use(tok->next()->varId()); // use = read + write - else if (Token::Match(tok, "[(,] (") && - Token::Match(tok->next()->link(), ") %var% [,)]")) - variables.use(tok->next()->link()->next()->varId()); // use = read + write - - // function - else if (Token::Match(tok, "%var% (")) - { - variables.read(tok->varId()); - if (Token::Match(tok->tokAt(2), "%var% =")) - variables.read(tok->tokAt(2)->varId()); - } - - else if (Token::Match(tok, "[{,] %var% [,}]")) - variables.read(tok->next()->varId()); - - else if (Token::Match(tok, "%var% .")) - variables.use(tok->varId()); // use = read + write - - else if ((Token::Match(tok, "[(=&!]") || tok->isExtendedOp()) && - (Token::Match(tok->next(), "%var%") && !Token::Match(tok->next(), "true|false|new"))) - variables.readAll(tok->next()->varId()); - - else if (Token::Match(tok, "%var%") && (tok->next()->str() == ")" || tok->next()->isExtendedOp())) - variables.readAll(tok->varId()); - - else if (Token::Match(tok, "; %var% ;")) - variables.readAll(tok->next()->varId()); - - if (Token::Match(tok, "++|-- %var%")) - { - if (tok->strAt(-1) != ";") - variables.use(tok->next()->varId()); - else - variables.modified(tok->next()->varId()); - } - - else if (Token::Match(tok, "%var% ++|--")) - { - if (tok->strAt(-1) != ";") - variables.use(tok->varId()); - else - variables.modified(tok->varId()); - } - - else if (tok->isAssignmentOp()) - { - for (const Token *tok2 = tok->next(); tok2 && tok2->str() != ";"; tok2 = tok2->next()) - { - if (tok2->varId()) - { - variables.read(tok2->varId()); - if (tok2->next()->isAssignmentOp()) - variables.write(tok2->varId()); - } - } - } - } - - // Check usage of all variables in the current scope.. - Variables::VariableMap::const_iterator it; - for (it = variables.varUsage().begin(); it != variables.varUsage().end(); ++it) - { - const Variables::VariableUsage &usage = it->second; - const std::string &varname = usage._name->str(); - - // variable has been marked as unused so ignore it - if (usage._name->isUnused()) - continue; - - // skip things that are only partially implemented to prevent false positives - if (usage._type == Variables::pointerPointer || - usage._type == Variables::pointerArray || - usage._type == Variables::referenceArray) - continue; - - // variable has had memory allocated for it, but hasn't done - // anything with that memory other than, perhaps, freeing it - if (usage.unused() && !usage._modified && usage._allocateMemory) - allocatedButUnusedVariableError(usage._name, varname); - - // variable has not been written, read, or modified - else if (usage.unused() && !usage._modified) - unusedVariableError(usage._name, varname); - - // variable has not been written but has been modified - else if (usage._modified & !usage._write) - unassignedVariableError(usage._name, varname); - - // variable has been written but not read - else if (!usage._read && !usage._modified) - unreadVariableError(usage._name, varname); - - // variable has been read but not written - else if (!usage._write && !usage._allocateMemory) - unassignedVariableError(usage._name, varname); - } - } -} - -void CheckOther::unusedVariableError(const Token *tok, const std::string &varname) -{ - reportError(tok, Severity::style, "unusedVariable", "Unused variable: " + varname); -} - -void CheckOther::allocatedButUnusedVariableError(const Token *tok, const std::string &varname) -{ - reportError(tok, Severity::style, "unusedAllocatedMemory", "Variable '" + varname + "' is allocated memory that is never used"); -} - -void CheckOther::unreadVariableError(const Token *tok, const std::string &varname) -{ - reportError(tok, Severity::style, "unreadVariable", "Variable '" + varname + "' is assigned a value that is never used"); -} - -void CheckOther::unassignedVariableError(const Token *tok, const std::string &varname) -{ - reportError(tok, Severity::style, "unassignedVariable", "Variable '" + varname + "' is not assigned a value"); -} - //--------------------------------------------------------------------------- // Check scope of variables.. //--------------------------------------------------------------------------- @@ -2995,107 +1610,6 @@ void CheckOther::passedByValueError(const Token *tok, const std::string &parname "as a (const) reference which is usually faster and recommended in C++."); } -//--------------------------------------------------------------------------- -// Check that all struct members are used -//--------------------------------------------------------------------------- -void CheckOther::checkStructMemberUsage() -{ - if (!_settings->isEnabled("style")) - return; - - std::string structname; - for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next()) - { - if (tok->fileIndex() != 0) - continue; - - if (Token::Match(tok, "struct|union %type% {")) - { - structname.clear(); - if (Token::simpleMatch(tok->previous(), "extern")) - continue; - if ((!tok->previous() || Token::simpleMatch(tok->previous(), ";")) && Token::Match(tok->tokAt(2)->link(), ("} ; " + tok->strAt(1) + " %var% ;").c_str())) - continue; - - structname = tok->strAt(1); - - // Bail out if struct/union contain any functions - for (const Token *tok2 = tok->tokAt(2); tok2; tok2 = tok2->next()) - { - if (tok2->str() == "(") - { - structname.clear(); - break; - } - - if (tok2->str() == "}") - break; - } - - // bail out if struct is inherited - if (!structname.empty() && Token::findmatch(tok, (",|private|protected|public " + structname).c_str())) - structname.clear(); - - // Bail out if some data is casted to struct.. - const std::string s("( struct| " + tok->next()->str() + " * ) & %var% ["); - if (Token::findmatch(tok, s.c_str())) - structname.clear(); - - // Try to prevent false positives when struct members are not used directly. - if (Token::findmatch(tok, (structname + " *").c_str())) - structname.clear(); - else if (Token::findmatch(tok, (structname + " %type% *").c_str())) - structname = ""; - } - - if (tok->str() == "}") - structname.clear(); - - if (!structname.empty() && Token::Match(tok, "[{;]")) - { - // Declaring struct variable.. - std::string varname; - - // declaring a POD variable? - if (!tok->next()->isStandardType()) - continue; - - if (Token::Match(tok->next(), "%type% %var% [;[]")) - varname = tok->strAt(2); - else if (Token::Match(tok->next(), "%type% %type% %var% [;[]")) - varname = tok->strAt(3); - else if (Token::Match(tok->next(), "%type% * %var% [;[]")) - varname = tok->strAt(3); - else if (Token::Match(tok->next(), "%type% %type% * %var% [;[]")) - varname = tok->strAt(4); - else - continue; - - // Check if the struct variable is used anywhere in the file - const std::string usagePattern(". " + varname); - bool used = false; - for (const Token *tok2 = _tokenizer->tokens(); tok2; tok2 = tok2->next()) - { - if (Token::simpleMatch(tok2, usagePattern.c_str())) - { - used = true; - break; - } - } - - if (! used) - { - unusedStructMemberError(tok->next(), structname, varname); - } - } - } -} - -void CheckOther::unusedStructMemberError(const Token *tok, const std::string &structname, const std::string &varname) -{ - reportError(tok, Severity::style, "unusedStructMember", "struct or union member '" + structname + "::" + varname + "' is never used"); -} - //--------------------------------------------------------------------------- // Check usage of char variables.. //--------------------------------------------------------------------------- diff --git a/lib/checkother.h b/lib/checkother.h index 798279b6a..4c9c76949 100644 --- a/lib/checkother.h +++ b/lib/checkother.h @@ -54,8 +54,6 @@ public: checkOther.warningOldStylePointerCast(); checkOther.checkUnsignedDivision(); checkOther.checkCharVariable(); - checkOther.functionVariableUsage(); - checkOther.checkStructMemberUsage(); checkOther.strPlusChar(); checkOther.sizeofsizeof(); checkOther.sizeofCalculation(); @@ -129,22 +127,12 @@ public: /** @brief %Check for unsigned division */ void checkUnsignedDivision(); - /** @brief %Check for unused function variables */ - void functionVariableUsage(); - void unusedVariableError(const Token *tok, const std::string &varname); - void allocatedButUnusedVariableError(const Token *tok, const std::string &varname); - void unreadVariableError(const Token *tok, const std::string &varname); - void unassignedVariableError(const Token *tok, const std::string &varname); - /** @brief %Check scope of variables */ void checkVariableScope(); /** @brief %Check for constant function parameter */ void checkConstantFunctionParameter(); - /** @brief %Check that all struct members are used */ - void checkStructMemberUsage(); - /** @brief Using char variable as array index / as operand in bit operation */ void checkCharVariable(); @@ -231,9 +219,6 @@ public: /** @brief %Check for duplicate break statements in a switch or loop */ void checkDuplicateBreak(); - /** @brief check if token is a record type without side effects */ - bool isRecordTypeWithoutSideEffects(const Token *tok); - /** @brief assigning bool to pointer */ void checkAssignBoolToPointer(); @@ -245,7 +230,6 @@ public: void dangerousUsageStrtolError(const Token *tok); void sprintfOverlappingDataError(const Token *tok, const std::string &varname); void udivError(const Token *tok); - void unusedStructMemberError(const Token *tok, const std::string &structname, const std::string &varname); void passedByValueError(const Token *tok, const std::string &parname); void constStatementError(const Token *tok, const std::string &type); void charArrayIndexError(const Token *tok); @@ -296,7 +280,6 @@ public: // style/warning c.cstyleCastError(0); c.dangerousUsageStrtolError(0); - c.unusedStructMemberError(0, "structname", "variable"); c.passedByValueError(0, "parametername"); c.constStatementError(0, "type"); c.charArrayIndexError(0); @@ -312,10 +295,6 @@ public: c.invalidScanfError(0); c.incorrectLogicOperatorError(0, true); c.secondAlwaysTrueFalseWhenFirstTrueError(0, "when first comparison is true, the 2nd comparison is always true"); - c.unusedVariableError(0, "varname"); - c.allocatedButUnusedVariableError(0, "varname"); - c.unreadVariableError(0, "varname"); - c.unassignedVariableError(0, "varname"); c.catchExceptionByValueError(0); c.memsetZeroBytesError(0, "varname"); c.clarifyCalculationError(0, "+"); @@ -358,7 +337,6 @@ public: "* bad usage of the function 'strtol'\n" "* [[CheckUnsignedDivision|unsigned division]]\n" "* Dangerous usage of 'scanf'\n" - "* unused struct member\n" "* passing parameter by value\n" "* [[IncompleteStatement|Incomplete statement]]\n" "* [[charvar|check how signed char variables are used]]\n" diff --git a/lib/checkunusedvar.cpp b/lib/checkunusedvar.cpp new file mode 100644 index 000000000..ac9d472eb --- /dev/null +++ b/lib/checkunusedvar.cpp @@ -0,0 +1,1516 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2011 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 . + */ + + +//--------------------------------------------------------------------------- +#include "checkunusedvar.h" +#include "symboldatabase.h" + +//--------------------------------------------------------------------------- + +// Register this check class (by creating a static instance of it) +namespace +{ +CheckUnusedVar instance; +} + +/** + * @brief This class is used to capture the control flow within a function. + */ +class ScopeInfo +{ +public: + ScopeInfo() : _token(NULL), _parent(NULL) { } + ScopeInfo(const Token *token, ScopeInfo *parent_) : _token(token), _parent(parent_) { } + ~ScopeInfo(); + + ScopeInfo *parent() + { + return _parent; + } + ScopeInfo *addChild(const Token *token); + void remove(ScopeInfo *scope); + +private: + const Token *_token; + ScopeInfo *_parent; + std::list _children; +}; + +ScopeInfo::~ScopeInfo() +{ + while (!_children.empty()) + { + delete *_children.begin(); + _children.pop_front(); + } +} + +ScopeInfo *ScopeInfo::addChild(const Token *token) +{ + ScopeInfo *temp = new ScopeInfo(token, this); + + _children.push_back(temp); + + return temp; +} + +void ScopeInfo::remove(ScopeInfo *scope) +{ + std::list::iterator it; + + for (it = _children.begin(); it != _children.end(); ++it) + { + if (*it == scope) + { + delete *it; + _children.erase(it); + break; + } + } +} + +/** + * @brief This class is used create a list of variables within a function. + */ +class Variables +{ +public: + enum VariableType { standard, array, pointer, reference, pointerArray, referenceArray, pointerPointer }; + + /** Store information about variable usage */ + class VariableUsage + { + public: + VariableUsage(const Token *name = 0, + VariableType type = standard, + ScopeInfo *scope = NULL, + bool read = false, + bool write = false, + bool modified = false, + bool allocateMemory = false) : + _name(name), + _type(type), + _scope(scope), + _read(read), + _write(write), + _modified(modified), + _allocateMemory(allocateMemory) + { + } + + /** variable is used.. set both read+write */ + void use() + { + _read = true; + _write = true; + } + + /** is variable unused? */ + bool unused() const + { + return (_read == false && _write == false); + } + + const Token *_name; + VariableType _type; + ScopeInfo *_scope; + bool _read; + bool _write; + bool _modified; // read/modify/write + bool _allocateMemory; + std::set _aliases; + std::set _assignments; + }; + + typedef std::map VariableMap; + + void clear() + { + _varUsage.clear(); + } + VariableMap &varUsage() + { + return _varUsage; + } + void addVar(const Token *name, VariableType type, ScopeInfo *scope, bool write_); + void allocateMemory(unsigned int varid); + void read(unsigned int varid); + void readAliases(unsigned int varid); + void readAll(unsigned int varid); + void write(unsigned int varid); + void writeAliases(unsigned int varid); + void writeAll(unsigned int varid); + void use(unsigned int varid); + void modified(unsigned int varid); + VariableUsage *find(unsigned int varid); + void alias(unsigned int varid1, unsigned int varid2, bool replace); + void erase(unsigned int varid) + { + _varUsage.erase(varid); + } + void eraseAliases(unsigned int varid); + void eraseAll(unsigned int varid); + void clearAliases(unsigned int varid); + +private: + VariableMap _varUsage; +}; + +/** + * Alias the 2 given variables. Either replace the existing aliases if + * they exist or merge them. You would replace an existing alias when this + * assignment is in the same scope as the previous assignment. You might + * merge the aliases when this assignment is in a different scope from the + * previous assignment depending on the relationship of the 2 scopes. + */ +void Variables::alias(unsigned int varid1, unsigned int varid2, bool replace) +{ + VariableUsage *var1 = find(varid1); + VariableUsage *var2 = find(varid2); + + // alias to self + if (varid1 == varid2) + { + if (var1) + var1->use(); + return; + } + + std::set::iterator i; + + if (replace) + { + // remove var1 from all aliases + for (i = var1->_aliases.begin(); i != var1->_aliases.end(); ++i) + { + VariableUsage *temp = find(*i); + + if (temp) + temp->_aliases.erase(var1->_name->varId()); + } + + // remove all aliases from var1 + var1->_aliases.clear(); + } + + // var1 gets all var2s aliases + for (i = var2->_aliases.begin(); i != var2->_aliases.end(); ++i) + { + if (*i != varid1) + var1->_aliases.insert(*i); + } + + // var2 is an alias of var1 + var2->_aliases.insert(varid1); + var1->_aliases.insert(varid2); + + if (var2->_type == Variables::pointer) + var2->_read = true; +} + +void Variables::clearAliases(unsigned int varid) +{ + VariableUsage *usage = find(varid); + + if (usage) + { + // remove usage from all aliases + std::set::iterator i; + + for (i = usage->_aliases.begin(); i != usage->_aliases.end(); ++i) + { + VariableUsage *temp = find(*i); + + if (temp) + temp->_aliases.erase(usage->_name->varId()); + } + + // remove all aliases from usage + usage->_aliases.clear(); + } +} + +void Variables::eraseAliases(unsigned int varid) +{ + VariableUsage *usage = find(varid); + + if (usage) + { + std::set::iterator aliases; + + for (aliases = usage->_aliases.begin(); aliases != usage->_aliases.end(); ++aliases) + erase(*aliases); + } +} + +void Variables::eraseAll(unsigned int varid) +{ + eraseAliases(varid); + erase(varid); +} + +void Variables::addVar(const Token *name, + VariableType type, + ScopeInfo *scope, + bool write_) +{ + if (name->varId() > 0) + _varUsage.insert(std::make_pair(name->varId(), VariableUsage(name, type, scope, false, write_, false))); +} + +void Variables::allocateMemory(unsigned int varid) +{ + VariableUsage *usage = find(varid); + + if (usage) + usage->_allocateMemory = true; +} + +void Variables::read(unsigned int varid) +{ + VariableUsage *usage = find(varid); + + if (usage) + usage->_read = true; +} + +void Variables::readAliases(unsigned int varid) +{ + VariableUsage *usage = find(varid); + + if (usage) + { + std::set::iterator aliases; + + for (aliases = usage->_aliases.begin(); aliases != usage->_aliases.end(); ++aliases) + { + VariableUsage *aliased = find(*aliases); + + if (aliased) + aliased->_read = true; + } + } +} + +void Variables::readAll(unsigned int varid) +{ + VariableUsage *usage = find(varid); + + if (usage) + { + usage->_read = true; + + std::set::iterator aliases; + + for (aliases = usage->_aliases.begin(); aliases != usage->_aliases.end(); ++aliases) + { + VariableUsage *aliased = find(*aliases); + + if (aliased) + aliased->_read = true; + } + } +} + +void Variables::write(unsigned int varid) +{ + VariableUsage *usage = find(varid); + + if (usage) + usage->_write = true; +} + +void Variables::writeAliases(unsigned int varid) +{ + VariableUsage *usage = find(varid); + + if (usage) + { + std::set::iterator aliases; + + for (aliases = usage->_aliases.begin(); aliases != usage->_aliases.end(); ++aliases) + { + VariableUsage *aliased = find(*aliases); + + if (aliased) + aliased->_write = true; + } + } +} + +void Variables::writeAll(unsigned int varid) +{ + VariableUsage *usage = find(varid); + + if (usage) + { + usage->_write = true; + + std::set::iterator aliases; + + for (aliases = usage->_aliases.begin(); aliases != usage->_aliases.end(); ++aliases) + { + VariableUsage *aliased = find(*aliases); + + if (aliased) + aliased->_write = true; + } + } +} + +void Variables::use(unsigned int varid) +{ + VariableUsage *usage = find(varid); + + if (usage) + { + usage->use(); + + std::set::iterator aliases; + + for (aliases = usage->_aliases.begin(); aliases != usage->_aliases.end(); ++aliases) + { + VariableUsage *aliased = find(*aliases); + + if (aliased) + aliased->use(); + } + } +} + +void Variables::modified(unsigned int varid) +{ + VariableUsage *usage = find(varid); + + if (usage) + { + usage->_modified = true; + + std::set::iterator aliases; + + for (aliases = usage->_aliases.begin(); aliases != usage->_aliases.end(); ++aliases) + { + VariableUsage *aliased = find(*aliases); + + if (aliased) + aliased->_modified = true; + } + } +} + +Variables::VariableUsage *Variables::find(unsigned int varid) +{ + if (varid) + { + VariableMap::iterator i = _varUsage.find(varid); + if (i != _varUsage.end()) + return &i->second; + } + return 0; +} + +static int doAssignment(Variables &variables, const Token *tok, bool dereference, ScopeInfo *scope) +{ + int next = 0; + + // a = a + b; + if (Token::Match(tok, "%var% = %var% !!;") && tok->str() == tok->strAt(2)) + { + return 2; + } + + // check for aliased variable + const unsigned int varid1 = tok->varId(); + Variables::VariableUsage *var1 = variables.find(varid1); + + if (var1) + { + Variables::VariableUsage *var2 = 0; + int start = 1; + + // search for '=' + while (tok->tokAt(start)->str() != "=") + start++; + + start++; + + if (Token::Match(tok->tokAt(start), "&| %var%") || + Token::Match(tok->tokAt(start), "( const| struct|union| %type% *| ) &| %var%") || + Token::Match(tok->tokAt(start), "( const| struct|union| %type% *| ) ( &| %var%") || + Token::Match(tok->tokAt(start), "%any% < const| struct|union| %type% *| > ( &| %var%")) + { + unsigned char offset = 0; + unsigned int varid2; + bool addressOf = false; + + if (Token::Match(tok->tokAt(start), "%var% .")) + variables.use(tok->tokAt(start)->varId()); // use = read + write + + // check for C style cast + if (tok->tokAt(start)->str() == "(") + { + if (tok->tokAt(start + 1)->str() == "const") + offset++; + + if (Token::Match(tok->tokAt(start + 1 + offset), "struct|union")) + offset++; + + if (tok->tokAt(start + 2 + offset)->str() == "*") + offset++; + + if (tok->tokAt(start + 3 + offset)->str() == "&") + { + addressOf = true; + next = start + 4 + offset; + } + else if (tok->tokAt(start + 3 + offset)->str() == "(") + { + if (tok->tokAt(start + 4 + offset)->str() == "&") + { + addressOf = true; + next = start + 5 + offset; + } + else + next = start + 4 + offset; + } + else + next = start + 3 + offset; + } + + // check for C++ style cast + else if (tok->tokAt(start)->str().find("cast") != std::string::npos && + tok->tokAt(start + 1)->str() == "<") + { + if (tok->tokAt(start + 2)->str() == "const") + offset++; + + if (Token::Match(tok->tokAt(start + 2 + offset), "struct|union")) + offset++; + + if (tok->tokAt(start + 3 + offset)->str() == "*") + offset++; + + if (tok->tokAt(start + 5 + offset)->str() == "&") + { + addressOf = true; + next = start + 6 + offset; + } + else + next = start + 5 + offset; + } + + // check for var ? ... + else if (Token::Match(tok->tokAt(start), "%var% ?")) + { + next = start; + } + + // no cast + else + { + if (tok->tokAt(start)->str() == "&") + { + addressOf = true; + next = start + 1; + } + else if (tok->tokAt(start)->str() == "new") + return 0; + else + next = start; + } + + // check if variable is local + varid2 = tok->tokAt(next)->varId(); + var2 = variables.find(varid2); + + if (var2) // local variable (alias or read it) + { + if (var1->_type == Variables::pointer) + { + if (dereference) + variables.read(varid2); + else + { + if (addressOf || + var2->_type == Variables::array || + var2->_type == Variables::pointer) + { + bool replace = true; + + // check if variable declared in same scope + if (scope == var1->_scope) + replace = true; + + // not in same scope as declaration + else + { + std::set::iterator assignment; + + // check for an assignment in this scope + assignment = var1->_assignments.find(scope); + + // no other assignment in this scope + if (assignment == var1->_assignments.end()) + { + // nothing to replace + if (var1->_assignments.empty()) + replace = false; + + // this variable has previous assignments + else + { + /** + * @todo determine if existing aliases should be replaced or merged + */ + + replace = false; + } + } + + // assignment in this scope + else + { + // replace when only one other assignment + if (var1->_assignments.size() == 1) + replace = true; + + // otherwise, merge them + else + replace = false; + } + } + + variables.alias(varid1, varid2, replace); + } + else if (tok->tokAt(next + 1)->str() == "?") + { + if (var2->_type == Variables::reference) + variables.readAliases(varid2); + else + variables.read(varid2); + } + } + } + else if (var1->_type == Variables::reference) + { + variables.alias(varid1, varid2, true); + } + else + { + if (var2->_type == Variables::pointer && tok->tokAt(next + 1)->str() == "[") + variables.readAliases(varid2); + + variables.read(varid2); + } + } + else // not a local variable (or an unsupported local variable) + { + if (var1->_type == Variables::pointer && !dereference) + { + // check if variable declaration is in this scope + if (var1->_scope == scope) + variables.clearAliases(varid1); + else + { + std::set::iterator assignment; + + // check for an assignment in this scope + assignment = var1->_assignments.find(scope); + + // no other assignment in this scope + if (assignment == var1->_assignments.end()) + { + /** + * @todo determine if existing aliases should be discarded + */ + } + + // this assignment replaces the last assignment in this scope + else + { + // aliased variables in a larger scope are not supported + // remove all aliases + variables.clearAliases(varid1); + } + } + } + } + } + + var1->_assignments.insert(scope); + } + + // check for alias to struct member + // char c[10]; a.b = c; + else if (Token::Match(tok->tokAt(-2), "%var% .")) + { + if (Token::Match(tok->tokAt(2), "%var%")) + { + unsigned int varid2 = tok->tokAt(2)->varId(); + Variables::VariableUsage *var2 = variables.find(varid2); + + // struct member aliased to local variable + if (var2 && (var2->_type == Variables::array || + var2->_type == Variables::pointer)) + { + // erase aliased variable and all variables that alias it + // to prevent false positives + variables.eraseAll(varid2); + } + } + } + + return next; +} + +static bool nextIsStandardType(const Token *tok) +{ + tok = tok->next(); + + if (tok->str() == "static") + tok = tok->next(); + + return tok->isStandardType(); +} + +static bool nextIsStandardTypeOrVoid(const Token *tok) +{ + tok = tok->next(); + + if (tok->str() == "static") + tok = tok->next(); + + if (tok->str() == "const") + tok = tok->next(); + + return tok->isStandardType() || tok->str() == "void"; +} + +bool CheckUnusedVar::isRecordTypeWithoutSideEffects(const Token *tok) +{ + const Variable * var = _tokenizer->getSymbolDatabase()->getVariableFromVarId(tok->varId()); + + // a type that has no side effects (no constructors and no members with constructors) + /** @todo false negative: check base class for side effects */ + /** @todo false negative: check constructors for side effects */ + if (var && var->type() && var->type()->numConstructors == 0 && + (var->type()->varlist.empty() || var->type()->needInitialization == Scope::True) && + var->type()->derivedFrom.empty()) + return true; + + return false; +} + +//--------------------------------------------------------------------------- +// Usage of function variables +//--------------------------------------------------------------------------- +void CheckUnusedVar::checkFunctionVariableUsage() +{ + if (!_settings->isEnabled("style")) + return; + + // Parse all executing scopes.. + const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase(); + + std::list::const_iterator scope; + + for (scope = symbolDatabase->scopeList.begin(); scope != symbolDatabase->scopeList.end(); ++scope) + { + // only check functions + if (scope->type != Scope::eFunction) + continue; + + // First token for the current scope.. + const Token *const tok1 = scope->classStart; + + // varId, usage {read, write, modified} + Variables variables; + + // scopes + ScopeInfo scopes; + ScopeInfo *info = &scopes; + + unsigned int indentlevel = 0; + for (const Token *tok = tok1; tok; tok = tok->next()) + { + if (tok->str() == "{") + { + // replace the head node when found + if (indentlevel == 0) + scopes = ScopeInfo(tok, NULL); + // add the new scope + else + info = info->addChild(tok); + ++indentlevel; + } + else if (tok->str() == "}") + { + --indentlevel; + + info = info->parent(); + + if (indentlevel == 0) + break; + } + else if (Token::Match(tok, "struct|union|class {") || + Token::Match(tok, "struct|union|class %type% {|:")) + { + while (tok->str() != "{") + tok = tok->next(); + tok = tok->link(); + if (! tok) + break; + } + + if (Token::Match(tok, "[;{}] asm ( ) ;")) + { + variables.clear(); + break; + } + + // standard type declaration with possible initialization + // int i; int j = 0; static int k; + if (Token::Match(tok, "[;{}] static| %type% %var% ;|=") && + !Token::Match(tok->next(), "return|throw")) + { + tok = tok->next(); + + const bool isStatic = tok->str() == "static"; + if (isStatic) + tok = tok->next(); + + if (tok->isStandardType() || isRecordTypeWithoutSideEffects(tok->next())) + { + variables.addVar(tok->next(), Variables::standard, info, + tok->tokAt(2)->str() == "=" || isStatic); + } + tok = tok->next(); + } + + // standard const type declaration + // const int i = x; + else if (Token::Match(tok, "[;{}] const %type% %var% =")) + { + tok = tok->next()->next(); + + if (tok->isStandardType() || isRecordTypeWithoutSideEffects(tok->next())) + variables.addVar(tok->next(), Variables::standard, info, true); + + tok = tok->next(); + } + + // std::string declaration with possible initialization + // std::string s; std::string s = "string"; + else if (Token::Match(tok, "[;{}] static| std :: string %var% ;|=")) + { + tok = tok->next(); + + const bool isStatic = tok->str() == "static"; + if (isStatic) + tok = tok->next(); + + tok = tok->tokAt(3); + variables.addVar(tok, Variables::standard, info, + tok->next()->str() == "=" || isStatic); + } + + // standard struct type declaration with possible initialization + // struct S s; struct S s = { 0 }; static struct S s; + else if (Token::Match(tok, "[;{}] static| struct %type% %var% ;|=") && + (isRecordTypeWithoutSideEffects(tok->strAt(1) == "static" ? tok->tokAt(4) : tok->tokAt(3)))) + { + tok = tok->next(); + + bool isStatic = tok->str() == "static"; + if (isStatic) + tok = tok->next(); + + tok = tok->next(); + + variables.addVar(tok->next(), Variables::standard, info, + tok->tokAt(2)->str() == "=" || isStatic); + tok = tok->next(); + } + + // standard type declaration and initialization using constructor + // int i(0); static int j(0); + else if (Token::Match(tok, "[;{}] static| %type% %var% ( %any% ) ;") && + nextIsStandardType(tok)) + { + tok = tok->next(); + + if (tok->str() == "static") + tok = tok->next(); + + variables.addVar(tok->next(), Variables::standard, info, true); + + // check if a local variable is used to initialize this variable + if (tok->tokAt(3)->varId() > 0) + variables.readAll(tok->tokAt(3)->varId()); + tok = tok->tokAt(4); + } + + // standard type declaration of array of with possible initialization + // int i[10]; int j[2] = { 0, 1 }; static int k[2] = { 2, 3 }; + else if (Token::Match(tok, "[;{}] static| const| %type% *| %var% [ %any% ] ;|=") && + nextIsStandardType(tok)) + { + tok = tok->next(); + + const bool isStatic = tok->str() == "static"; + if (isStatic) + tok = tok->next(); + + if (tok->str() == "const") + tok = tok->next(); + + if (tok->str() != "return" && tok->str() != "throw") + { + bool isPointer = bool(tok->strAt(1) == "*"); + const Token * const nametok = tok->tokAt(isPointer ? 2 : 1); + + variables.addVar(nametok, isPointer ? Variables::pointerArray : Variables::array, info, + nametok->tokAt(4)->str() == "=" || isStatic); + + // check for reading array size from local variable + if (nametok->tokAt(2)->varId() != 0) + variables.read(nametok->tokAt(2)->varId()); + + // look at initializers + if (Token::simpleMatch(nametok->tokAt(4), "= {")) + { + tok = nametok->tokAt(6); + while (tok->str() != "}") + { + if (Token::Match(tok, "%var%")) + variables.read(tok->varId()); + tok = tok->next(); + } + } + else + tok = nametok->tokAt(3); + } + } + + // pointer or reference declaration with possible initialization + // int * i; int * j = 0; static int * k = 0; + else if (Token::Match(tok, "[;{}] static| const| %type% *|& %var% ;|=")) + { + tok = tok->next(); + + const bool isStatic = tok->str() == "static"; + if (isStatic) + tok = tok->next(); + + if (tok->str() == "const") + tok = tok->next(); + + if (tok->strAt(1) == "::") + tok = tok->tokAt(2); + + if (tok->str() != "return" && tok->str() != "throw") + { + Variables::VariableType type; + + if (tok->next()->str() == "*") + type = Variables::pointer; + else + type = Variables::reference; + + bool written = tok->tokAt(3)->str() == "="; + + variables.addVar(tok->tokAt(2), type, info, written || isStatic); + + int offset = 0; + + // check for assignment + if (written) + offset = doAssignment(variables, tok->tokAt(2), false, info); + + tok = tok->tokAt(2 + offset); + } + } + + // pointer to pointer declaration with possible initialization + // int ** i; int ** j = 0; static int ** k = 0; + else if (Token::Match(tok, "[;{}] static| const| %type% * * %var% ;|=")) + { + tok = tok->next(); + + const bool isStatic = tok->str() == "static"; + if (isStatic) + tok = tok->next(); + + if (tok->str() == "const") + tok = tok->next(); + + if (tok->str() != "return") + { + bool written = tok->tokAt(4)->str() == "="; + + variables.addVar(tok->tokAt(3), Variables::pointerPointer, info, written || isStatic); + + int offset = 0; + + // check for assignment + if (written) + offset = doAssignment(variables, tok->tokAt(3), false, info); + + tok = tok->tokAt(3 + offset); + } + } + + // pointer or reference of struct or union declaration with possible initialization + // struct s * i; struct s * j = 0; static struct s * k = 0; + else if (Token::Match(tok, "[;{}] static| const| struct|union %type% *|& %var% ;|=")) + { + Variables::VariableType type; + + tok = tok->next(); + + const bool isStatic = tok->str() == "static"; + if (isStatic) + tok = tok->next(); + + if (tok->str() == "const") + tok = tok->next(); + + if (tok->strAt(2) == "*") + type = Variables::pointer; + else + type = Variables::reference; + + const bool written = tok->strAt(4) == "="; + + variables.addVar(tok->tokAt(3), type, info, written || isStatic); + + int offset = 0; + + // check for assignment + if (written) + offset = doAssignment(variables, tok->tokAt(3), false, info); + + tok = tok->tokAt(3 + offset); + } + + // pointer or reference declaration with initialization using constructor + // int * i(j); int * k(i); static int * l(i); + else if (Token::Match(tok, "[;{}] static| const| %type% &|* %var% ( %any% ) ;") && + nextIsStandardTypeOrVoid(tok)) + { + Variables::VariableType type; + + tok = tok->next(); + + if (tok->str() == "static") + tok = tok->next(); + + if (tok->str() == "const") + tok = tok->next(); + + if (tok->next()->str() == "*") + type = Variables::pointer; + else + type = Variables::reference; + + unsigned int varid = 0; + + // check for aliased variable + if (Token::Match(tok->tokAt(4), "%var%")) + varid = tok->tokAt(4)->varId(); + + variables.addVar(tok->tokAt(2), type, info, true); + + // check if a local variable is used to initialize this variable + if (varid > 0) + { + Variables::VariableUsage *var = variables.find(varid); + + if (type == Variables::pointer) + { + variables.use(tok->tokAt(4)->varId()); + + if (var && (var->_type == Variables::array || + var->_type == Variables::pointer)) + var->_aliases.insert(tok->varId()); + } + else + { + variables.readAll(tok->tokAt(4)->varId()); + if (var) + var->_aliases.insert(tok->varId()); + } + } + tok = tok->tokAt(5); + } + + // array of pointer or reference declaration with possible initialization + // int * p[10]; int * q[10] = { 0 }; static int * * r[10] = { 0 }; + else if (Token::Match(tok, "[;{}] static| const| %type% *|& %var% [ %any% ] ;|=")) + { + tok = tok->next(); + + const bool isStatic = tok->str() == "static"; + if (isStatic) + tok = tok->next(); + + if (tok->str() == "const") + tok = tok->next(); + + if (tok->str() != "return") + { + variables.addVar(tok->tokAt(2), + tok->next()->str() == "*" ? Variables::pointerArray : Variables::referenceArray, info, + tok->tokAt(6)->str() == "=" || isStatic); + + // check for reading array size from local variable + if (tok->tokAt(4)->varId() != 0) + variables.read(tok->tokAt(4)->varId()); + + tok = tok->tokAt(5); + } + } + + // array of pointer or reference of struct or union declaration with possible initialization + // struct S * p[10]; struct T * q[10] = { 0 }; static struct S * r[10] = { 0 }; + else if (Token::Match(tok, "[;{}] static| const| struct|union %type% *|& %var% [ %any% ] ;|=")) + { + tok = tok->next(); + + const bool isStatic = tok->str() == "static"; + if (isStatic) + tok = tok->next(); + + if (tok->str() == "const") + tok = tok->next(); + + variables.addVar(tok->tokAt(3), + tok->tokAt(2)->str() == "*" ? Variables::pointerArray : Variables::referenceArray, info, + tok->tokAt(7)->str() == "=" || isStatic); + + // check for reading array size from local variable + if (tok->tokAt(5)->varId() != 0) + variables.read(tok->tokAt(5)->varId()); + + tok = tok->tokAt(6); + } + + // Freeing memory (not considered "using" the pointer if it was also allocated in this function) + else if (Token::Match(tok, "free|g_free|kfree|vfree ( %var% )") || + Token::Match(tok, "delete %var% ;") || + Token::Match(tok, "delete [ ] %var% ;")) + { + unsigned int varid = 0; + if (tok->str() != "delete") + { + varid = tok->tokAt(2)->varId(); + tok = tok->tokAt(3); + } + else if (tok->strAt(1) == "[") + { + varid = tok->tokAt(3)->varId(); + tok = tok->tokAt(4); + } + else + { + varid = tok->next()->varId(); + tok = tok->tokAt(2); + } + + Variables::VariableUsage *var = variables.find(varid); + if (var && !var->_allocateMemory) + { + variables.readAll(varid); + } + } + + else if (Token::Match(tok, "return|throw %var%")) + variables.readAll(tok->next()->varId()); + + // assignment + else if (Token::Match(tok, "*| (| ++|--| %var% ++|--| )| =") || + Token::Match(tok, "*| ( const| %type% *| ) %var% =")) + { + bool dereference = false; + bool pre = false; + bool post = false; + + if (tok->str() == "*") + { + dereference = true; + tok = tok->next(); + } + + if (Token::Match(tok, "( const| %type% *| ) %var% =")) + tok = tok->link()->next(); + + else if (tok->str() == "(") + tok = tok->next(); + + if (Token::Match(tok, "++|--")) + { + pre = true; + tok = tok->next(); + } + + if (Token::Match(tok->next(), "++|--")) + post = true; + + const unsigned int varid1 = tok->varId(); + const Token *start = tok; + + tok = tok->tokAt(doAssignment(variables, tok, dereference, info)); + + if (pre || post) + variables.use(varid1); + + if (dereference) + { + Variables::VariableUsage *var = variables.find(varid1); + if (var && var->_type == Variables::array) + variables.write(varid1); + variables.writeAliases(varid1); + variables.read(varid1); + } + else + { + Variables::VariableUsage *var = variables.find(varid1); + if (var && var->_type == Variables::reference) + { + variables.writeAliases(varid1); + variables.read(varid1); + } + // Consider allocating memory separately because allocating/freeing alone does not constitute using the variable + else if (var && var->_type == Variables::pointer && + Token::Match(start, "%var% = new|malloc|calloc|g_malloc|kmalloc|vmalloc")) + { + bool allocate = true; + + if (start->strAt(2) == "new") + { + // is it a user defined type? + if (!start->tokAt(3)->isStandardType()) + { + if (!isRecordTypeWithoutSideEffects(start)) + allocate = false; + } + } + + if (allocate) + variables.allocateMemory(varid1); + else + variables.write(varid1); + } + else if (varid1 && Token::Match(tok, "%varid% .", varid1)) + { + variables.use(varid1); + } + else + { + variables.write(varid1); + } + + Variables::VariableUsage *var2 = variables.find(tok->varId()); + if (var2) + { + if (var2->_type == Variables::reference) + { + variables.writeAliases(tok->varId()); + variables.read(tok->varId()); + } + else if (tok->varId() != varid1 && Token::Match(tok, "%var% .")) + variables.read(tok->varId()); + else if (tok->varId() != varid1 && + var2->_type == Variables::standard && + tok->strAt(-1) != "&") + variables.use(tok->varId()); + } + } + + const Token *equal = tok->next(); + + if (Token::Match(tok->next(), "[ %any% ]")) + equal = tok->tokAt(4); + + // checked for chained assignments + if (tok != start && equal->str() == "=") + { + Variables::VariableUsage *var = variables.find(tok->varId()); + + if (var && var->_type != Variables::reference) + var->_read = true; + + tok = tok->previous(); + } + } + + // assignment + else if (Token::Match(tok, "%var% [") && Token::simpleMatch(tok->next()->link(), "] =")) + { + unsigned int varid = tok->varId(); + const Variables::VariableUsage *var = variables.find(varid); + + if (var) + { + // Consider allocating memory separately because allocating/freeing alone does not constitute using the variable + if (var->_type == Variables::pointer && + Token::Match(tok->next()->link(), "] = new|malloc|calloc|g_malloc|kmalloc|vmalloc")) + { + variables.allocateMemory(varid); + } + else if (var->_type == Variables::pointer || var->_type == Variables::reference) + { + variables.read(varid); + variables.writeAliases(varid); + } + else + variables.writeAll(varid); + } + } + + else if (Token::Match(tok, ">>|& %var%")) + variables.use(tok->next()->varId()); // use = read + write + else if (Token::Match(tok, "[;{}] %var% >>")) + variables.use(tok->next()->varId()); // use = read + write + + // function parameter + else if (Token::Match(tok, "[(,] %var% [")) + variables.use(tok->next()->varId()); // use = read + write + else if (Token::Match(tok, "[(,] %var% [,)]") && tok->previous()->str() != "*") + variables.use(tok->next()->varId()); // use = read + write + else if (Token::Match(tok, "[(,] (") && + Token::Match(tok->next()->link(), ") %var% [,)]")) + variables.use(tok->next()->link()->next()->varId()); // use = read + write + + // function + else if (Token::Match(tok, "%var% (")) + { + variables.read(tok->varId()); + if (Token::Match(tok->tokAt(2), "%var% =")) + variables.read(tok->tokAt(2)->varId()); + } + + else if (Token::Match(tok, "[{,] %var% [,}]")) + variables.read(tok->next()->varId()); + + else if (Token::Match(tok, "%var% .")) + variables.use(tok->varId()); // use = read + write + + else if ((Token::Match(tok, "[(=&!]") || tok->isExtendedOp()) && + (Token::Match(tok->next(), "%var%") && !Token::Match(tok->next(), "true|false|new"))) + variables.readAll(tok->next()->varId()); + + else if (Token::Match(tok, "%var%") && (tok->next()->str() == ")" || tok->next()->isExtendedOp())) + variables.readAll(tok->varId()); + + else if (Token::Match(tok, "; %var% ;")) + variables.readAll(tok->next()->varId()); + + if (Token::Match(tok, "++|-- %var%")) + { + if (tok->strAt(-1) != ";") + variables.use(tok->next()->varId()); + else + variables.modified(tok->next()->varId()); + } + + else if (Token::Match(tok, "%var% ++|--")) + { + if (tok->strAt(-1) != ";") + variables.use(tok->varId()); + else + variables.modified(tok->varId()); + } + + else if (tok->isAssignmentOp()) + { + for (const Token *tok2 = tok->next(); tok2 && tok2->str() != ";"; tok2 = tok2->next()) + { + if (tok2->varId()) + { + variables.read(tok2->varId()); + if (tok2->next()->isAssignmentOp()) + variables.write(tok2->varId()); + } + } + } + } + + // Check usage of all variables in the current scope.. + Variables::VariableMap::const_iterator it; + for (it = variables.varUsage().begin(); it != variables.varUsage().end(); ++it) + { + const Variables::VariableUsage &usage = it->second; + const std::string &varname = usage._name->str(); + + // variable has been marked as unused so ignore it + if (usage._name->isUnused()) + continue; + + // skip things that are only partially implemented to prevent false positives + if (usage._type == Variables::pointerPointer || + usage._type == Variables::pointerArray || + usage._type == Variables::referenceArray) + continue; + + // variable has had memory allocated for it, but hasn't done + // anything with that memory other than, perhaps, freeing it + if (usage.unused() && !usage._modified && usage._allocateMemory) + allocatedButUnusedVariableError(usage._name, varname); + + // variable has not been written, read, or modified + else if (usage.unused() && !usage._modified) + unusedVariableError(usage._name, varname); + + // variable has not been written but has been modified + else if (usage._modified & !usage._write) + unassignedVariableError(usage._name, varname); + + // variable has been written but not read + else if (!usage._read && !usage._modified) + unreadVariableError(usage._name, varname); + + // variable has been read but not written + else if (!usage._write && !usage._allocateMemory) + unassignedVariableError(usage._name, varname); + } + } +} + +void CheckUnusedVar::unusedVariableError(const Token *tok, const std::string &varname) +{ + reportError(tok, Severity::style, "unusedVariable", "Unused variable: " + varname); +} + +void CheckUnusedVar::allocatedButUnusedVariableError(const Token *tok, const std::string &varname) +{ + reportError(tok, Severity::style, "unusedAllocatedMemory", "Variable '" + varname + "' is allocated memory that is never used"); +} + +void CheckUnusedVar::unreadVariableError(const Token *tok, const std::string &varname) +{ + reportError(tok, Severity::style, "unreadVariable", "Variable '" + varname + "' is assigned a value that is never used"); +} + +void CheckUnusedVar::unassignedVariableError(const Token *tok, const std::string &varname) +{ + reportError(tok, Severity::style, "unassignedVariable", "Variable '" + varname + "' is not assigned a value"); +} + +//--------------------------------------------------------------------------- +// Check that all struct members are used +//--------------------------------------------------------------------------- +void CheckUnusedVar::checkStructMemberUsage() +{ + if (!_settings->isEnabled("style")) + return; + + std::string structname; + for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next()) + { + if (tok->fileIndex() != 0) + continue; + + if (Token::Match(tok, "struct|union %type% {")) + { + structname.clear(); + if (Token::simpleMatch(tok->previous(), "extern")) + continue; + if ((!tok->previous() || Token::simpleMatch(tok->previous(), ";")) && Token::Match(tok->tokAt(2)->link(), ("} ; " + tok->strAt(1) + " %var% ;").c_str())) + continue; + + structname = tok->strAt(1); + + // Bail out if struct/union contain any functions + for (const Token *tok2 = tok->tokAt(2); tok2; tok2 = tok2->next()) + { + if (tok2->str() == "(") + { + structname.clear(); + break; + } + + if (tok2->str() == "}") + break; + } + + // bail out if struct is inherited + if (!structname.empty() && Token::findmatch(tok, (",|private|protected|public " + structname).c_str())) + structname.clear(); + + // Bail out if some data is casted to struct.. + const std::string s("( struct| " + tok->next()->str() + " * ) & %var% ["); + if (Token::findmatch(tok, s.c_str())) + structname.clear(); + + // Try to prevent false positives when struct members are not used directly. + if (Token::findmatch(tok, (structname + " *").c_str())) + structname.clear(); + else if (Token::findmatch(tok, (structname + " %type% *").c_str())) + structname = ""; + } + + if (tok->str() == "}") + structname.clear(); + + if (!structname.empty() && Token::Match(tok, "[{;]")) + { + // Declaring struct variable.. + std::string varname; + + // declaring a POD variable? + if (!tok->next()->isStandardType()) + continue; + + if (Token::Match(tok->next(), "%type% %var% [;[]")) + varname = tok->strAt(2); + else if (Token::Match(tok->next(), "%type% %type% %var% [;[]")) + varname = tok->strAt(3); + else if (Token::Match(tok->next(), "%type% * %var% [;[]")) + varname = tok->strAt(3); + else if (Token::Match(tok->next(), "%type% %type% * %var% [;[]")) + varname = tok->strAt(4); + else + continue; + + // Check if the struct variable is used anywhere in the file + const std::string usagePattern(". " + varname); + bool used = false; + for (const Token *tok2 = _tokenizer->tokens(); tok2; tok2 = tok2->next()) + { + if (Token::simpleMatch(tok2, usagePattern.c_str())) + { + used = true; + break; + } + } + + if (! used) + { + unusedStructMemberError(tok->next(), structname, varname); + } + } + } +} + +void CheckUnusedVar::unusedStructMemberError(const Token *tok, const std::string &structname, const std::string &varname) +{ + reportError(tok, Severity::style, "unusedStructMember", "struct or union member '" + structname + "::" + varname + "' is never used"); +} + diff --git a/lib/checkunusedvar.h b/lib/checkunusedvar.h new file mode 100644 index 000000000..c7fb76dad --- /dev/null +++ b/lib/checkunusedvar.h @@ -0,0 +1,113 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2011 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 . + */ + + +//--------------------------------------------------------------------------- +#ifndef CheckUnusedVarH +#define CheckUnusedVarH +//--------------------------------------------------------------------------- + +#include "check.h" +#include "settings.h" + +class Token; + +/// @addtogroup Checks +/// @{ + + +/** @brief Various small checks */ + +class CheckUnusedVar : public Check +{ +public: + /** @brief This constructor is used when registering the CheckClass */ + CheckUnusedVar() : Check(myName()) + { } + + /** @brief This constructor is used when running checks. */ + CheckUnusedVar(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) + : Check(myName(), tokenizer, settings, errorLogger) + { } + + /** @brief Run checks against the normal token list */ + void runChecks(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) + { + CheckUnusedVar checkUnusedVar(tokenizer, settings, errorLogger); + + // Coding style checks + checkUnusedVar.checkStructMemberUsage(); + checkUnusedVar.checkFunctionVariableUsage(); + } + + /** @brief Run checks against the simplified token list */ + void runSimplifiedChecks(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) + { + CheckUnusedVar checkUnusedVar(tokenizer, settings, errorLogger); + } + + /** @brief %Check for unused function variables */ + void checkFunctionVariableUsage(); + + /** @brief %Check that all struct members are used */ + void checkStructMemberUsage(); + + // Error messages.. + void unusedStructMemberError(const Token *tok, const std::string &structname, const std::string &varname); + void unusedVariableError(const Token *tok, const std::string &varname); + void allocatedButUnusedVariableError(const Token *tok, const std::string &varname); + void unreadVariableError(const Token *tok, const std::string &varname); + void unassignedVariableError(const Token *tok, const std::string &varname); + + void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) + { + CheckUnusedVar c(0, settings, errorLogger); + + // style/warning + c.unusedVariableError(0, "varname"); + c.allocatedButUnusedVariableError(0, "varname"); + c.unreadVariableError(0, "varname"); + c.unassignedVariableError(0, "varname"); + c.unusedStructMemberError(0, "structname", "variable"); + } + + std::string myName() const + { + return "UnusedVar"; + } + + std::string classInfo() const + { + return "UnusedVar checks\n" + + // style + "* unused variable\n" + "* allocated but unused variable\n" + "* unred variable\n" + "* unassigned variable\n" + "* unused struct member\n"; + } + +private: + /** @brief check if token is a record type without side effects */ + bool isRecordTypeWithoutSideEffects(const Token *tok); +}; +/// @} +//--------------------------------------------------------------------------- +#endif + diff --git a/lib/lib.pri b/lib/lib.pri index 95f0ebe22..831bf7d24 100644 --- a/lib/lib.pri +++ b/lib/lib.pri @@ -18,6 +18,7 @@ HEADERS += $${BASEPATH}check.h \ $${BASEPATH}checkstl.h \ $${BASEPATH}checkuninitvar.h \ $${BASEPATH}checkunusedfunctions.h \ + $${BASEPATH}checkunusedvar.h \ $${BASEPATH}cppcheck.h \ $${BASEPATH}errorlogger.h \ $${BASEPATH}executionpath.h \ @@ -45,6 +46,7 @@ SOURCES += $${BASEPATH}check64bit.cpp \ $${BASEPATH}checkstl.cpp \ $${BASEPATH}checkuninitvar.cpp \ $${BASEPATH}checkunusedfunctions.cpp \ + $${BASEPATH}checkunusedvar.cpp \ $${BASEPATH}cppcheck.cpp \ $${BASEPATH}errorlogger.cpp \ $${BASEPATH}executionpath.cpp \ diff --git a/test/testunusedvar.cpp b/test/testunusedvar.cpp index b5e29daee..d1e03bdbc 100644 --- a/test/testunusedvar.cpp +++ b/test/testunusedvar.cpp @@ -22,7 +22,7 @@ #include "testsuite.h" #include "tokenize.h" -#include "checkother.h" +#include "checkunusedvar.h" #include extern std::ostringstream errout; @@ -139,8 +139,8 @@ private: tokenizer.tokenize(istr, "test.cpp"); // Check for unused variables.. - CheckOther checkOther(&tokenizer, &settings, this); - checkOther.checkStructMemberUsage(); + CheckUnusedVar checkUnusedVar(&tokenizer, &settings, this); + checkUnusedVar.checkStructMemberUsage(); } void structmember1() @@ -376,8 +376,8 @@ private: tokenizer.tokenize(istr, "test.cpp"); // Check for unused variables.. - CheckOther checkOther(&tokenizer, &settings, this); - checkOther.functionVariableUsage(); + CheckUnusedVar checkUnusedVar(&tokenizer, &settings, this); + checkUnusedVar.checkFunctionVariableUsage(); } void localvar1()