cppcheck/lib/checkunusedvar.cpp
Oliver Stöneberg 91070ca794
utils.h: added startsWith() and started using it (#5381)
This makes the code much more readable. It also makes it less prone to
errors because we do not need to specify the length of the string to
match and the returnvalue is clear.

The code with the bad returnvalue check was never executed and I added a
test to show that.
2023-09-08 19:30:25 +02:00

1751 lines
70 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)
{
const 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 = Variables::array;
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;
}
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()), "=")) ||
(tok->isUnaryOp("*") && Token::simpleMatch(tok->astParent(), "=") && Token::simpleMatch(tok->astOperand1(), "+"))) {
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 &&
((mTokenizer->isCPP() && Token::simpleMatch(skipBrackets(tok->next()), "= new")) ||
(Token::Match(skipBrackets(tok->next()), "= %name% (") && mSettings->library.getAllocFuncInfo(tok->tokAt(2))))) {
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%")) {
const Token* vartok = tok->next();
while (vartok->str() == "*")
vartok = vartok->next();
if (!(vartok->variable() && vartok == vartok->variable()->nameToken()) &&
!(tok->str() == "(" && !Token::Match(tok->previous(), "%name%")))
variables.use(vartok->varId(), vartok);
}
// 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;
logChecker("CheckUnusedVar::checkFunctionVariableUsage"); // style
// 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 (!isInitialization && 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..
std::string typeName = op1Var->getTypeName();
if (startsWith(typeName, "::"))
typeName.erase(typeName.begin(), typeName.begin() + 2);
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() || (!var->isReference() && vnt->next()->str() == "=")) {
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;
logChecker("CheckUnusedVar::checkStructMemberUsage"); // style
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;
if (scope.classDef->isExpandedMacro())
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 (Token::Match(tok, ". %name%") && !tok->next()->variable() && !tok->next()->function() && tok->next()->str() == var.name()) {
// not known => assume variable is used
use = true;
break;
}
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 indirect = bodyVariable->isArray() ? bodyVariable->dimensions().size() : bodyVariable->isPointer();
if (isVariableChanged(bodyToken, indirect, 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;
}