1733 lines
69 KiB
C++
1733 lines
69 KiB
C++
/*
|
|
* Cppcheck - A tool for static C/C++ code analysis
|
|
* Copyright (C) 2007-2023 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 "checkunusedvar.h"
|
|
|
|
#include "astutils.h"
|
|
#include "errortypes.h"
|
|
#include "fwdanalysis.h"
|
|
#include "library.h"
|
|
#include "preprocessor.h"
|
|
#include "settings.h"
|
|
#include "symboldatabase.h"
|
|
#include "token.h"
|
|
#include "tokenize.h"
|
|
#include "tokenlist.h"
|
|
#include "utils.h"
|
|
#include "valueflow.h"
|
|
|
|
#include <algorithm>
|
|
#include <list>
|
|
#include <set>
|
|
#include <utility>
|
|
#include <vector>
|
|
//---------------------------------------------------------------------------
|
|
|
|
// Register this check class (by creating a static instance of it)
|
|
namespace {
|
|
CheckUnusedVar instance;
|
|
}
|
|
|
|
static const struct CWE CWE563(563U); // Assignment to Variable without Use ('Unused Variable')
|
|
static const struct CWE CWE665(665U); // Improper Initialization
|
|
|
|
/** Is scope a raii class scope */
|
|
static bool isRaiiClassScope(const Scope *classScope)
|
|
{
|
|
return classScope && classScope->getDestructor() != nullptr;
|
|
}
|
|
|
|
/** Is ValueType a raii class? */
|
|
static bool isRaiiClass(const ValueType *valueType, bool cpp, bool defaultReturn = true)
|
|
{
|
|
if (!cpp)
|
|
return false;
|
|
|
|
if (!valueType)
|
|
return defaultReturn;
|
|
|
|
if ((valueType->smartPointerType && isRaiiClassScope(valueType->smartPointerType->classScope)) || (!valueType->smartPointerType && valueType->type == ValueType::Type::SMART_POINTER))
|
|
return true;
|
|
|
|
switch (valueType->type) {
|
|
case ValueType::Type::UNKNOWN_TYPE:
|
|
case ValueType::Type::NONSTD:
|
|
return defaultReturn;
|
|
|
|
case ValueType::Type::RECORD:
|
|
if (isRaiiClassScope(valueType->typeScope))
|
|
return true;
|
|
return defaultReturn;
|
|
|
|
case ValueType::Type::POD:
|
|
case ValueType::Type::SMART_POINTER:
|
|
case ValueType::Type::CONTAINER:
|
|
case ValueType::Type::ITERATOR:
|
|
case ValueType::Type::VOID:
|
|
case ValueType::Type::BOOL:
|
|
case ValueType::Type::CHAR:
|
|
case ValueType::Type::SHORT:
|
|
case ValueType::Type::WCHAR_T:
|
|
case ValueType::Type::INT:
|
|
case ValueType::Type::LONG:
|
|
case ValueType::Type::LONGLONG:
|
|
case ValueType::Type::UNKNOWN_INT:
|
|
case ValueType::Type::FLOAT:
|
|
case ValueType::Type::DOUBLE:
|
|
case ValueType::Type::LONGDOUBLE:
|
|
return false;
|
|
}
|
|
|
|
return defaultReturn;
|
|
}
|
|
|
|
/**
|
|
* @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, none };
|
|
|
|
/** Store information about variable usage */
|
|
class VariableUsage {
|
|
public:
|
|
explicit VariableUsage(const Variable *var = nullptr,
|
|
VariableType type = standard,
|
|
bool read = false,
|
|
bool write = false,
|
|
bool modified = false,
|
|
bool allocateMemory = false) :
|
|
_var(var),
|
|
_lastAccess(var ? var->nameToken() : nullptr),
|
|
mType(type),
|
|
_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 && !_write);
|
|
}
|
|
|
|
std::set<nonneg int> _aliases;
|
|
std::set<const Scope*> _assignments;
|
|
|
|
const Variable* _var;
|
|
const Token* _lastAccess;
|
|
VariableType mType;
|
|
bool _read;
|
|
bool _write;
|
|
bool _modified; // read/modify/write
|
|
bool _allocateMemory;
|
|
};
|
|
|
|
void clear() {
|
|
mVarUsage.clear();
|
|
}
|
|
const std::map<nonneg int, VariableUsage> &varUsage() const {
|
|
return mVarUsage;
|
|
}
|
|
void addVar(const Variable *var, VariableType type, bool write_);
|
|
void allocateMemory(nonneg int varid, const Token* tok);
|
|
void read(nonneg int varid, const Token* tok);
|
|
void readAliases(nonneg int varid, const Token* tok);
|
|
void readAll(nonneg int varid, const Token* tok);
|
|
void write(nonneg int varid, const Token* tok);
|
|
void writeAliases(nonneg int varid, const Token* tok);
|
|
void writeAll(nonneg int varid, const Token* tok);
|
|
void use(nonneg int varid, const Token* tok);
|
|
void modified(nonneg int varid, const Token* tok);
|
|
VariableUsage *find(nonneg int varid);
|
|
void alias(nonneg int varid1, nonneg int varid2, bool replace);
|
|
void erase(nonneg int varid) {
|
|
mVarUsage.erase(varid);
|
|
}
|
|
void eraseAliases(nonneg int varid);
|
|
void eraseAll(nonneg int varid);
|
|
void clearAliases(nonneg int varid);
|
|
|
|
private:
|
|
|
|
std::map<nonneg int, VariableUsage> mVarUsage;
|
|
};
|
|
|
|
|
|
/**
|
|
* 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(nonneg int varid1, nonneg int varid2, bool replace)
|
|
{
|
|
VariableUsage *var1 = find(varid1);
|
|
VariableUsage *var2 = find(varid2);
|
|
|
|
if (!var1 || !var2)
|
|
return;
|
|
|
|
// alias to self
|
|
if (varid1 == varid2) {
|
|
var1->use();
|
|
return;
|
|
}
|
|
|
|
if (replace) {
|
|
// remove var1 from all aliases
|
|
for (std::set<nonneg int>::const_iterator i = var1->_aliases.cbegin(); i != var1->_aliases.cend(); ++i) {
|
|
VariableUsage *temp = find(*i);
|
|
|
|
if (temp)
|
|
temp->_aliases.erase(var1->_var->declarationId());
|
|
}
|
|
|
|
// remove all aliases from var1
|
|
var1->_aliases.clear();
|
|
}
|
|
|
|
// var1 gets all var2s aliases
|
|
for (std::set<nonneg int>::const_iterator i = var2->_aliases.cbegin(); i != var2->_aliases.cend(); ++i) {
|
|
if (*i != varid1)
|
|
var1->_aliases.insert(*i);
|
|
}
|
|
|
|
// var2 is an alias of var1
|
|
var2->_aliases.insert(varid1);
|
|
var1->_aliases.insert(varid2);
|
|
|
|
if (var2->mType == Variables::pointer) {
|
|
var2->_read = true;
|
|
}
|
|
}
|
|
|
|
void Variables::clearAliases(nonneg int varid)
|
|
{
|
|
VariableUsage *usage = find(varid);
|
|
|
|
if (usage) {
|
|
// remove usage from all aliases
|
|
std::set<nonneg int>::const_iterator i;
|
|
|
|
for (i = usage->_aliases.cbegin(); i != usage->_aliases.cend(); ++i) {
|
|
VariableUsage *temp = find(*i);
|
|
|
|
if (temp)
|
|
temp->_aliases.erase(usage->_var->declarationId());
|
|
}
|
|
|
|
// remove all aliases from usage
|
|
usage->_aliases.clear();
|
|
}
|
|
}
|
|
|
|
void Variables::eraseAliases(nonneg int varid)
|
|
{
|
|
VariableUsage *usage = find(varid);
|
|
|
|
if (usage) {
|
|
for (std::set<nonneg int>::const_iterator aliases = usage->_aliases.cbegin(); aliases != usage->_aliases.cend(); ++aliases)
|
|
erase(*aliases);
|
|
}
|
|
}
|
|
|
|
void Variables::eraseAll(nonneg int varid)
|
|
{
|
|
eraseAliases(varid);
|
|
erase(varid);
|
|
}
|
|
|
|
void Variables::addVar(const Variable *var,
|
|
VariableType type,
|
|
bool write_)
|
|
{
|
|
if (var->declarationId() > 0) {
|
|
mVarUsage.insert(std::make_pair(var->declarationId(), VariableUsage(var, type, false, write_, false)));
|
|
}
|
|
}
|
|
|
|
void Variables::allocateMemory(nonneg int varid, const Token* tok)
|
|
{
|
|
VariableUsage *usage = find(varid);
|
|
|
|
if (usage) {
|
|
usage->_allocateMemory = true;
|
|
usage->_lastAccess = tok;
|
|
}
|
|
}
|
|
|
|
void Variables::read(nonneg int varid, const Token* tok)
|
|
{
|
|
VariableUsage *usage = find(varid);
|
|
|
|
if (usage) {
|
|
usage->_read = true;
|
|
if (tok)
|
|
usage->_lastAccess = tok;
|
|
}
|
|
}
|
|
|
|
void Variables::readAliases(nonneg int varid, const Token* tok)
|
|
{
|
|
VariableUsage *usage = find(varid);
|
|
|
|
if (usage) {
|
|
for (nonneg int const aliases : usage->_aliases) {
|
|
VariableUsage *aliased = find(aliases);
|
|
|
|
if (aliased) {
|
|
aliased->_read = true;
|
|
aliased->_lastAccess = tok;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Variables::readAll(nonneg int varid, const Token* tok)
|
|
{
|
|
read(varid, tok);
|
|
readAliases(varid, tok);
|
|
}
|
|
|
|
void Variables::write(nonneg int varid, const Token* tok)
|
|
{
|
|
VariableUsage *usage = find(varid);
|
|
|
|
if (usage) {
|
|
usage->_write = true;
|
|
if (!usage->_var->isStatic() && !Token::simpleMatch(tok->next(), "= 0 ;"))
|
|
usage->_read = false;
|
|
usage->_lastAccess = tok;
|
|
}
|
|
}
|
|
|
|
void Variables::writeAliases(nonneg int varid, const Token* tok)
|
|
{
|
|
VariableUsage *usage = find(varid);
|
|
|
|
if (usage) {
|
|
for (std::set<nonneg int>::const_iterator aliases = usage->_aliases.cbegin(); aliases != usage->_aliases.cend(); ++aliases) {
|
|
VariableUsage *aliased = find(*aliases);
|
|
|
|
if (aliased) {
|
|
aliased->_write = true;
|
|
aliased->_lastAccess = tok;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Variables::writeAll(nonneg int varid, const Token* tok)
|
|
{
|
|
write(varid, tok);
|
|
writeAliases(varid, tok);
|
|
}
|
|
|
|
void Variables::use(nonneg int varid, const Token* tok)
|
|
{
|
|
VariableUsage *usage = find(varid);
|
|
|
|
if (usage) {
|
|
usage->use();
|
|
usage->_lastAccess = tok;
|
|
|
|
for (std::set<nonneg int>::const_iterator aliases = usage->_aliases.cbegin(); aliases != usage->_aliases.cend(); ++aliases) {
|
|
VariableUsage *aliased = find(*aliases);
|
|
|
|
if (aliased) {
|
|
aliased->use();
|
|
aliased->_lastAccess = tok;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Variables::modified(nonneg int varid, const Token* tok)
|
|
{
|
|
VariableUsage *usage = find(varid);
|
|
|
|
if (usage) {
|
|
if (!usage->_var->isStatic())
|
|
usage->_read = false;
|
|
usage->_modified = true;
|
|
usage->_lastAccess = tok;
|
|
|
|
for (std::set<nonneg int>::const_iterator aliases = usage->_aliases.cbegin(); aliases != usage->_aliases.cend(); ++aliases) {
|
|
VariableUsage *aliased = find(*aliases);
|
|
|
|
if (aliased) {
|
|
aliased->_modified = true;
|
|
aliased->_lastAccess = tok;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Variables::VariableUsage *Variables::find(nonneg int varid)
|
|
{
|
|
if (varid) {
|
|
const std::map<nonneg int, VariableUsage>::iterator i = mVarUsage.find(varid);
|
|
if (i != mVarUsage.end())
|
|
return &i->second;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static const Token* doAssignment(Variables &variables, const Token *tok, bool dereference, const Scope *scope)
|
|
{
|
|
// a = a + b;
|
|
if (Token::Match(tok, "%var% = %var% !!;")) {
|
|
const Token* rhsVarTok = tok->tokAt(2);
|
|
if (tok->varId() == rhsVarTok->varId()) {
|
|
return rhsVarTok;
|
|
}
|
|
}
|
|
|
|
if (Token::Match(tok, "%var% %assign%") && tok->strAt(1) != "=")
|
|
return tok->next();
|
|
|
|
const Token* const tokOld = tok;
|
|
|
|
// check for aliased variable
|
|
const nonneg int varid1 = tok->varId();
|
|
Variables::VariableUsage *var1 = variables.find(varid1);
|
|
|
|
if (var1) {
|
|
// jump behind '='
|
|
tok = tok->next();
|
|
while (!tok->isAssignmentOp()) {
|
|
if (tok->varId())
|
|
variables.read(tok->varId(), tok);
|
|
tok = tok->next();
|
|
}
|
|
tok = tok->next();
|
|
|
|
if (Token::Match(tok, "( const| struct|union| %type% * ) ( ("))
|
|
tok = tok->link()->next();
|
|
|
|
if (Token::Match(tok, "( [(<] const| struct|union| %type% *| [>)]"))
|
|
tok = tok->next();
|
|
|
|
if (Token::Match(tok, "(| &| %name%") ||
|
|
(Token::Match(tok->next(), "< const| struct|union| %type% *| > ( &| %name%"))) {
|
|
bool addressOf = false;
|
|
|
|
if (Token::Match(tok, "%var% ."))
|
|
variables.use(tok->varId(), tok); // use = read + write
|
|
|
|
// check for C style cast
|
|
if (tok->str() == "(") {
|
|
tok = tok->next();
|
|
if (tok->str() == "const")
|
|
tok = tok->next();
|
|
|
|
if (Token::Match(tok, "struct|union"))
|
|
tok = tok->next();
|
|
|
|
while ((tok->isName() && tok->varId() == 0) || (tok->str() == "*") || (tok->str() == ")"))
|
|
tok = tok->next();
|
|
|
|
if (tok->str() == "&") {
|
|
addressOf = true;
|
|
tok = tok->next();
|
|
} else if (tok->str() == "(") {
|
|
tok = tok->next();
|
|
if (tok->str() == "&") {
|
|
addressOf = true;
|
|
tok = tok->next();
|
|
}
|
|
} else if (Token::Match(tok, "%cop% %var%")) {
|
|
variables.read(tok->next()->varId(), tok);
|
|
}
|
|
}
|
|
|
|
// check for C++ style cast
|
|
else if (tok->str().find("cast") != std::string::npos &&
|
|
tok->strAt(1) == "<") {
|
|
tok = tok->tokAt(2);
|
|
if (tok->str() == "const")
|
|
tok = tok->next();
|
|
|
|
if (Token::Match(tok, "struct|union"))
|
|
tok = tok->next();
|
|
|
|
tok = tok->next();
|
|
if (tok->str() == "*")
|
|
tok = tok->next();
|
|
|
|
tok = tok->tokAt(2);
|
|
if (!tok)
|
|
return tokOld;
|
|
if (tok->str() == "&") {
|
|
addressOf = true;
|
|
tok = tok->next();
|
|
}
|
|
}
|
|
|
|
// no cast, no ?
|
|
else if (!Token::Match(tok, "%name% ?")) {
|
|
if (tok->str() == "&") {
|
|
addressOf = true;
|
|
tok = tok->next();
|
|
} else if (tok->str() == "new")
|
|
return tokOld;
|
|
}
|
|
|
|
// check if variable is local
|
|
const nonneg int varid2 = tok->varId();
|
|
const Variables::VariableUsage* var2 = variables.find(varid2);
|
|
|
|
if (var2) { // local variable (alias or read it)
|
|
if (var1->mType == Variables::pointer || var1->mType == Variables::pointerArray) {
|
|
if (dereference)
|
|
variables.read(varid2, tok);
|
|
else {
|
|
if (addressOf ||
|
|
var2->mType == Variables::array ||
|
|
var2->mType == Variables::pointer) {
|
|
bool replace = true;
|
|
|
|
// pointerArray => don't replace
|
|
if (var1->mType == Variables::pointerArray)
|
|
replace = false;
|
|
|
|
// check if variable declared in same scope
|
|
else if (scope == var1->_var->scope())
|
|
replace = true;
|
|
|
|
// not in same scope as declaration
|
|
else {
|
|
// no other assignment in this scope
|
|
if (var1->_assignments.find(scope) == var1->_assignments.end() ||
|
|
scope->type == Scope::eSwitch) {
|
|
// nothing to replace
|
|
// cppcheck-suppress duplicateBranch - remove when TODO below is address
|
|
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, merge them otherwise
|
|
replace = (var1->_assignments.size() == 1);
|
|
}
|
|
}
|
|
|
|
variables.alias(varid1, varid2, replace);
|
|
} else if (tok->strAt(1) == "?") {
|
|
if (var2->mType == Variables::reference)
|
|
variables.readAliases(varid2, tok);
|
|
else
|
|
variables.read(varid2, tok);
|
|
} else {
|
|
variables.readAll(varid2, tok);
|
|
}
|
|
}
|
|
} else if (var1->mType == Variables::reference) {
|
|
variables.alias(varid1, varid2, true);
|
|
} else if (var1->mType == Variables::standard && addressOf) {
|
|
variables.alias(varid1, varid2, true);
|
|
} else {
|
|
if ((var2->mType == Variables::pointer || var2->mType == Variables::pointerArray) && tok->strAt(1) == "[")
|
|
variables.readAliases(varid2, tok);
|
|
|
|
variables.read(varid2, tok);
|
|
}
|
|
} else { // not a local variable (or an unsupported local variable)
|
|
if (var1->mType == Variables::pointer && !dereference) {
|
|
// check if variable declaration is in this scope
|
|
if (var1->_var->scope() == scope) {
|
|
// If variable is used in RHS then "use" variable
|
|
for (const Token *rhs = tok; rhs && rhs->str() != ";"; rhs = rhs->next()) {
|
|
if (rhs->varId() == varid1) {
|
|
variables.use(varid1, tok);
|
|
break;
|
|
}
|
|
}
|
|
variables.clearAliases(varid1);
|
|
} else {
|
|
// no other assignment in this scope
|
|
if (var1->_assignments.find(scope) == 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else
|
|
tok = tokOld;
|
|
|
|
var1->_assignments.insert(scope);
|
|
}
|
|
|
|
// check for alias to struct member
|
|
// char c[10]; a.b = c;
|
|
else if (Token::Match(tok->tokAt(-2), "%name% .")) {
|
|
const Token *rhsVarTok = tok->tokAt(2);
|
|
if (rhsVarTok && rhsVarTok->varId()) {
|
|
const nonneg int varid2 = rhsVarTok->varId();
|
|
const Variables::VariableUsage *var2 = variables.find(varid2);
|
|
|
|
// struct member aliased to local variable
|
|
if (var2 && (var2->mType == Variables::array ||
|
|
var2->mType == Variables::pointer)) {
|
|
// erase aliased variable and all variables that alias it
|
|
// to prevent false positives
|
|
variables.eraseAll(varid2);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Possible pointer alias
|
|
else if (Token::Match(tok, "%name% = %name% ;")) {
|
|
const nonneg int varid2 = tok->tokAt(2)->varId();
|
|
const Variables::VariableUsage *var2 = variables.find(varid2);
|
|
if (var2 && (var2->mType == Variables::array ||
|
|
var2->mType == Variables::pointer)) {
|
|
variables.use(varid2,tok);
|
|
}
|
|
}
|
|
|
|
return tok;
|
|
}
|
|
|
|
static bool isPartOfClassStructUnion(const Token* tok)
|
|
{
|
|
for (; tok; tok = tok->previous()) {
|
|
if (tok->str() == "}" || tok->str() == ")")
|
|
tok = tok->link();
|
|
else if (tok->str() == "(")
|
|
return (false);
|
|
else if (tok->str() == "{") {
|
|
return (tok->strAt(-1) == "struct" || tok->strAt(-2) == "struct" || tok->strAt(-1) == "class" || tok->strAt(-2) == "class" || tok->strAt(-1) == "union" || tok->strAt(-2) == "union");
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool isVarDecl(const Token *tok)
|
|
{
|
|
return tok && tok->variable() && tok->variable()->nameToken() == tok;
|
|
}
|
|
|
|
// Skip [ .. ]
|
|
static const Token * skipBrackets(const Token *tok)
|
|
{
|
|
while (tok && tok->str() == "[")
|
|
tok = tok->link()->next();
|
|
return tok;
|
|
}
|
|
|
|
|
|
// Skip [ .. ] . x
|
|
static const Token * skipBracketsAndMembers(const Token *tok)
|
|
{
|
|
while (tok) {
|
|
if (tok->str() == "[")
|
|
tok = tok->link()->next();
|
|
else if (Token::Match(tok, ". %name%"))
|
|
tok = tok->tokAt(2);
|
|
else
|
|
break;
|
|
}
|
|
return tok;
|
|
}
|
|
|
|
static void useFunctionArgs(const Token *tok, Variables& variables)
|
|
{
|
|
// TODO: Match function args to see if they are const or not. Assume that const data is not written.
|
|
if (!tok)
|
|
return;
|
|
if (tok->str() == ",") {
|
|
useFunctionArgs(tok->astOperand1(), variables);
|
|
useFunctionArgs(tok->astOperand2(), variables);
|
|
} else if (Token::Match(tok, "[+:]") && (!tok->valueType() || tok->valueType()->pointer)) {
|
|
useFunctionArgs(tok->astOperand1(), variables);
|
|
useFunctionArgs(tok->astOperand2(), variables);
|
|
} else if (tok->variable() && tok->variable()->isArray()) {
|
|
variables.use(tok->varId(), tok);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Usage of function variables
|
|
//---------------------------------------------------------------------------
|
|
void CheckUnusedVar::checkFunctionVariableUsage_iterateScopes(const Scope* const scope, Variables& variables)
|
|
{
|
|
// Find declarations if the scope is executable..
|
|
if (scope->isExecutable()) {
|
|
// Find declarations
|
|
for (std::list<Variable>::const_iterator i = scope->varlist.cbegin(); i != scope->varlist.cend(); ++i) {
|
|
if (i->isThrow() || i->isExtern())
|
|
continue;
|
|
Variables::VariableType type = Variables::none;
|
|
if (i->isArray() && (i->nameToken()->previous()->str() == "*" || i->nameToken()->strAt(-2) == "*"))
|
|
type = Variables::pointerArray;
|
|
else if (i->isArray() && i->nameToken()->previous()->str() == "&")
|
|
type = Variables::referenceArray;
|
|
else if (i->isArray())
|
|
type = (i->dimensions().size() == 1U) ? Variables::array : Variables::pointerArray;
|
|
else if (i->isReference() && !(i->valueType() && i->valueType()->type == ValueType::UNKNOWN_TYPE && Token::simpleMatch(i->typeStartToken(), "auto")))
|
|
type = Variables::reference;
|
|
else if (i->nameToken()->previous()->str() == "*" && i->nameToken()->strAt(-2) == "*")
|
|
type = Variables::pointerPointer;
|
|
else if (i->isPointerToArray())
|
|
type = Variables::pointerPointer;
|
|
else if (i->isPointer())
|
|
type = Variables::pointer;
|
|
else if (mTokenizer->isC() ||
|
|
i->typeEndToken()->isStandardType() ||
|
|
isRecordTypeWithoutSideEffects(i->type()) ||
|
|
mSettings->library.detectContainer(i->typeStartToken()) ||
|
|
i->isStlType())
|
|
type = Variables::standard;
|
|
if (type == Variables::none || isPartOfClassStructUnion(i->typeStartToken()))
|
|
continue;
|
|
const Token* defValTok = i->nameToken()->next();
|
|
if (Token::Match(i->nameToken()->previous(), "* %var% ) (")) // function pointer. Jump behind parameter list.
|
|
defValTok = defValTok->linkAt(1)->next();
|
|
for (; defValTok; defValTok = defValTok->next()) {
|
|
if (defValTok->str() == "[")
|
|
defValTok = defValTok->link();
|
|
else if (defValTok->str() == "(" || defValTok->str() == "{" || defValTok->str() == "=" || defValTok->str() == ":") {
|
|
variables.addVar(&*i, type, true);
|
|
break;
|
|
} else if (defValTok->str() == ";" || defValTok->str() == "," || defValTok->str() == ")") {
|
|
variables.addVar(&*i, type, i->isStatic() && i->scope()->type != Scope::eFunction);
|
|
break;
|
|
}
|
|
}
|
|
if (i->isArray() && i->isClass() && // Array of class/struct members. Initialized by ctor except for std::array
|
|
!(i->isStlType() && i->valueType() && i->valueType()->containerTypeToken && i->valueType()->containerTypeToken->isStandardType()))
|
|
variables.write(i->declarationId(), i->nameToken());
|
|
if (i->isArray() && Token::Match(i->nameToken(), "%name% [ %var% ]")) // Array index variable read.
|
|
variables.read(i->nameToken()->tokAt(2)->varId(), i->nameToken());
|
|
|
|
if (defValTok && defValTok->next()) {
|
|
// simple assignment "var = 123"
|
|
if (defValTok->str() == "=" && defValTok->next()->str() != "{") {
|
|
doAssignment(variables, i->nameToken(), false, scope);
|
|
} else {
|
|
// could be "var = {...}" OR "var{...}" (since C++11)
|
|
const Token* tokBraceStart = nullptr;
|
|
if (Token::simpleMatch(defValTok, "= {")) {
|
|
// "var = {...}"
|
|
tokBraceStart = defValTok->next();
|
|
} else if (defValTok->str() == "{") {
|
|
// "var{...}"
|
|
tokBraceStart = defValTok;
|
|
}
|
|
if (tokBraceStart) {
|
|
for (const Token* tok = tokBraceStart->next(); tok && tok != tokBraceStart->link(); tok = tok->next()) {
|
|
if (tok->varId()) {
|
|
// Variables used to initialize the array read.
|
|
variables.read(tok->varId(), i->nameToken());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check variable usage
|
|
const Token *tok;
|
|
if (scope->type == Scope::eFunction)
|
|
tok = scope->bodyStart->next();
|
|
else
|
|
tok = scope->classDef->next();
|
|
for (; tok && tok != scope->bodyEnd; tok = tok->next()) {
|
|
if (tok->str() == "{" && tok != scope->bodyStart && !tok->previous()->varId()) {
|
|
if (std::any_of(scope->nestedList.cbegin(), scope->nestedList.cend(), [&](const Scope* s) {
|
|
return s->bodyStart == tok;
|
|
})) {
|
|
checkFunctionVariableUsage_iterateScopes(tok->scope(), variables); // Scan child scope
|
|
tok = tok->link();
|
|
}
|
|
if (!tok)
|
|
break;
|
|
}
|
|
|
|
if (Token::Match(tok, "asm ( %str% )")) {
|
|
variables.clear();
|
|
break;
|
|
}
|
|
|
|
// templates
|
|
if (tok->isName() && endsWith(tok->str(), '>')) {
|
|
// TODO: This is a quick fix to handle when constants are used
|
|
// as template parameters. Try to handle this better, perhaps
|
|
// only remove constants.
|
|
variables.clear();
|
|
}
|
|
|
|
else if (Token::Match(tok->previous(), "[;{}]")) {
|
|
for (const Token* tok2 = tok->next(); tok2; tok2 = tok2->next()) {
|
|
if (tok2->varId()) {
|
|
// Is this a variable declaration?
|
|
const Variable *var = tok2->variable();
|
|
if (!var || var->nameToken() != tok2)
|
|
continue;
|
|
|
|
// Mark template parameters used in declaration as use..
|
|
if (tok2->strAt(-1) == ">") {
|
|
for (const Token *tok3 = tok; tok3 != tok2; tok3 = tok3->next()) {
|
|
if (tok3->varId())
|
|
variables.use(tok3->varId(), tok3);
|
|
}
|
|
}
|
|
|
|
// Skip variable declaration..
|
|
tok = tok2->next();
|
|
if (Token::Match(tok, "( %name% )")) // Simple initialization through copy ctor
|
|
tok = tok->next();
|
|
else if (Token::Match(tok, "= %var% ;")) { // Simple initialization
|
|
tok = tok->next();
|
|
if (!var->isReference())
|
|
variables.read(tok->varId(), tok);
|
|
} else if (tok->str() == "[" && Token::simpleMatch(skipBrackets(tok),"= {")) {
|
|
const Token * const rhs1 = skipBrackets(tok)->next();
|
|
for (const Token *rhs = rhs1->link(); rhs != rhs1; rhs = rhs->previous()) {
|
|
if (rhs->varId())
|
|
variables.readAll(rhs->varId(), rhs);
|
|
}
|
|
} else if (var->typeEndToken()->str() == ">") // Be careful with types like std::vector
|
|
tok = tok->previous();
|
|
break;
|
|
} else if (Token::Match(tok2, "[;({=]"))
|
|
break;
|
|
}
|
|
}
|
|
// Freeing memory (not considered "using" the pointer if it was also allocated in this function)
|
|
if ((Token::Match(tok, "%name% ( %var% )") && mSettings->library.getDeallocFuncInfo(tok)) ||
|
|
(mTokenizer->isCPP() && (Token::Match(tok, "delete %var% ;") || Token::Match(tok, "delete [ ] %var% ;")))) {
|
|
nonneg int varid = 0;
|
|
if (tok->str() != "delete") {
|
|
const Token *varTok = tok->tokAt(2);
|
|
varid = varTok->varId();
|
|
tok = varTok->next();
|
|
} else if (tok->strAt(1) == "[") {
|
|
const Token *varTok = tok->tokAt(3);
|
|
varid = varTok->varId();
|
|
tok = varTok;
|
|
} else {
|
|
varid = tok->next()->varId();
|
|
tok = tok->next();
|
|
}
|
|
|
|
const Variables::VariableUsage *const var = variables.find(varid);
|
|
if (var) {
|
|
if (!var->_aliases.empty())
|
|
variables.use(varid, tok);
|
|
else if (!var->_allocateMemory)
|
|
variables.readAll(varid, tok);
|
|
}
|
|
}
|
|
|
|
else if (Token::Match(tok, "return|throw")) {
|
|
for (const Token *tok2 = tok->next(); tok2; tok2 = tok2->next()) {
|
|
if (tok2->varId())
|
|
variables.readAll(tok2->varId(), tok);
|
|
else if (tok2->str() == ";")
|
|
break;
|
|
}
|
|
}
|
|
|
|
// assignment
|
|
else if (Token::Match(tok, "*| ++|--| %name% ++|--| %assign%") ||
|
|
Token::Match(tok, "*| ( const| %type% *| ) %name% %assign%")) {
|
|
bool dereference = false;
|
|
bool pre = false;
|
|
bool post = false;
|
|
|
|
if (tok->str() == "*") {
|
|
dereference = true;
|
|
tok = tok->next();
|
|
}
|
|
|
|
if (Token::Match(tok, "( const| %type% *| ) %name% %assign%"))
|
|
tok = tok->link()->next();
|
|
|
|
else if (tok->str() == "(")
|
|
tok = tok->next();
|
|
|
|
if (tok->tokType() == Token::eIncDecOp) {
|
|
pre = true;
|
|
tok = tok->next();
|
|
}
|
|
|
|
if (tok->next()->tokType() == Token::eIncDecOp)
|
|
post = true;
|
|
|
|
const nonneg int varid1 = tok->varId();
|
|
const Token * const start = tok;
|
|
|
|
// assignment in while head..
|
|
bool inwhile = false;
|
|
{
|
|
const Token *parent = tok->astParent();
|
|
while (parent) {
|
|
if (Token::simpleMatch(parent->previous(), "while (")) {
|
|
inwhile = true;
|
|
break;
|
|
}
|
|
parent = parent->astParent();
|
|
}
|
|
}
|
|
|
|
tok = doAssignment(variables, tok, dereference, scope);
|
|
|
|
if (tok && tok->isAssignmentOp() && tok->str() != "=") {
|
|
variables.use(varid1, tok);
|
|
if (Token::Match(tok, "%assign% %name%")) {
|
|
tok = tok->next();
|
|
variables.read(tok->varId(), tok);
|
|
}
|
|
}
|
|
|
|
if (pre || post)
|
|
variables.use(varid1, tok);
|
|
|
|
if (dereference) {
|
|
const Variables::VariableUsage *const var = variables.find(varid1);
|
|
if (var && var->mType == Variables::array)
|
|
variables.write(varid1, tok);
|
|
variables.writeAliases(varid1, tok);
|
|
variables.read(varid1, tok);
|
|
} else {
|
|
const Variables::VariableUsage *const var = variables.find(varid1);
|
|
if (var && (inwhile || start->strAt(-1) == ",")) {
|
|
variables.use(varid1, tok);
|
|
} else if (var && var->mType == Variables::reference) {
|
|
variables.writeAliases(varid1, tok);
|
|
variables.read(varid1, tok);
|
|
}
|
|
// Consider allocating memory separately because allocating/freeing alone does not constitute using the variable
|
|
else if (var && var->mType == Variables::pointer &&
|
|
Token::Match(start, "%name% =") &&
|
|
findAllocFuncCallToken(start->next()->astOperand2(), mSettings->library)) {
|
|
|
|
const Token *allocFuncCallToken = findAllocFuncCallToken(start->next()->astOperand2(), mSettings->library);
|
|
const Library::AllocFunc *allocFunc = mSettings->library.getAllocFuncInfo(allocFuncCallToken);
|
|
|
|
bool allocateMemory = !allocFunc || Library::ismemory(allocFunc->groupId);
|
|
|
|
if (allocFuncCallToken->str() == "new") {
|
|
const Token *type = allocFuncCallToken->next();
|
|
|
|
// skip nothrow
|
|
if (mTokenizer->isCPP() && (Token::simpleMatch(type, "( nothrow )") ||
|
|
Token::simpleMatch(type, "( std :: nothrow )")))
|
|
type = type->link()->next();
|
|
|
|
// is it a user defined type?
|
|
if (!type->isStandardType()) {
|
|
const Variable *variable = start->variable();
|
|
if (!variable || !isRecordTypeWithoutSideEffects(variable->type()))
|
|
allocateMemory = false;
|
|
}
|
|
}
|
|
|
|
if (allocateMemory)
|
|
variables.allocateMemory(varid1, tok);
|
|
else
|
|
variables.write(varid1, tok);
|
|
} else if (varid1 && Token::Match(tok, "%varid% .", varid1)) {
|
|
variables.read(varid1, tok);
|
|
variables.write(varid1, start);
|
|
} else {
|
|
variables.write(varid1, tok);
|
|
}
|
|
}
|
|
|
|
const Variables::VariableUsage * const var2 = variables.find(tok->varId());
|
|
if (var2) {
|
|
if (var2->mType == Variables::reference) {
|
|
variables.writeAliases(tok->varId(), tok);
|
|
variables.read(tok->varId(), tok);
|
|
} else if (tok->varId() != varid1 && Token::Match(tok, "%name% .|["))
|
|
variables.read(tok->varId(), tok);
|
|
else if (tok->varId() != varid1 &&
|
|
var2->mType == Variables::standard &&
|
|
tok->strAt(-1) != "&")
|
|
variables.use(tok->varId(), tok);
|
|
}
|
|
|
|
const Token * const equal = skipBracketsAndMembers(tok->next());
|
|
|
|
// checked for chained assignments
|
|
if (tok != start && equal && equal->str() == "=") {
|
|
const nonneg int varId = tok->varId();
|
|
const Variables::VariableUsage * const var = variables.find(varId);
|
|
|
|
if (var && var->mType != Variables::reference) {
|
|
variables.read(varId,tok);
|
|
}
|
|
|
|
tok = tok->previous();
|
|
}
|
|
}
|
|
|
|
// assignment
|
|
else if ((Token::Match(tok, "%name% [") && Token::simpleMatch(skipBracketsAndMembers(tok->next()), "=")) ||
|
|
(Token::simpleMatch(tok, "* (") && Token::simpleMatch(tok->next()->link(), ") ="))) {
|
|
const Token *eq = tok;
|
|
while (eq && !eq->isAssignmentOp())
|
|
eq = eq->astParent();
|
|
|
|
const bool deref = eq && eq->astOperand1() && eq->astOperand1()->valueType() && eq->astOperand1()->valueType()->pointer == 0U;
|
|
|
|
if (tok->str() == "*") {
|
|
tok = tok->tokAt(2);
|
|
if (tok->str() == "(")
|
|
tok = tok->link()->next();
|
|
}
|
|
|
|
const nonneg 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->mType == Variables::pointer &&
|
|
Token::Match(skipBrackets(tok->next()), "= new|malloc|calloc|kmalloc|kzalloc|kcalloc|strdup|strndup|vmalloc|g_new0|g_try_new|g_new|g_malloc|g_malloc0|g_try_malloc|g_try_malloc0|g_strdup|g_strndup|g_strdup_printf")) {
|
|
variables.allocateMemory(varid, tok);
|
|
} else if (var->mType == Variables::pointer || var->mType == Variables::reference) {
|
|
variables.read(varid, tok);
|
|
variables.writeAliases(varid, tok);
|
|
} else if (var->mType == Variables::pointerArray) {
|
|
tok = doAssignment(variables, tok, deref, scope);
|
|
} else
|
|
variables.writeAll(varid, tok);
|
|
}
|
|
}
|
|
|
|
else if (mTokenizer->isCPP() && Token::Match(tok, "[;{}] %var% <<")) {
|
|
variables.erase(tok->next()->varId());
|
|
}
|
|
|
|
else if (Token::Match(tok, "& %var%")) {
|
|
if (tok->astOperand2()) { // bitop
|
|
variables.read(tok->next()->varId(), tok);
|
|
} else // addressof
|
|
variables.use(tok->next()->varId(), tok); // use = read + write
|
|
} else if (Token::Match(tok, ">>|>>= %name%")) {
|
|
if (isLikelyStreamRead(mTokenizer->isCPP(), tok))
|
|
variables.use(tok->next()->varId(), tok); // use = read + write
|
|
else
|
|
variables.read(tok->next()->varId(), tok);
|
|
} else if (Token::Match(tok, "%var% >>|&") && Token::Match(tok->previous(), "[{};:]")) {
|
|
variables.read(tok->varId(), tok);
|
|
} else if (isLikelyStreamRead(mTokenizer->isCPP(),tok->previous())) {
|
|
variables.use(tok->varId(), tok);
|
|
}
|
|
|
|
// function parameter
|
|
else if (Token::Match(tok, "[(,] %var% [")) {
|
|
variables.use(tok->next()->varId(), tok); // use = read + write
|
|
} else if (Token::Match(tok, "[(,] %var% [,)]") && tok->previous()->str() != "*") {
|
|
variables.use(tok->next()->varId(), tok); // use = read + write
|
|
} else if (Token::Match(tok, "[(,] & %var% [,)]")) {
|
|
variables.eraseAll(tok->tokAt(2)->varId());
|
|
} else if (Token::Match(tok, "[(,] (") &&
|
|
Token::Match(tok->next()->link(), ") %var% [,)]")) {
|
|
variables.use(tok->next()->link()->next()->varId(), tok); // use = read + write
|
|
} else if (Token::Match(tok, "[(,] *| %var% =")) {
|
|
tok = tok->next();
|
|
if (tok->str() == "*")
|
|
tok = tok->next();
|
|
variables.use(tok->varId(), tok);
|
|
}
|
|
|
|
// function
|
|
else if (Token::Match(tok, "%name% (")) {
|
|
if (tok->varId() && !tok->function()) // operator()
|
|
variables.use(tok->varId(), tok);
|
|
else
|
|
variables.read(tok->varId(), tok);
|
|
useFunctionArgs(tok->next()->astOperand2(), variables);
|
|
} else if (Token::Match(tok, "std :: ref ( %var% )")) {
|
|
variables.eraseAll(tok->tokAt(4)->varId());
|
|
}
|
|
|
|
else if (Token::Match(tok->previous(), "[{,] %var% [,}]")) {
|
|
variables.read(tok->varId(), tok);
|
|
}
|
|
|
|
else if (tok->varId() && Token::Match(tok, "%var% .")) {
|
|
variables.use(tok->varId(), tok); // use = read + write
|
|
}
|
|
|
|
else if (tok->str() == ":" && (!tok->valueType() || tok->valueType()->pointer)) {
|
|
if (tok->astOperand1())
|
|
variables.use(tok->astOperand1()->varId(), tok->astOperand1());
|
|
if (tok->astOperand2())
|
|
variables.use(tok->astOperand2()->varId(), tok->astOperand2());
|
|
}
|
|
|
|
else if (tok->isExtendedOp() && tok->next() && tok->next()->varId() && tok->strAt(2) != "=" && !isVarDecl(tok->next())) {
|
|
variables.readAll(tok->next()->varId(), tok);
|
|
}
|
|
|
|
else if (tok->varId() && !isVarDecl(tok) && tok->next() && (tok->next()->str() == ")" || tok->next()->isExtendedOp())) {
|
|
if (Token::Match(tok->tokAt(-2), "%name% ( %var% [,)]") &&
|
|
!(tok->tokAt(-2)->variable() && tok->tokAt(-2)->variable()->isReference()))
|
|
variables.use(tok->varId(), tok);
|
|
else
|
|
variables.readAll(tok->varId(), tok);
|
|
}
|
|
|
|
else if (Token::Match(tok, "%var% ;") && Token::Match(tok->previous(), "[;{}:]")) {
|
|
variables.readAll(tok->varId(), tok);
|
|
}
|
|
|
|
// ++|--
|
|
else if (tok->next() && tok->next()->tokType() == Token::eIncDecOp && tok->next()->astOperand1() && tok->next()->astOperand1()->varId()) {
|
|
if (tok->next()->astParent())
|
|
variables.use(tok->next()->astOperand1()->varId(), tok);
|
|
else
|
|
variables.modified(tok->next()->astOperand1()->varId(), tok);
|
|
}
|
|
|
|
else if (tok->isAssignmentOp()) {
|
|
for (const Token *tok2 = tok->next(); tok2 && tok2->str() != ";"; tok2 = tok2->next()) {
|
|
if (tok2->varId()) {
|
|
if (tok2->strAt(1) == "=")
|
|
variables.write(tok2->varId(), tok);
|
|
else if (tok2->next() && tok2->next()->isAssignmentOp())
|
|
variables.use(tok2->varId(), tok);
|
|
else
|
|
variables.read(tok2->varId(), tok);
|
|
}
|
|
}
|
|
} else if (tok->variable() && tok->variable()->isClass() && tok->variable()->type() &&
|
|
(tok->variable()->type()->needInitialization == Type::NeedInitialization::False) &&
|
|
tok->next()->str() == ";") {
|
|
variables.write(tok->varId(), tok);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckUnusedVar::checkFunctionVariableUsage()
|
|
{
|
|
if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->checkLibrary)
|
|
return;
|
|
|
|
// Parse all executing scopes..
|
|
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
|
|
|
|
auto reportLibraryCfgError = [this](const Token* tok, const std::string& typeName) {
|
|
if (mSettings->checkLibrary) {
|
|
reportError(tok,
|
|
Severity::information,
|
|
"checkLibraryCheckType",
|
|
"--check-library: Provide <type-checks><unusedvar> configuration for " + typeName);
|
|
}
|
|
};
|
|
|
|
// only check functions
|
|
for (const Scope * scope : symbolDatabase->functionScopes) {
|
|
// Bailout when there are lambdas or inline functions
|
|
// TODO: Handle lambdas and inline functions properly
|
|
if (scope->hasInlineOrLambdaFunction())
|
|
continue;
|
|
|
|
for (const Token *tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) {
|
|
if (findLambdaEndToken(tok))
|
|
// todo: handle lambdas
|
|
break;
|
|
if (Token::simpleMatch(tok, "try {"))
|
|
// todo: check try blocks
|
|
tok = tok->linkAt(1);
|
|
const Token *varDecl = nullptr;
|
|
if (tok->variable() && tok->variable()->nameToken() == tok) {
|
|
const Token * eq = tok->next();
|
|
while (Token::simpleMatch(eq, "["))
|
|
eq = eq->link()->next();
|
|
if (Token::simpleMatch(eq, ") (") && Token::simpleMatch(eq->linkAt(1), ") ="))
|
|
eq = eq->linkAt(1)->next();
|
|
if (Token::simpleMatch(eq, "=")) {
|
|
varDecl = tok;
|
|
tok = eq;
|
|
}
|
|
}
|
|
// not assignment/initialization/increment => continue
|
|
const bool isAssignment = tok->isAssignmentOp() && tok->astOperand1();
|
|
const bool isInitialization = (Token::Match(tok, "%var% (|{") && tok->variable() && tok->variable()->nameToken() == tok);
|
|
const bool isIncrementOrDecrement = (tok->tokType() == Token::Type::eIncDecOp);
|
|
if (!isAssignment && !isInitialization && !isIncrementOrDecrement)
|
|
continue;
|
|
|
|
if (isInitialization && Token::Match(tok, "%var% { }")) // don't warn for trivial initialization
|
|
continue;
|
|
|
|
if (isIncrementOrDecrement && tok->astParent() && precedes(tok, tok->astOperand1()))
|
|
continue;
|
|
|
|
if (tok->str() == "=" && !(tok->valueType() && tok->valueType()->pointer) && isRaiiClass(tok->valueType(), mTokenizer->isCPP(), false))
|
|
continue;
|
|
|
|
const bool isPointer = tok->valueType() && (tok->valueType()->pointer || tok->valueType()->type == ValueType::SMART_POINTER);
|
|
|
|
if (tok->isName()) {
|
|
if (isRaiiClass(tok->valueType(), mTokenizer->isCPP(), false))
|
|
continue;
|
|
tok = tok->next();
|
|
}
|
|
if (tok->astParent() && !tok->astParent()->isAssignmentOp() && tok->str() != "(") {
|
|
const Token *parent = tok->astParent();
|
|
while (Token::Match(parent, "%oror%|%comp%|!|&&"))
|
|
parent = parent->astParent();
|
|
if (!parent)
|
|
continue;
|
|
if (!Token::simpleMatch(parent->previous(), "if ("))
|
|
continue;
|
|
}
|
|
// Do not warn about assignment with NULL
|
|
if (isPointer && isNullOperand(tok->astOperand2()))
|
|
continue;
|
|
|
|
if (!tok->astOperand1())
|
|
continue;
|
|
|
|
const Token *iteratorToken = tok->astOperand1();
|
|
while (Token::Match(iteratorToken, "[.*]"))
|
|
iteratorToken = iteratorToken->astOperand1();
|
|
if (iteratorToken && iteratorToken->variable() && iteratorToken->variable()->typeEndToken()->str().find("iterator") != std::string::npos)
|
|
continue;
|
|
|
|
const Token *op1tok = tok->astOperand1();
|
|
while (Token::Match(op1tok, ".|[|*"))
|
|
op1tok = op1tok->astOperand1();
|
|
|
|
// Assignment in macro => do not warn
|
|
if (isAssignment && tok->isExpandedMacro() && op1tok && op1tok->isExpandedMacro())
|
|
continue;
|
|
|
|
const Variable *op1Var = op1tok ? op1tok->variable() : nullptr;
|
|
if (!op1Var && Token::Match(tok, "(|{") && tok->previous() && tok->previous()->variable())
|
|
op1Var = tok->previous()->variable();
|
|
std::string bailoutTypeName;
|
|
if (op1Var) {
|
|
if (op1Var->isReference() && op1Var->nameToken() != tok->astOperand1())
|
|
// todo: check references
|
|
continue;
|
|
|
|
if (op1Var->isStatic())
|
|
// todo: check static variables
|
|
continue;
|
|
|
|
if (op1Var->nameToken()->isAttributeUnused())
|
|
continue;
|
|
|
|
// Avoid FP for union..
|
|
if (op1Var->type() && op1Var->type()->isUnionType())
|
|
continue;
|
|
|
|
// Bailout for unknown template classes, we have no idea what side effects such assignments have
|
|
if (mTokenizer->isCPP() &&
|
|
op1Var->isClass() &&
|
|
(!op1Var->valueType() || op1Var->valueType()->type == ValueType::Type::UNKNOWN_TYPE)) {
|
|
// Check in the library if we should bailout or not..
|
|
const std::string typeName = op1Var->getTypeName();
|
|
switch (mSettings->library.getTypeCheck("unusedvar", typeName)) {
|
|
case Library::TypeCheck::def:
|
|
bailoutTypeName = typeName;
|
|
break;
|
|
case Library::TypeCheck::check:
|
|
break;
|
|
case Library::TypeCheck::suppress:
|
|
case Library::TypeCheck::checkFiniteLifetime:
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Is there a redundant assignment?
|
|
const Token *start = tok->findExpressionStartEndTokens().second->next();
|
|
|
|
const Token *expr = varDecl ? varDecl : tok->astOperand1();
|
|
|
|
if (isInitialization)
|
|
expr = tok->previous();
|
|
|
|
// Is variable in lhs a union member?
|
|
if (tok->previous() && tok->previous()->variable() && tok->previous()->variable()->nameToken()->scope()->type == Scope::eUnion)
|
|
continue;
|
|
|
|
FwdAnalysis fwdAnalysis(mTokenizer->isCPP(), mSettings->library);
|
|
const Token* scopeEnd = ValueFlow::getEndOfExprScope(expr, scope, /*smallest*/ false);
|
|
if (fwdAnalysis.unusedValue(expr, start, scopeEnd)) {
|
|
if (!bailoutTypeName.empty()) {
|
|
if (bailoutTypeName != "auto")
|
|
reportLibraryCfgError(tok, bailoutTypeName);
|
|
continue;
|
|
}
|
|
|
|
// warn
|
|
if (!expr->variable() || !expr->variable()->isMaybeUnused())
|
|
unreadVariableError(tok, expr->expressionString(), false);
|
|
}
|
|
}
|
|
|
|
// varId, usage {read, write, modified}
|
|
Variables variables;
|
|
|
|
checkFunctionVariableUsage_iterateScopes(scope, variables);
|
|
|
|
|
|
// Check usage of all variables in the current scope..
|
|
for (std::map<nonneg int, Variables::VariableUsage>::const_iterator it = variables.varUsage().cbegin();
|
|
it != variables.varUsage().cend();
|
|
++it) {
|
|
const Variables::VariableUsage &usage = it->second;
|
|
|
|
// variable has been marked as unused so ignore it
|
|
if (usage._var->nameToken()->isAttributeUnused() || usage._var->nameToken()->isAttributeUsed())
|
|
continue;
|
|
|
|
// skip things that are only partially implemented to prevent false positives
|
|
if (usage.mType == Variables::pointerPointer ||
|
|
usage.mType == Variables::pointerArray ||
|
|
usage.mType == Variables::referenceArray)
|
|
continue;
|
|
|
|
const std::string &varname = usage._var->name();
|
|
const Variable* var = symbolDatabase->getVariableFromVarId(it->first);
|
|
|
|
// 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._lastAccess, varname);
|
|
|
|
// variable has not been written, read, or modified
|
|
else if (usage.unused() && !usage._modified) {
|
|
if (!usage._var->isMaybeUnused()) {
|
|
unusedVariableError(usage._var->nameToken(), varname);
|
|
}
|
|
}
|
|
// variable has not been written but has been modified
|
|
else if (usage._modified && !usage._write && !usage._allocateMemory && var && !var->isStlType()) {
|
|
if (var->isStatic()) // static variables are initialized by default
|
|
continue;
|
|
unassignedVariableError(usage._var->nameToken(), varname);
|
|
}
|
|
// variable has been read but not written
|
|
else if (!usage._write && !usage._allocateMemory && var && !var->isStlType() && !isEmptyType(var->type()))
|
|
unassignedVariableError(usage._var->nameToken(), varname);
|
|
else if (!usage._var->isMaybeUnused() && !usage._modified && !usage._read && var) {
|
|
const Token* vnt = var->nameToken();
|
|
bool error = false;
|
|
if (vnt->next()->isSplittedVarDeclEq()) {
|
|
const Token* nextStmt = vnt->tokAt(2);
|
|
if (nextStmt->isExpandedMacro()) {
|
|
const Token* parent = nextStmt;
|
|
while (parent->astParent() && parent == parent->astParent()->astOperand1())
|
|
parent = parent->astParent();
|
|
if (parent->isAssignmentOp() && parent->isExpandedMacro())
|
|
continue;
|
|
}
|
|
while (nextStmt && nextStmt->str() != ";")
|
|
nextStmt = nextStmt->next();
|
|
error = precedes(usage._lastAccess, nextStmt);
|
|
}
|
|
if (error) {
|
|
if (mTokenizer->isCPP() && var->isClass() &&
|
|
(!var->valueType() || var->valueType()->type == ValueType::Type::UNKNOWN_TYPE)) {
|
|
const std::string typeName = var->getTypeName();
|
|
switch (mSettings->library.getTypeCheck("unusedvar", typeName)) {
|
|
case Library::TypeCheck::def:
|
|
reportLibraryCfgError(vnt, typeName);
|
|
break;
|
|
case Library::TypeCheck::check:
|
|
break;
|
|
case Library::TypeCheck::suppress:
|
|
case Library::TypeCheck::checkFiniteLifetime:
|
|
error = false;
|
|
}
|
|
}
|
|
if (error)
|
|
unreadVariableError(vnt, varname, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckUnusedVar::unusedVariableError(const Token *tok, const std::string &varname)
|
|
{
|
|
if (!mSettings->severity.isEnabled(Severity::style))
|
|
return;
|
|
|
|
reportError(tok, Severity::style, "unusedVariable", "$symbol:" + varname + "\nUnused variable: $symbol", CWE563, Certainty::normal);
|
|
}
|
|
|
|
void CheckUnusedVar::allocatedButUnusedVariableError(const Token *tok, const std::string &varname)
|
|
{
|
|
if (!mSettings->severity.isEnabled(Severity::style))
|
|
return;
|
|
|
|
reportError(tok, Severity::style, "unusedAllocatedMemory", "$symbol:" + varname + "\nVariable '$symbol' is allocated memory that is never used.", CWE563, Certainty::normal);
|
|
}
|
|
|
|
void CheckUnusedVar::unreadVariableError(const Token *tok, const std::string &varname, bool modified)
|
|
{
|
|
if (!mSettings->severity.isEnabled(Severity::style))
|
|
return;
|
|
|
|
if (modified)
|
|
reportError(tok, Severity::style, "unreadVariable", "$symbol:" + varname + "\nVariable '$symbol' is modified but its new value is never used.", CWE563, Certainty::normal);
|
|
else
|
|
reportError(tok, Severity::style, "unreadVariable", "$symbol:" + varname + "\nVariable '$symbol' is assigned a value that is never used.", CWE563, Certainty::normal);
|
|
}
|
|
|
|
void CheckUnusedVar::unassignedVariableError(const Token *tok, const std::string &varname)
|
|
{
|
|
if (!mSettings->severity.isEnabled(Severity::style))
|
|
return;
|
|
|
|
reportError(tok, Severity::style, "unassignedVariable", "$symbol:" + varname + "\nVariable '$symbol' is not assigned a value.", CWE665, Certainty::normal);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Check that all struct members are used
|
|
//---------------------------------------------------------------------------
|
|
void CheckUnusedVar::checkStructMemberUsage()
|
|
{
|
|
if (!mSettings->severity.isEnabled(Severity::style))
|
|
return;
|
|
|
|
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
|
|
|
|
for (const Scope &scope : symbolDatabase->scopeList) {
|
|
if (scope.type != Scope::eStruct && scope.type != Scope::eClass && scope.type != Scope::eUnion)
|
|
continue;
|
|
|
|
if (scope.bodyStart->fileIndex() != 0 || scope.className.empty())
|
|
continue;
|
|
|
|
// Packed struct => possibly used by lowlevel code. Struct members might be required by hardware.
|
|
if (scope.bodyEnd->isAttributePacked())
|
|
continue;
|
|
if (const Preprocessor *preprocessor = mTokenizer->getPreprocessor()) {
|
|
const auto& directives = preprocessor->getDirectives();
|
|
const bool isPacked = std::any_of(directives.cbegin(), directives.cend(), [&](const Directive& d) {
|
|
return d.linenr < scope.bodyStart->linenr() && d.str == "#pragma pack(1)" && d.file == mTokenizer->list.getFiles().front();
|
|
});
|
|
if (isPacked)
|
|
continue;
|
|
}
|
|
|
|
// Bail out for template struct, members might be used in non-matching instantiations
|
|
if (scope.className.find('<') != std::string::npos)
|
|
continue;
|
|
|
|
// bail out if struct is inherited
|
|
const bool isInherited = std::any_of(symbolDatabase->scopeList.cbegin(), symbolDatabase->scopeList.cend(), [&](const Scope& derivedScope) {
|
|
const Type* dType = derivedScope.definedType;
|
|
return dType && std::any_of(dType->derivedFrom.cbegin(), dType->derivedFrom.cend(), [&](const Type::BaseInfo& derivedFrom) {
|
|
return derivedFrom.type == scope.definedType && derivedFrom.access != AccessControl::Private;
|
|
});
|
|
});
|
|
|
|
// bail out for extern/global struct
|
|
bool bailout = false;
|
|
for (const Variable* var : symbolDatabase->variableList()) {
|
|
if (var && (var->isExtern() || (var->isGlobal() && !var->isStatic())) && var->typeEndToken()->str() == scope.className) {
|
|
bailout = true;
|
|
break;
|
|
}
|
|
if (bailout)
|
|
break;
|
|
}
|
|
if (bailout)
|
|
continue;
|
|
|
|
// Bail out if some data is casted to struct..
|
|
const std::string castPattern("( struct| " + scope.className + " * ) &| %name%");
|
|
if (Token::findmatch(scope.bodyEnd, castPattern.c_str()))
|
|
continue;
|
|
|
|
// (struct S){..}
|
|
const std::string initPattern("( struct| " + scope.className + " ) {");
|
|
if (Token::findmatch(scope.bodyEnd, initPattern.c_str()))
|
|
continue;
|
|
|
|
// Bail out if struct is used in sizeof..
|
|
for (const Token *tok = scope.bodyEnd; nullptr != (tok = Token::findsimplematch(tok, "sizeof ("));) {
|
|
tok = tok->tokAt(2);
|
|
if (Token::Match(tok, ("struct| " + scope.className).c_str())) {
|
|
bailout = true;
|
|
break;
|
|
}
|
|
}
|
|
if (bailout)
|
|
continue;
|
|
|
|
for (const Variable &var : scope.varlist) {
|
|
// only warn for variables without side effects
|
|
if (!var.typeStartToken()->isStandardType() && !var.isPointer() && !astIsContainer(var.nameToken()) && !isRecordTypeWithoutSideEffects(var.type()))
|
|
continue;
|
|
if (isInherited && !var.isPrivate())
|
|
continue;
|
|
|
|
// Check if the struct member variable is used anywhere in the file
|
|
bool use = false;
|
|
for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) {
|
|
if (tok->variable() != &var)
|
|
continue;
|
|
if (tok != var.nameToken()) {
|
|
use = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!use) {
|
|
std::string prefix = "struct";
|
|
if (scope.type == Scope::ScopeType::eClass)
|
|
prefix = "class";
|
|
else if (scope.type == Scope::ScopeType::eUnion)
|
|
prefix = "union";
|
|
unusedStructMemberError(var.nameToken(), scope.className, var.name(), prefix);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckUnusedVar::unusedStructMemberError(const Token* tok, const std::string& structname, const std::string& varname, const std::string& prefix)
|
|
{
|
|
reportError(tok, Severity::style, "unusedStructMember", "$symbol:" + structname + "::" + varname + '\n' + prefix + " member '$symbol' is never used.", CWE563, Certainty::normal);
|
|
}
|
|
|
|
bool CheckUnusedVar::isRecordTypeWithoutSideEffects(const Type* type)
|
|
{
|
|
// a type that has no side effects (no constructors and no members with constructors)
|
|
/** @todo false negative: check constructors for side effects */
|
|
const std::pair<std::map<const Type *,bool>::iterator,bool> found=mIsRecordTypeWithoutSideEffectsMap.insert(
|
|
std::pair<const Type *,bool>(type,false)); //Initialize with side effects for possible recursions
|
|
bool & withoutSideEffects = found.first->second;
|
|
if (!found.second)
|
|
return withoutSideEffects;
|
|
|
|
// unknown types are assumed to have side effects
|
|
if (!type || !type->classScope)
|
|
return (withoutSideEffects = false);
|
|
|
|
// Non-empty constructors => possible side effects
|
|
for (const Function& f : type->classScope->functionList) {
|
|
if (!f.isConstructor() && !f.isDestructor())
|
|
continue;
|
|
if (f.argDef && Token::simpleMatch(f.argDef->link(), ") ="))
|
|
continue; // ignore default/deleted constructors
|
|
const bool emptyBody = (f.functionScope && Token::simpleMatch(f.functionScope->bodyStart, "{ }"));
|
|
|
|
const Token* nextToken = f.argDef->link();
|
|
if (Token::simpleMatch(nextToken, ") :")) {
|
|
// validating initialization list
|
|
nextToken = nextToken->next(); // goto ":"
|
|
|
|
for (const Token *initListToken = nextToken; Token::Match(initListToken, "[:,] %var% [({]"); initListToken = initListToken->linkAt(2)->next()) {
|
|
const Token* varToken = initListToken->next();
|
|
const Variable* variable = varToken->variable();
|
|
if (variable && !isVariableWithoutSideEffects(*variable)) {
|
|
return withoutSideEffects = false;
|
|
}
|
|
|
|
const Token* valueEnd = initListToken->linkAt(2);
|
|
for (const Token* valueToken = initListToken->tokAt(3); valueToken != valueEnd; valueToken = valueToken->next()) {
|
|
const Variable* initValueVar = valueToken->variable();
|
|
if (initValueVar && !isVariableWithoutSideEffects(*initValueVar)) {
|
|
return withoutSideEffects = false;
|
|
}
|
|
if ((valueToken->tokType() == Token::Type::eName) ||
|
|
(valueToken->tokType() == Token::Type::eLambda) ||
|
|
(valueToken->tokType() == Token::Type::eOther)) {
|
|
return withoutSideEffects = false;
|
|
}
|
|
const Function* initValueFunc = valueToken->function();
|
|
if (initValueFunc && !isFunctionWithoutSideEffects(*initValueFunc, valueToken,
|
|
std::list<const Function*> {})) {
|
|
return withoutSideEffects = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!emptyBody)
|
|
return (withoutSideEffects = false);
|
|
}
|
|
|
|
// Derived from type that has side effects?
|
|
if (std::any_of(type->derivedFrom.cbegin(), type->derivedFrom.cend(), [this](const Type::BaseInfo& derivedFrom) {
|
|
return !isRecordTypeWithoutSideEffects(derivedFrom.type);
|
|
}))
|
|
return (withoutSideEffects = false);
|
|
|
|
// Is there a member variable with possible side effects
|
|
for (const Variable& var : type->classScope->varlist) {
|
|
withoutSideEffects = isVariableWithoutSideEffects(var);
|
|
if (!withoutSideEffects) {
|
|
return withoutSideEffects;
|
|
}
|
|
}
|
|
|
|
|
|
return (withoutSideEffects = true);
|
|
}
|
|
|
|
bool CheckUnusedVar::isVariableWithoutSideEffects(const Variable& var)
|
|
{
|
|
if (var.isPointer())
|
|
return true;
|
|
|
|
const Type* variableType = var.type();
|
|
if (variableType) {
|
|
if (!isRecordTypeWithoutSideEffects(variableType))
|
|
return false;
|
|
} else {
|
|
if (WRONG_DATA(!var.valueType(), var.typeStartToken()))
|
|
return false;
|
|
const ValueType::Type valueType = var.valueType()->type;
|
|
if ((valueType == ValueType::Type::UNKNOWN_TYPE) || (valueType == ValueType::Type::NONSTD))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CheckUnusedVar::isEmptyType(const Type* type)
|
|
{
|
|
// a type that has no variables and no constructor
|
|
|
|
const std::pair<std::map<const Type *,bool>::iterator,bool> found=mIsEmptyTypeMap.insert(
|
|
std::pair<const Type *,bool>(type,false));
|
|
bool & emptyType=found.first->second;
|
|
if (!found.second)
|
|
return emptyType;
|
|
|
|
if (type && type->classScope && type->classScope->numConstructors == 0 &&
|
|
(type->classScope->varlist.empty())) {
|
|
return (emptyType = std::all_of(type->derivedFrom.cbegin(), type->derivedFrom.cend(), [this](const Type::BaseInfo& bi) {
|
|
return isEmptyType(bi.type);
|
|
}));
|
|
}
|
|
|
|
// unknown types are assumed to be nonempty
|
|
return (emptyType = false);
|
|
}
|
|
|
|
bool CheckUnusedVar::isFunctionWithoutSideEffects(const Function& func, const Token* functionUsageToken,
|
|
std::list<const Function*> checkedFuncs)
|
|
{
|
|
// no body to analyze
|
|
if (!func.hasBody()) {
|
|
return false;
|
|
}
|
|
|
|
for (const Token* argsToken = functionUsageToken->next(); !Token::simpleMatch(argsToken, ")"); argsToken = argsToken->next()) {
|
|
const Variable* argVar = argsToken->variable();
|
|
if (argVar && argVar->isGlobal()) {
|
|
return false; // TODO: analyze global variable usage
|
|
}
|
|
}
|
|
|
|
bool sideEffectReturnFound = false;
|
|
std::set<const Variable*> pointersToGlobals;
|
|
for (const Token* bodyToken = func.functionScope->bodyStart->next(); bodyToken != func.functionScope->bodyEnd;
|
|
bodyToken = bodyToken->next()) {
|
|
// check variable inside function body
|
|
const Variable* bodyVariable = bodyToken->variable();
|
|
if (bodyVariable) {
|
|
if (!isVariableWithoutSideEffects(*bodyVariable)) {
|
|
return false;
|
|
}
|
|
// check if global variable is changed
|
|
if (bodyVariable->isGlobal() || (pointersToGlobals.find(bodyVariable) != pointersToGlobals.end())) {
|
|
const int depth = 20;
|
|
if (isVariableChanged(bodyToken, depth, mSettings, mTokenizer->isCPP())) {
|
|
return false;
|
|
}
|
|
// check if pointer to global variable assigned to another variable (another_var = &global_var)
|
|
if (Token::simpleMatch(bodyToken->tokAt(-1), "&") && Token::simpleMatch(bodyToken->tokAt(-2), "=")) {
|
|
const Token* assigned_var_token = bodyToken->tokAt(-3);
|
|
if (assigned_var_token && assigned_var_token->variable()) {
|
|
pointersToGlobals.insert(assigned_var_token->variable());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// check nested function
|
|
const Function* bodyFunction = bodyToken->function();
|
|
if (bodyFunction) {
|
|
if (std::find(checkedFuncs.cbegin(), checkedFuncs.cend(), bodyFunction) != checkedFuncs.cend()) { // recursion found
|
|
continue;
|
|
}
|
|
checkedFuncs.push_back(bodyFunction);
|
|
if (!isFunctionWithoutSideEffects(*bodyFunction, bodyToken, checkedFuncs)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// check returned value
|
|
if (Token::simpleMatch(bodyToken, "return")) {
|
|
const Token* returnValueToken = bodyToken->next();
|
|
// TODO: handle complex return expressions
|
|
if (!Token::simpleMatch(returnValueToken->next(), ";")) {
|
|
sideEffectReturnFound = true;
|
|
continue;
|
|
}
|
|
// simple one-token return
|
|
const Variable* returnVariable = returnValueToken->variable();
|
|
if (returnValueToken->isLiteral() ||
|
|
(returnVariable && isVariableWithoutSideEffects(*returnVariable))) {
|
|
continue;
|
|
}
|
|
sideEffectReturnFound = true;
|
|
}
|
|
|
|
// unknown name
|
|
if (bodyToken->isNameOnly()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return !sideEffectReturnFound;
|
|
}
|