Cppcheck
checkunusedfunctions.cpp
Go to the documentation of this file.
1 /*
2  * Cppcheck - A tool for static C/C++ code analysis
3  * Copyright (C) 2007-2024 Cppcheck team.
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 
20 //---------------------------------------------------------------------------
21 #include "checkunusedfunctions.h"
22 
23 #include "astutils.h"
24 #include "errorlogger.h"
25 #include "errortypes.h"
26 #include "library.h"
27 #include "settings.h"
28 #include "symboldatabase.h"
29 #include "token.h"
30 #include "tokenize.h"
31 #include "tokenlist.h"
32 #include "utils.h"
33 
34 #include <algorithm>
35 #include <cctype>
36 #include <cstring>
37 #include <fstream>
38 #include <map>
39 #include <sstream>
40 #include <tuple>
41 #include <utility>
42 #include <vector>
43 
44 #include "xml.h"
45 
46 //---------------------------------------------------------------------------
47 
48 static const CWE CWE561(561U); // Dead Code
49 
50 static std::string stripTemplateParameters(const std::string& funcName) {
51  std::string name = funcName;
52  const auto pos = name.find('<');
53  if (pos > 0 && pos != std::string::npos)
54  name.erase(pos - 1);
55  return name;
56 }
57 
58 //---------------------------------------------------------------------------
59 // FUNCTION USAGE - Check for unused functions etc
60 //---------------------------------------------------------------------------
61 
62 void CheckUnusedFunctions::parseTokens(const Tokenizer &tokenizer, const Settings &settings)
63 {
64  const char * const FileName = tokenizer.list.getFiles().front().c_str();
65 
66  const bool doMarkup = settings.library.markupFile(FileName);
67 
68  // Function declarations..
69  if (!doMarkup) {
70  const SymbolDatabase* symbolDatabase = tokenizer.getSymbolDatabase();
71  for (const Scope* scope : symbolDatabase->functionScopes) {
72  const Function* func = scope->function;
73  if (!func || !func->token)
74  continue;
75 
76  // Don't warn about functions that are marked by __attribute__((constructor)) or __attribute__((destructor))
77  if (func->isAttributeConstructor() || func->isAttributeDestructor() || func->type != Function::eFunction || func->isOperator())
78  continue;
79 
80  if (func->isExtern())
81  continue;
82 
83  mFunctionDecl.emplace_back(func);
84 
86 
87  if (!usage.lineNumber)
88  usage.lineNumber = func->token->linenr();
89 
90  // TODO: why always overwrite this but not the filename and line?
91  usage.fileIndex = func->token->fileIndex();
92  const std::string& fileName = tokenizer.list.file(func->token);
93 
94  // No filename set yet..
95  if (usage.filename.empty()) {
96  usage.filename = fileName;
97  }
98  // Multiple files => filename = "+"
99  else if (usage.filename != fileName) {
100  //func.filename = "+";
101  usage.usedOtherFile |= usage.usedSameFile;
102  }
103  }
104  }
105 
106  // Function usage..
107  const Token *lambdaEndToken = nullptr;
108  for (const Token *tok = tokenizer.tokens(); tok; tok = tok->next()) {
109 
110  if (tok == lambdaEndToken)
111  lambdaEndToken = nullptr;
112  else if (!lambdaEndToken && tok->str() == "[")
113  lambdaEndToken = findLambdaEndToken(tok);
114 
115  // parsing of library code to find called functions
116  if (settings.library.isexecutableblock(FileName, tok->str())) {
117  const Token * markupVarToken = tok->tokAt(settings.library.blockstartoffset(FileName));
118  // not found
119  if (!markupVarToken)
120  continue;
121  int scope = 0;
122  bool start = true;
123  // find all function calls in library code (starts with '(', not if or while etc)
124  while ((scope || start) && markupVarToken) {
125  if (markupVarToken->str() == settings.library.blockstart(FileName)) {
126  scope++;
127  start = false;
128  } else if (markupVarToken->str() == settings.library.blockend(FileName))
129  scope--;
130  else if (!settings.library.iskeyword(FileName, markupVarToken->str())) {
131  mFunctionCalls.insert(markupVarToken->str());
132  if (mFunctions.find(markupVarToken->str()) != mFunctions.end())
133  mFunctions[markupVarToken->str()].usedOtherFile = true;
134  else if (markupVarToken->next()->str() == "(") {
135  FunctionUsage &func = mFunctions[markupVarToken->str()];
136  func.filename = tokenizer.list.getFiles()[markupVarToken->fileIndex()];
137  if (func.filename.empty() || func.filename == "+")
138  func.usedOtherFile = true;
139  else
140  func.usedSameFile = true;
141  }
142  }
143  markupVarToken = markupVarToken->next();
144  }
145  }
146 
147  if (!doMarkup // only check source files
148  && settings.library.isexporter(tok->str()) && tok->next() != nullptr) {
149  const Token * propToken = tok->next();
150  while (propToken && propToken->str() != ")") {
151  if (settings.library.isexportedprefix(tok->str(), propToken->str())) {
152  const Token* nextPropToken = propToken->next();
153  const std::string& value = nextPropToken->str();
154  if (mFunctions.find(value) != mFunctions.end()) {
155  mFunctions[value].usedOtherFile = true;
156  }
157  mFunctionCalls.insert(value);
158  }
159  if (settings.library.isexportedsuffix(tok->str(), propToken->str())) {
160  const Token* prevPropToken = propToken->previous();
161  const std::string& value = prevPropToken->str();
162  if (value != ")" && mFunctions.find(value) != mFunctions.end()) {
163  mFunctions[value].usedOtherFile = true;
164  }
165  mFunctionCalls.insert(value);
166  }
167  propToken = propToken->next();
168  }
169  }
170 
171  if (doMarkup && settings.library.isimporter(FileName, tok->str()) && tok->next()) {
172  const Token * propToken = tok->next();
173  if (propToken->next()) {
174  propToken = propToken->next();
175  while (propToken && propToken->str() != ")") {
176  const std::string& value = propToken->str();
177  if (!value.empty()) {
178  mFunctions[value].usedOtherFile = true;
179  mFunctionCalls.insert(value);
180  break;
181  }
182  propToken = propToken->next();
183  }
184  }
185  }
186 
187  if (settings.library.isreflection(tok->str())) {
188  const int argIndex = settings.library.reflectionArgument(tok->str());
189  if (argIndex >= 0) {
190  const Token * funcToken = tok->next();
191  int index = 0;
192  std::string value;
193  while (funcToken) {
194  if (funcToken->str()==",") {
195  if (++index == argIndex)
196  break;
197  value.clear();
198  } else
199  value += funcToken->str();
200  funcToken = funcToken->next();
201  }
202  if (index == argIndex) {
203  value = value.substr(1, value.length() - 2);
204  mFunctions[value].usedOtherFile = true;
205  mFunctionCalls.insert(std::move(value));
206  }
207  }
208  }
209 
210  const Token *funcname = nullptr;
211 
212  if (doMarkup)
213  funcname = Token::Match(tok, "%name% (") ? tok : nullptr;
214  else if ((lambdaEndToken || tok->scope()->isExecutable()) && Token::Match(tok, "%name% (")) {
215  funcname = tok;
216  } else if ((lambdaEndToken || tok->scope()->isExecutable()) && Token::Match(tok, "%name% <") && Token::simpleMatch(tok->linkAt(1), "> (")) {
217  funcname = tok;
218  } else if (Token::Match(tok, "< %name%") && tok->link()) {
219  funcname = tok->next();
220  while (Token::Match(funcname, "%name% :: %name%"))
221  funcname = funcname->tokAt(2);
222  } else if (tok->scope()->type != Scope::ScopeType::eEnum && (Token::Match(tok, "[;{}.,()[=+-/|!?:]") || Token::Match(tok, "return|throw"))) {
223  funcname = tok->next();
224  if (funcname && funcname->str() == "&")
225  funcname = funcname->next();
226  if (funcname && funcname->str() == "::")
227  funcname = funcname->next();
228  while (Token::Match(funcname, "%name% :: %name%"))
229  funcname = funcname->tokAt(2);
230 
231  if (!Token::Match(funcname, "%name% [(),;]:}>]") || funcname->varId())
232  continue;
233  }
234 
235  if (!funcname || funcname->isKeyword() || funcname->isStandardType())
236  continue;
237 
238  // funcname ( => Assert that the end parentheses isn't followed by {
239  if (Token::Match(funcname, "%name% (|<") && funcname->linkAt(1)) {
240  const Token *ftok = funcname->next();
241  if (ftok->str() == "<")
242  ftok = ftok->link();
243  if (Token::Match(ftok->linkAt(1), ") const|throw|{"))
244  funcname = nullptr;
245  }
246 
247  if (funcname) {
248  const auto baseName = stripTemplateParameters(funcname->str());
249  FunctionUsage &func = mFunctions[baseName];
250  const std::string& called_from_file = tokenizer.list.getFiles()[funcname->fileIndex()];
251 
252  if (func.filename.empty() || func.filename == "+" || func.filename != called_from_file)
253  func.usedOtherFile = true;
254  else
255  func.usedSameFile = true;
256 
257  mFunctionCalls.insert(baseName);
258  }
259  }
260 }
261 
262 
263 static bool isOperatorFunction(const std::string & funcName)
264 {
265  /* Operator functions are invalid function names for C, so no need to check
266  * this in here. As result the returned error function might be incorrect.
267  *
268  * List of valid operators can be found at:
269  * http://en.cppreference.com/w/cpp/language/operators
270  *
271  * Conversion functions must be a member function (at least for gcc), so no
272  * need to cover them for unused functions.
273  *
274  * To speed up the comparison, not the whole list of operators is used.
275  * Instead only the character after the operator prefix is checked to be a
276  * none alpa numeric value, but the '_', to cover function names like
277  * "operator_unused". In addition the following valid operators are checked:
278  * - new
279  * - new[]
280  * - delete
281  * - delete[]
282  */
283  const std::string operatorPrefix = "operator";
284  if (funcName.compare(0, operatorPrefix.length(), operatorPrefix) != 0) {
285  return false;
286  }
287 
288  // Taking care of funcName == "operator", which is no valid operator
289  if (funcName.length() == operatorPrefix.length()) {
290  return false;
291  }
292 
293  const char firstOperatorChar = funcName[operatorPrefix.length()];
294  if (firstOperatorChar == '_') {
295  return false;
296  }
297 
298  if (!std::isalnum(firstOperatorChar)) {
299  return true;
300  }
301 
302  const std::vector<std::string> additionalOperators = {
303  "new", "new[]", "delete", "delete[]"
304  };
305 
306 
307  return std::find(additionalOperators.cbegin(), additionalOperators.cend(), funcName.substr(operatorPrefix.length())) != additionalOperators.cend();
308 }
309 
310 #define logChecker(id) \
311  do { \
312  const ErrorMessage errmsg({}, nullptr, Severity::internal, "logChecker", (id), CWE(0U), Certainty::normal); \
313  errorLogger.reportErr(errmsg); \
314  } while (false)
315 
316 bool CheckUnusedFunctions::check(const Settings& settings, ErrorLogger& errorLogger) const
317 {
318  logChecker("CheckUnusedFunctions::check"); // unusedFunction
319 
320  using ErrorParams = std::tuple<std::string, unsigned int, unsigned int, std::string>;
321  std::vector<ErrorParams> errors; // ensure well-defined order
322 
323  for (std::unordered_map<std::string, FunctionUsage>::const_iterator it = mFunctions.cbegin(); it != mFunctions.cend(); ++it) {
324  const FunctionUsage &func = it->second;
325  if (func.usedOtherFile || func.filename.empty())
326  continue;
327  if (settings.library.isentrypoint(it->first))
328  continue;
329  if (!func.usedSameFile) {
330  if (isOperatorFunction(it->first))
331  continue;
332  std::string filename;
333  if (func.filename != "+")
334  filename = func.filename;
335  errors.emplace_back(filename, func.fileIndex, func.lineNumber, it->first);
336  } else if (!func.usedOtherFile) {
337  /** @todo add error message "function is only used in <file> it can be static" */
338  /*
339  std::ostringstream errmsg;
340  errmsg << "The function '" << it->first << "' is only used in the file it was declared in so it should have local linkage.";
341  mErrorLogger->reportErr( errmsg.str() );
342  errors = true;
343  */
344  }
345  }
346  std::sort(errors.begin(), errors.end());
347  for (const auto& e : errors)
348  unusedFunctionError(errorLogger, std::get<0>(e), std::get<1>(e), std::get<2>(e), std::get<3>(e));
349  return !errors.empty();
350 }
351 
353  const std::string &filename, unsigned int fileIndex, unsigned int lineNumber,
354  const std::string &funcname)
355 {
356  std::list<ErrorMessage::FileLocation> locationList;
357  if (!filename.empty()) {
358  locationList.emplace_back(filename, lineNumber, 0);
359  locationList.back().fileIndex = fileIndex;
360  }
361 
362  const ErrorMessage errmsg(std::move(locationList), emptyString, Severity::style, "$symbol:" + funcname + "\nThe function '$symbol' is never used.", "unusedFunction", CWE561, Certainty::normal);
363  errorLogger.reportErr(errmsg);
364 }
365 
367  : functionName(f->name()), lineNumber(f->token->linenr())
368 {}
369 
371 {
372  std::ostringstream ret;
373  for (const FunctionDecl &functionDecl : mFunctionDecl) {
374  ret << " <functiondecl"
375  << " functionName=\"" << ErrorLogger::toxml(functionDecl.functionName) << '\"'
376  << " lineNumber=\"" << functionDecl.lineNumber << "\"/>\n";
377  }
378  for (const std::string &fc : mFunctionCalls) {
379  ret << " <functioncall functionName=\"" << ErrorLogger::toxml(fc) << "\"/>\n";
380  }
381  return ret.str();
382 }
383 
384 namespace {
385  struct Location {
386  Location() : lineNumber(0) {}
387  Location(std::string f, const int l) : fileName(std::move(f)), lineNumber(l) {}
388  std::string fileName;
389  int lineNumber;
390  };
391 }
392 
393 void CheckUnusedFunctions::analyseWholeProgram(const Settings &settings, ErrorLogger &errorLogger, const std::string &buildDir)
394 {
395  std::map<std::string, Location> decls;
396  std::set<std::string> calls;
397 
398  const std::string filesTxt(buildDir + "/files.txt");
399  std::ifstream fin(filesTxt.c_str());
400  std::string filesTxtLine;
401  while (std::getline(fin, filesTxtLine)) {
402  const std::string::size_type firstColon = filesTxtLine.find(':');
403  if (firstColon == std::string::npos)
404  continue;
405  const std::string::size_type secondColon = filesTxtLine.find(':', firstColon+1);
406  if (secondColon == std::string::npos)
407  continue;
408  const std::string xmlfile = buildDir + '/' + filesTxtLine.substr(0,firstColon);
409  const std::string sourcefile = filesTxtLine.substr(secondColon+1);
410 
411  tinyxml2::XMLDocument doc;
412  const tinyxml2::XMLError error = doc.LoadFile(xmlfile.c_str());
413  if (error != tinyxml2::XML_SUCCESS)
414  continue;
415 
416  const tinyxml2::XMLElement * const rootNode = doc.FirstChildElement();
417  if (rootNode == nullptr)
418  continue;
419 
420  for (const tinyxml2::XMLElement *e = rootNode->FirstChildElement(); e; e = e->NextSiblingElement()) {
421  if (std::strcmp(e->Name(), "FileInfo") != 0)
422  continue;
423  const char *checkattr = e->Attribute("check");
424  if (checkattr == nullptr || std::strcmp(checkattr,"CheckUnusedFunctions") != 0)
425  continue;
426  for (const tinyxml2::XMLElement *e2 = e->FirstChildElement(); e2; e2 = e2->NextSiblingElement()) {
427  const char* functionName = e2->Attribute("functionName");
428  if (functionName == nullptr)
429  continue;
430  if (std::strcmp(e2->Name(),"functioncall") == 0) {
431  calls.insert(functionName);
432  continue;
433  }
434  if (std::strcmp(e2->Name(),"functiondecl") == 0) {
435  const char* lineNumber = e2->Attribute("lineNumber");
436  if (lineNumber) {
437  // cppcheck-suppress templateInstantiation - TODO: fix this - see #11631
438  decls[functionName] = Location(sourcefile, strToInt<int>(lineNumber));
439  }
440  }
441  }
442  }
443  }
444 
445  for (std::map<std::string, Location>::const_iterator decl = decls.cbegin(); decl != decls.cend(); ++decl) {
446  const std::string &functionName = stripTemplateParameters(decl->first);
447 
448  if (settings.library.isentrypoint(functionName))
449  continue;
450 
451  if (calls.find(functionName) == calls.end() && !isOperatorFunction(functionName)) {
452  const Location &loc = decl->second;
453  unusedFunctionError(errorLogger, loc.fileName, /*fileIndex*/ 0, loc.lineNumber, functionName);
454  }
455  }
456 }
457 
459 {
460  for (const auto& entry : check.mFunctions)
461  {
462  FunctionUsage &usage = mFunctions[entry.first];
463  if (!usage.lineNumber)
464  usage.lineNumber = entry.second.lineNumber;
465  // TODO: why always overwrite this but not the filename and line?
466  usage.fileIndex = entry.second.fileIndex;
467  if (usage.filename.empty())
468  usage.filename = entry.second.filename;
469  // cppcheck-suppress bitwiseOnBoolean - TODO: FP
470  usage.usedOtherFile |= entry.second.usedOtherFile;
471  // cppcheck-suppress bitwiseOnBoolean - TODO: FP
472  usage.usedSameFile |= entry.second.usedSameFile;
473  }
474  mFunctionDecl.insert(mFunctionDecl.cend(), check.mFunctionDecl.cbegin(), check.mFunctionDecl.cend());
475  mFunctionCalls.insert(check.mFunctionCalls.cbegin(), check.mFunctionCalls.cend());
476 }
const Token * findLambdaEndToken(const Token *first)
find lambda function end token
Definition: astutils.cpp:3190
#define logChecker(id)
static bool isOperatorFunction(const std::string &funcName)
static const CWE CWE561(561U)
static std::string stripTemplateParameters(const std::string &funcName)
Check for functions never called.
std::unordered_map< std::string, FunctionUsage > mFunctions
void parseTokens(const Tokenizer &tokenizer, const Settings &settings)
static void unusedFunctionError(ErrorLogger &errorLogger, const std::string &filename, unsigned int fileIndex, unsigned int lineNumber, const std::string &funcname)
static void analyseWholeProgram(const Settings &settings, ErrorLogger &errorLogger, const std::string &buildDir)
std::list< FunctionDecl > mFunctionDecl
void updateFunctionData(const CheckUnusedFunctions &check)
std::string analyzerInfo() const
bool check(const Settings &settings, ErrorLogger &errorLogger) const
std::set< std::string > mFunctionCalls
This is an interface, which the class responsible of error logging should implement.
Definition: errorlogger.h:214
virtual void reportErr(const ErrorMessage &msg)=0
Information about found errors and warnings is directed here.
static std::string toxml(const std::string &str)
Convert XML-sensitive characters into XML entities.
Wrapper for error messages, provided by reportErr()
Definition: errorlogger.h:48
const std::string & name() const
bool isAttributeConstructor() const
Type type
constructor, destructor, ...
bool isOperator() const
bool isExtern() const
const Token * token
function name token in implementation
bool isAttributeDestructor() const
bool isexecutableblock(const std::string &file, const std::string &token) const
Definition: library.cpp:1594
bool isexportedsuffix(const std::string &prefix, const std::string &token) const
Definition: library.h:406
bool isexporter(const std::string &prefix) const
Definition: library.h:397
const std::string & blockstart(const std::string &file) const
Definition: library.cpp:1612
bool isentrypoint(const std::string &func) const
Definition: library.h:427
const std::string & blockend(const std::string &file) const
Definition: library.cpp:1623
bool isreflection(const std::string &token) const
Definition: library.h:416
bool iskeyword(const std::string &file, const std::string &keyword) const
Definition: library.cpp:1634
bool markupFile(const std::string &path) const
Definition: library.cpp:1577
bool isimporter(const std::string &file, const std::string &importer) const
Definition: library.cpp:1641
bool isexportedprefix(const std::string &prefix, const std::string &token) const
Definition: library.h:401
int blockstartoffset(const std::string &file) const
Definition: library.cpp:1600
int reflectionArgument(const std::string &token) const
Definition: library.h:420
Function * function
function info for this function
This is just a container for general settings so that we don't need to pass individual values to func...
Definition: settings.h:95
Library library
Library.
Definition: settings.h:237
std::vector< const Scope * > functionScopes
Fast access to function scopes.
const std::string & file(const Token *tok) const
get filename for given token
const std::vector< std::string > & getFiles() const
Get filenames (the sourcefile + the files it include).
Definition: tokenlist.h:141
The token list that the TokenList generates is a linked-list of this class.
Definition: token.h:150
void str(T &&s)
Definition: token.h:179
static bool Match(const Token *tok, const char pattern[], nonneg int varid=0)
Match given token (or list of tokens) to a pattern list.
Definition: token.cpp:722
bool isKeyword() const
Definition: token.h:358
nonneg int varId() const
Definition: token.h:870
const Token * tokAt(int index) const
Definition: token.cpp:427
void scope(const Scope *s)
Associate this token with given scope.
Definition: token.h:1042
void link(Token *linkToToken)
Create link to given token.
Definition: token.h:1015
const Token * linkAt(int index) const
Definition: token.cpp:447
Token * previous()
Definition: token.h:862
nonneg int linenr() const
Definition: token.h:816
bool isStandardType() const
Definition: token.h:449
Token * next()
Definition: token.h:830
static bool simpleMatch(const Token *tok, const char(&pattern)[count])
Match given token (or list of tokens) to a pattern list.
Definition: token.h:252
nonneg int fileIndex() const
Definition: token.h:809
The main purpose is to tokenize the source code.
Definition: tokenize.h:46
const Token * tokens() const
Definition: tokenize.h:592
TokenList list
Token list: stores all tokens.
Definition: tokenize.h:590
const SymbolDatabase * getSymbolDatabase() const
Definition: tokenize.h:563
static const std::string emptyString
Definition: config.h:127
@ style
Style warning.
@ error
Programming error.