cppcheck/lib/cppcheck.cpp
Stefan Weil 785bc3d04b Optimize handling of --append=<file> option (execution time, memory)
Whitespace was removed from the append data for every configuration
and every file: n(configuration) * n(files).

Removing whitespace immediately after the append data was read can
significantly reduce the execution time and memory usage.
It also allows further improvement because copies to the temporary
object appendCode are no longer needed.

Signed-off-by: Stefan Weil <sw@weilnetz.de>
2011-11-15 19:27:36 +01:00

521 lines
17 KiB
C++

/*
* Cppcheck - A tool for static C/C++ code analysis
* Copyright (C) 2007-2011 Daniel Marjamäki and Cppcheck team.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "cppcheck.h"
#include "preprocessor.h" // preprocessor.
#include "tokenize.h" // <- Tokenizer
#include "check.h"
#include "path.h"
#include <algorithm>
#include <iostream>
#include <fstream>
#include <stdexcept>
#include <ctime>
#include "timer.h"
#ifdef HAVE_RULES
#define PCRE_STATIC
#include <pcre.h>
#endif
static const char Version[] = "1.51";
static const char ExtraVersion[] = "";
static TimerResults S_timerResults;
CppCheck::CppCheck(ErrorLogger &errorLogger, bool useGlobalSuppressions)
: _useGlobalSuppressions(useGlobalSuppressions), _errorLogger(errorLogger)
{
exitcode = 0;
}
CppCheck::~CppCheck()
{
if (_settings._showtime != SHOWTIME_NONE)
S_timerResults.ShowResults();
}
void CppCheck::settings(const Settings &currentSettings)
{
_settings = currentSettings;
}
const char * CppCheck::version()
{
return Version;
}
const char * CppCheck::extraVersion()
{
return ExtraVersion;
}
unsigned int CppCheck::check(const std::string &path)
{
_filename = path;
return processFile();
}
unsigned int CppCheck::check(const std::string &path, const std::string &content)
{
_filename = path;
_fileContent = content;
const unsigned int retval = processFile();
_fileContent.clear();
return retval;
}
std::string CppCheck::replaceAll(std::string code, const std::string &from, const std::string &to)
{
size_t pos = 0;
while ((pos = code.find(from, pos)) != std::string::npos) {
code.replace(pos, from.length(), to);
pos += to.length();
}
return code;
}
bool CppCheck::findError(std::string code, const char FileName[])
{
// First make sure that error occurs with the original code
checkFile(code, FileName);
if (_errorList.empty()) {
// Error does not occur with this code
return false;
}
std::string previousCode = code;
std::string error = _errorList.front();
for (;;) {
// Try to remove included files from the source
size_t found=previousCode.rfind("\n#endfile");
if (found == std::string::npos) {
// No modifications can be done to the code
} else {
// Modify code and re-check it to see if error
// is still there.
code = previousCode.substr(found+9);
_errorList.clear();
checkFile(code, FileName);
}
if (_errorList.empty()) {
// Latest code didn't fail anymore. Fall back
// to previous code
code = previousCode;
} else {
error = _errorList.front();
}
// Add '\n' so that "\n#file" on first line would be found
code = "// " + error + "\n" + code;
code = replaceAll(code, "\n#file", "\n// #file");
code = replaceAll(code, "\n#endfile", "\n// #endfile");
// We have reduced the code as much as we can. Print out
// the code and quit.
_errorLogger.reportOut(code);
break;
}
return true;
}
unsigned int CppCheck::processFile()
{
exitcode = 0;
// TODO: Should this be moved out to its own function so all the files can be
// analysed before any files are checked?
if (_settings.test_2_pass && _settings._jobs == 1) {
const std::string printname = Path::toNativeSeparators(_filename);
reportOut("Analysing " + printname + "...");
std::ifstream f(_filename.c_str());
analyseFile(f, _filename);
}
_errout.str("");
if (_settings.terminated())
return exitcode;
if (_settings._errorsOnly == false) {
std::string fixedpath(_filename);
fixedpath = Path::simplifyPath(fixedpath.c_str());
fixedpath = Path::toNativeSeparators(fixedpath);
_errorLogger.reportOut(std::string("Checking ") + fixedpath + std::string("..."));
}
try {
Preprocessor preprocessor(&_settings, this);
std::list<std::string> configurations;
std::string filedata = "";
if (!_fileContent.empty()) {
// File content was given as a string
std::istringstream iss(_fileContent);
preprocessor.preprocess(iss, filedata, configurations, _filename, _settings._includePaths);
} else {
// Only file name was given, read the content from file
std::ifstream fin(_filename.c_str());
Timer t("Preprocessor::preprocess", _settings._showtime, &S_timerResults);
preprocessor.preprocess(fin, filedata, configurations, _filename, _settings._includePaths);
}
if (_settings.checkConfiguration) {
return 0;
}
_settings.ifcfg = bool(configurations.size() > 1);
if (!_settings.userDefines.empty()) {
configurations.clear();
configurations.push_back(_settings.userDefines);
}
int checkCount = 0;
for (std::list<std::string>::const_iterator it = configurations.begin(); it != configurations.end(); ++it) {
// Check only a few configurations (default 12), after that bail out, unless --force
// was used.
if (!_settings._force && checkCount >= _settings._maxConfigs) {
const std::string fixedpath = Path::toNativeSeparators(_filename);
ErrorLogger::ErrorMessage::FileLocation location;
location.setfile(fixedpath);
std::list<ErrorLogger::ErrorMessage::FileLocation> loclist;
loclist.push_back(location);
const std::string msg("Interrupted checking because of too many #ifdef configurations.\n"
"The checking of the file was interrupted because there were too many "
"#ifdef configurations. Checking of all #ifdef configurations can be forced "
"by --force command line option or from GUI preferences. However that may "
"increase the checking time.");
ErrorLogger::ErrorMessage errmsg(loclist,
Severity::information,
msg,
"toomanyconfigs",
false);
if (!_settings.nomsg.isSuppressedLocal(errmsg._id, fixedpath, location.line)) {
reportErr(errmsg);
}
break;
}
cfg = *it;
Timer t("Preprocessor::getcode", _settings._showtime, &S_timerResults);
const std::string codeWithoutCfg = Preprocessor::getcode(filedata, *it, _filename, &_settings, &_errorLogger);
t.Stop();
// If only errors are printed, print filename after the check
if (_settings._errorsOnly == false && it != configurations.begin()) {
std::string fixedpath = Path::simplifyPath(_filename.c_str());
fixedpath = Path::toNativeSeparators(fixedpath);
_errorLogger.reportOut(std::string("Checking ") + fixedpath + ": " + cfg + std::string("..."));
}
const std::string &appendCode = _settings.append();
if (_settings.debugFalsePositive) {
if (findError(codeWithoutCfg + appendCode, _filename.c_str())) {
return exitcode;
}
} else {
checkFile(codeWithoutCfg + appendCode, _filename.c_str());
}
++checkCount;
}
} catch (std::runtime_error &e) {
// Exception was thrown when checking this file..
const std::string fixedpath = Path::toNativeSeparators(_filename);
_errorLogger.reportOut("Bailing out from checking " + fixedpath + ": " + e.what());
}
if (!_settings._errorsOnly)
reportUnmatchedSuppressions(_settings.nomsg.getUnmatchedLocalSuppressions(_filename));
_errorList.clear();
return exitcode;
}
void CppCheck::checkFunctionUsage()
{
// This generates false positives - especially for libraries
if (_settings.isEnabled("unusedFunction") && _settings._jobs == 1) {
const bool verbose_orig = _settings._verbose;
_settings._verbose = false;
_errout.str("");
if (_settings._errorsOnly == false)
_errorLogger.reportOut("Checking usage of global functions..");
_checkUnusedFunctions.check(this);
_settings._verbose = verbose_orig;
}
}
void CppCheck::analyseFile(std::istream &fin, const std::string &filename)
{
// Preprocess file..
Preprocessor preprocessor(&_settings, this);
std::list<std::string> configurations;
std::string filedata = "";
preprocessor.preprocess(fin, filedata, configurations, filename, _settings._includePaths);
const std::string code = Preprocessor::getcode(filedata, "", filename, &_settings, &_errorLogger);
if (_settings.checkConfiguration) {
return;
}
// Tokenize..
Tokenizer tokenizer(&_settings, this);
std::istringstream istr(code);
tokenizer.tokenize(istr, filename.c_str(), "");
tokenizer.simplifyTokenList();
// Analyse the tokens..
std::set<std::string> data;
for (std::list<Check *>::const_iterator it = Check::instances().begin(); it != Check::instances().end(); ++it) {
(*it)->analyse(tokenizer.tokens(), data);
}
// Save analysis results..
// TODO: This loop should be protected by a mutex or something like that
// The saveAnalysisData must _not_ be called from many threads at the same time.
for (std::list<Check *>::const_iterator it = Check::instances().begin(); it != Check::instances().end(); ++it) {
(*it)->saveAnalysisData(data);
}
}
//---------------------------------------------------------------------------
// CppCheck - A function that checks a specified file
//---------------------------------------------------------------------------
void CppCheck::checkFile(const std::string &code, const char FileName[])
{
if (_settings.terminated() || _settings.checkConfiguration)
return;
Tokenizer _tokenizer(&_settings, this);
bool result;
// Tokenize the file
std::istringstream istr(code);
Timer timer("Tokenizer::tokenize", _settings._showtime, &S_timerResults);
result = _tokenizer.tokenize(istr, FileName, cfg);
timer.Stop();
if (!result) {
// File had syntax errors, abort
return;
}
Timer timer2("Tokenizer::fillFunctionList", _settings._showtime, &S_timerResults);
_tokenizer.fillFunctionList();
timer2.Stop();
// call all "runChecks" in all registered Check classes
for (std::list<Check *>::iterator it = Check::instances().begin(); it != Check::instances().end(); ++it) {
if (_settings.terminated())
return;
Timer timerRunChecks((*it)->name() + "::runChecks", _settings._showtime, &S_timerResults);
(*it)->runChecks(&_tokenizer, &_settings, this);
}
Timer timer3("Tokenizer::simplifyTokenList", _settings._showtime, &S_timerResults);
result = _tokenizer.simplifyTokenList();
timer3.Stop();
if (!result)
return;
Timer timer4("Tokenizer::fillFunctionList", _settings._showtime, &S_timerResults);
_tokenizer.fillFunctionList();
timer4.Stop();
if (_settings.isEnabled("unusedFunction") && _settings._jobs == 1)
_checkUnusedFunctions.parseTokens(_tokenizer);
// call all "runSimplifiedChecks" in all registered Check classes
for (std::list<Check *>::iterator it = Check::instances().begin(); it != Check::instances().end(); ++it) {
if (_settings.terminated())
return;
Timer timerSimpleChecks((*it)->name() + "::runSimplifiedChecks", _settings._showtime, &S_timerResults);
(*it)->runSimplifiedChecks(&_tokenizer, &_settings, this);
}
#ifdef HAVE_RULES
// Are there extra rules?
if (!_settings.rules.empty()) {
std::ostringstream ostr;
for (const Token *tok = _tokenizer.tokens(); tok; tok = tok->next())
ostr << " " << tok->str();
const std::string str(ostr.str());
for (std::list<Settings::Rule>::const_iterator it = _settings.rules.begin(); it != _settings.rules.end(); ++it) {
const Settings::Rule &rule = *it;
if (rule.pattern.empty() || rule.id.empty() || rule.severity.empty())
continue;
const char *error = 0;
int erroffset = 0;
pcre *re = pcre_compile(rule.pattern.c_str(),0,&error,&erroffset,NULL);
if (!re && error) {
ErrorLogger::ErrorMessage errmsg(std::list<ErrorLogger::ErrorMessage::FileLocation>(),
Severity::error,
error,
"pcre_compile",
false);
reportErr(errmsg);
}
if (!re)
continue;
int pos = 0;
int ovector[30];
while (0 <= pcre_exec(re, NULL, str.c_str(), (int)str.size(), pos, 0, ovector, 30)) {
unsigned int pos1 = (unsigned int)ovector[0];
unsigned int pos2 = (unsigned int)ovector[1];
// jump to the end of the match for the next pcre_exec
pos = (int)pos2;
// determine location..
ErrorLogger::ErrorMessage::FileLocation loc;
loc.setfile(_tokenizer.getFiles()->front());
loc.line = 0;
unsigned int len = 0;
for (const Token *tok = _tokenizer.tokens(); tok; tok = tok->next()) {
len = len + 1 + tok->str().size();
if (len > pos1) {
loc.setfile(_tokenizer.getFiles()->at(tok->fileIndex()));
loc.line = tok->linenr();
break;
}
}
const std::list<ErrorLogger::ErrorMessage::FileLocation> callStack(1, loc);
// Create error message
std::string summary;
if (rule.summary.empty())
summary = "found '" + str.substr(pos1, pos2 - pos1) + "'";
else
summary = rule.summary;
const ErrorLogger::ErrorMessage errmsg(callStack, Severity::fromString(rule.severity), summary, rule.id, false);
// Report error
reportErr(errmsg);
}
pcre_free(re);
}
}
#endif
}
Settings &CppCheck::settings()
{
return _settings;
}
//---------------------------------------------------------------------------
void CppCheck::reportErr(const ErrorLogger::ErrorMessage &msg)
{
const std::string errmsg = msg.toString(_settings._verbose);
if (errmsg.empty())
return;
if (_settings.debugFalsePositive) {
// Don't print out error
_errorList.push_back(errmsg);
return;
}
// Alert only about unique errors
if (std::find(_errorList.begin(), _errorList.end(), errmsg) != _errorList.end())
return;
std::string file;
unsigned int line(0);
if (!msg._callStack.empty()) {
file = msg._callStack.back().getfile(false);
line = msg._callStack.back().line;
}
if (_useGlobalSuppressions) {
if (_settings.nomsg.isSuppressed(msg._id, file, line))
return;
} else {
if (_settings.nomsg.isSuppressedLocal(msg._id, file, line))
return;
}
if (!_settings.nofail.isSuppressed(msg._id, file, line))
exitcode = 1;
_errorList.push_back(errmsg);
std::string errmsg2(errmsg);
if (_settings._verbose) {
errmsg2 += "\n Defines=\'" + cfg + "\'\n";
}
_errorLogger.reportErr(msg);
_errout << errmsg2 << std::endl;
}
void CppCheck::reportOut(const std::string &outmsg)
{
_errorLogger.reportOut(outmsg);
}
void CppCheck::reportProgress(const std::string &filename, const char stage[], const unsigned int value)
{
_errorLogger.reportProgress(filename, stage, value);
}
void CppCheck::reportStatus(unsigned int /*fileindex*/, unsigned int /*filecount*/, long /*sizedone*/, long /*sizetotal*/)
{
}
void CppCheck::getErrorMessages()
{
// call all "getErrorMessages" in all registered Check classes
for (std::list<Check *>::iterator it = Check::instances().begin(); it != Check::instances().end(); ++it)
(*it)->getErrorMessages(this, &_settings);
Tokenizer tokenizer(&_settings, 0);
tokenizer.getErrorMessages(this, &_settings);
Preprocessor::getErrorMessages(this, &_settings);
}