/*
* Cppcheck - A tool for static C/C++ code analysis
* Copyright (C) 2007-2026 Cppcheck team.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
//---------------------------------------------------------------------------
#include "checkunusedfunctions.h"
#include "analyzerinfo.h"
#include "astutils.h"
#include "errorlogger.h"
#include "errortypes.h"
#include "library.h"
#include "settings.h"
#include "symboldatabase.h"
#include "token.h"
#include "tokenize.h"
#include "tokenlist.h"
#include "utils.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "xml.h"
//---------------------------------------------------------------------------
static const CWE CWE561(561U); // Dead Code
static std::string stripTemplateParameters(const std::string& funcName) {
std::string name = funcName;
const auto pos = name.find('isAttributeConstructor() || func->isAttributeDestructor() || func->type != FunctionType::eFunction || func->isOperator())
continue;
if (func->isAttributeUnused() || func->isAttributeMaybeUnused())
continue;
if (func->isExtern())
continue;
bool foundAllBaseClasses{};
if (const Function* ofunc = func->getOverriddenFunction(&foundAllBaseClasses)) {
if (!foundAllBaseClasses || ofunc->isPure())
continue;
}
else if (func->isImplicitlyVirtual()) {
continue;
}
mFunctionDecl.emplace_back(func);
FunctionUsage &usage = mFunctions[stripTemplateParameters(func->name())];
if (func->retDef && (func->retDef->isAttributeUnused() || func->retDef->isAttributeMaybeUnused())) {
usage.usedOtherFile = true;
}
if (!usage.lineNumber) {
usage.lineNumber = func->token->linenr();
usage.column = func->token->column();
}
usage.isC = func->token->isC();
usage.isStatic = func->isStatic();
// TODO: why always overwrite this but not the filename and line?
usage.fileIndex = func->token->fileIndex();
const std::string& fileName = tokenizer.list.file(func->token);
// No filename set yet..
if (usage.filename.empty()) {
usage.filename = fileName;
}
// Multiple files => filename = "+"
else if (usage.filename != fileName) {
//func.filename = "+";
usage.usedOtherFile |= usage.usedSameFile;
}
}
}
// Function usage..
const Token *lambdaEndToken = nullptr;
for (const Token *tok = tokenizer.tokens(); tok; tok = tok->next()) {
if (tok == lambdaEndToken)
lambdaEndToken = nullptr;
else if (!lambdaEndToken && tok->str() == "[")
lambdaEndToken = findLambdaEndToken(tok);
// parsing of library code to find called functions
if (library.isexecutableblock(FileName, tok->str())) {
const Token * markupVarToken = tok->tokAt(library.blockstartoffset(FileName));
// not found
if (!markupVarToken)
continue;
int scope = 0;
bool start = true;
// find all function calls in library code (starts with '(', not if or while etc)
while ((scope || start) && markupVarToken) {
if (markupVarToken->str() == library.blockstart(FileName)) {
scope++;
start = false;
} else if (markupVarToken->str() == library.blockend(FileName))
scope--;
else if (!library.iskeyword(FileName, markupVarToken->str())) {
mFunctionCalls.insert(markupVarToken->str());
if (mFunctions.find(markupVarToken->str()) != mFunctions.end())
mFunctions[markupVarToken->str()].usedOtherFile = true;
else if (markupVarToken->strAt(1) == "(") {
FunctionUsage &func = mFunctions[markupVarToken->str()];
func.filename = tokenizer.list.getFiles()[markupVarToken->fileIndex()];
if (func.filename.empty() || func.filename == "+")
func.usedOtherFile = true;
else
func.usedSameFile = true;
}
}
markupVarToken = markupVarToken->next();
}
}
if (!doMarkup // only check source files
&& library.isexporter(tok->str()) && tok->next() != nullptr) {
const Token * propToken = tok->next();
while (propToken && propToken->str() != ")") {
if (library.isexportedprefix(tok->str(), propToken->str())) {
const Token* nextPropToken = propToken->next();
const std::string& value = nextPropToken->str();
if (mFunctions.find(value) != mFunctions.end()) {
mFunctions[value].usedOtherFile = true;
}
mFunctionCalls.insert(value);
}
if (library.isexportedsuffix(tok->str(), propToken->str())) {
const Token* prevPropToken = propToken->previous();
const std::string& value = prevPropToken->str();
if (value != ")" && mFunctions.find(value) != mFunctions.end()) {
mFunctions[value].usedOtherFile = true;
}
mFunctionCalls.insert(value);
}
propToken = propToken->next();
}
}
if (doMarkup && library.isimporter(FileName, tok->str()) && tok->next()) {
const Token * propToken = tok->next();
if (propToken->next()) {
propToken = propToken->next();
while (propToken && propToken->str() != ")") {
const std::string& value = propToken->str();
if (!value.empty()) {
mFunctions[value].usedOtherFile = true;
mFunctionCalls.insert(value);
break;
}
propToken = propToken->next();
}
}
}
if (library.isreflection(tok->str())) {
const int argIndex = library.reflectionArgument(tok->str());
if (argIndex >= 0) {
const Token * funcToken = tok->next();
int index = 0;
std::string value;
while (funcToken) {
if (funcToken->str()==",") {
if (++index == argIndex)
break;
value.clear();
} else
value += funcToken->str();
funcToken = funcToken->next();
}
if (index == argIndex) {
value = value.substr(1, value.length() - 2);
mFunctions[value].usedOtherFile = true;
mFunctionCalls.insert(std::move(value));
}
}
}
if (tok->hasAttributeCleanup()) {
const std::string& funcname = tok->getAttributeCleanup();
mFunctions[funcname].usedOtherFile = true;
mFunctionCalls.insert(funcname);
continue;
}
const Token *funcname = nullptr;
if (doMarkup)
funcname = Token::Match(tok, "%name% (") ? tok : nullptr;
else if ((lambdaEndToken || tok->scope()->isExecutable()) && Token::Match(tok, "%name% (")) {
funcname = tok;
} else if ((lambdaEndToken || tok->scope()->isExecutable()) && Token::Match(tok, "%name% (")) {
funcname = tok;
} else if (Token::Match(tok, "< %name%") && tok->link()) {
funcname = tok->next();
while (Token::Match(funcname, "%name% :: %name%"))
funcname = funcname->tokAt(2);
} else if (tok->scope()->type != ScopeType::eEnum && (Token::Match(tok, "[;{}.,()[=+-/|!?:]") || Token::Match(tok, "return|throw"))) {
funcname = tok->next();
if (funcname && funcname->str() == "&")
funcname = funcname->next();
if (funcname && funcname->str() == "::")
funcname = funcname->next();
while (Token::Match(funcname, "%name% :: %name%"))
funcname = funcname->tokAt(2);
if (funcname && funcname->isName() && !funcname->function() && !tok->astParent() && Token::Match(tok, "[(,]")) // unknown type in parameter list
continue;
if (!Token::Match(funcname, "%name% [(),;]:}]"))
continue;
}
if (!funcname || funcname->isKeyword() || funcname->isStandardType() || funcname->varId() || funcname->enumerator() || funcname->type() || funcname->isLiteral())
continue;
// funcname ( => Assert that the end parentheses isn't followed by {
if (Token::Match(funcname, "%name% (|Attribute("column"), "0");
// cppcheck-suppress templateInstantiation - TODO: fix this - see #11631
decls[functionName] = Location(file ? file : filesTxtInfo.sourceFile, strToInt(lineNumber), strToInt(column));
}
}
}
};
const std::string err = AnalyzerInformation::processFilesTxt(buildDir, handler, settings.debugainfo);
if (!err.empty()) {
const ErrorMessage errmsg({}, "", Severity::error, err, "internalError", Certainty::normal);
errorLogger.reportErr(errmsg);
return;
}
for (auto decl = decls.cbegin(); decl != decls.cend(); ++decl) {
const std::string &functionName = stripTemplateParameters(decl->first);
if (settings.library.isentrypoint(functionName))
continue;
if (calls.find(functionName) == calls.end() && !isOperatorFunction(functionName)) {
const Location &loc = decl->second;
unusedFunctionError(errorLogger, loc.fileName, /*fileIndex*/ 0, loc.lineNumber, loc.column, functionName);
}
}
}
void CheckUnusedFunctions::updateFunctionData(const CheckUnusedFunctions& checkUnusedFunctions)
{
for (const auto& entry : checkUnusedFunctions.mFunctions)
{
FunctionUsage &usage = mFunctions[entry.first];
if (!usage.lineNumber) {
usage.lineNumber = entry.second.lineNumber;
usage.column = entry.second.column;
}
// TODO: why always overwrite this but not the filename and line?
usage.fileIndex = entry.second.fileIndex;
if (usage.filename.empty())
usage.filename = entry.second.filename;
usage.usedOtherFile |= entry.second.usedOtherFile;
usage.usedSameFile |= entry.second.usedSameFile;
}
mFunctionDecl.insert(mFunctionDecl.cend(), checkUnusedFunctions.mFunctionDecl.cbegin(), checkUnusedFunctions.mFunctionDecl.cend());
mFunctionCalls.insert(checkUnusedFunctions.mFunctionCalls.cbegin(), checkUnusedFunctions.mFunctionCalls.cend());
}