cppcheck/lib/cppcheck.cpp
Kimmo Varis 68c52ddd69 Refactoring information messages.
Currently the information severity messages are outputted as error
messages with Severity::Information. This causes constant confusion
as people think it as mildest error severity (and rightfully so).
When it was meant to be for printing messages about the checking
procedure itself (like missing header files etc).

So I'm adding a new function for the ErrorLogger for printing these
informative messages. This makes clear the distinction of errors
found from the code and messages related to the checking itself.
It also makes it easier for clients to handle these separately.
2012-06-19 00:16:20 +03:00

535 lines
19 KiB
C++

/*
* Cppcheck - A tool for static C/C++ code analysis
* Copyright (C) 2007-2012 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 <fstream>
#include <sstream>
#include <stdexcept>
#include "timer.h"
#ifdef HAVE_RULES
#define PCRE_STATIC
#include <pcre.h>
#endif
static const char Version[] = "1.55 dev";
static const char ExtraVersion[] = "";
static TimerResults S_timerResults;
CppCheck::CppCheck(ErrorLogger &errorLogger, bool useGlobalSuppressions)
: _errorLogger(errorLogger), exitcode(0), _useGlobalSuppressions(useGlobalSuppressions)
{
}
CppCheck::~CppCheck()
{
if (_settings._showtime != SHOWTIME_NONE)
S_timerResults.ShowResults();
}
const char * CppCheck::version()
{
return Version;
}
const char * CppCheck::extraVersion()
{
return ExtraVersion;
}
unsigned int CppCheck::check(const std::string &path)
{
return processFile(path);
}
unsigned int CppCheck::check(const std::string &path, const std::string &content)
{
_fileContent = content;
const unsigned int retval = processFile(path);
_fileContent.clear();
return retval;
}
void 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();
}
}
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;
replaceAll(code, "\n#file", "\n// #file");
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(const std::string& filename)
{
exitcode = 0;
// only show debug warnings for C/C++ source files (don't fix
// debug warnings for java/c#/etc files)
if (!Path::acceptFile(filename))
_settings.debugwarnings = false;
if (_settings.terminated())
return exitcode;
if (_settings._errorsOnly == false) {
std::string fixedpath = Path::simplifyPath(filename.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;
}
if (!_settings.userDefines.empty()) {
configurations.clear();
configurations.push_back(_settings.userDefines);
}
if (!_settings._force && configurations.size() > _settings._maxConfigs) {
const std::string fixedpath = Path::toNativeSeparators(filename);
ErrorLogger::ErrorMessage::FileLocation location;
location.setfile(fixedpath);
const std::list<ErrorLogger::ErrorMessage::FileLocation> loclist(1, location);
std::ostringstream msg;
msg << "Too many #ifdef configurations - cppcheck will only check " << _settings._maxConfigs << " of " << configurations.size() << ".\n"
"The checking of the file will be interrupted because there are 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.str(),
"toomanyconfigs",
false);
reportErr(errmsg);
}
unsigned 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);
ErrorLogger::ErrorMessage errmsg(loclist,
Severity::information,
"Interrupted checking because of too many #ifdef configurations.",
"toomanyconfigs",
false);
reportInfo(errmsg);
break;
}
cfg = *it;
Timer t("Preprocessor::getcode", _settings._showtime, &S_timerResults);
const std::string codeWithoutCfg = preprocessor.getcode(filedata, *it, filename);
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 (const 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;
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);
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);
if (_settings._showtime != SHOWTIME_NONE)
_tokenizer.setTimerResults(&S_timerResults);
try {
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;
}
// Update the _dependencies..
if (_tokenizer.list.getFiles().size() >= 2)
_dependencies.insert(_tokenizer.list.getFiles().begin()+1, _tokenizer.list.getFiles().end());
// 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);
}
if (_settings.isEnabled("unusedFunction") && _settings._jobs == 1)
_checkUnusedFunctions.parseTokens(_tokenizer);
Timer timer3("Tokenizer::simplifyTokenList", _settings._showtime, &S_timerResults);
result = _tokenizer.simplifyTokenList();
timer3.Stop();
if (!result)
return;
// 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.getSourceFilePath());
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.list.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
} catch (const InternalError &e) {
std::list<ErrorLogger::ErrorMessage::FileLocation> locationList;
ErrorLogger::ErrorMessage::FileLocation loc2;
loc2.setfile(Path::toNativeSeparators(FileName));
locationList.push_back(loc2);
ErrorLogger::ErrorMessage::FileLocation loc;
if (e.token) {
loc.line = e.token->linenr();
const std::string fixedpath = Path::toNativeSeparators(_tokenizer.list.file(e.token));
loc.setfile(fixedpath);
} else {
loc.setfile(_tokenizer.getSourceFilePath());
}
locationList.push_back(loc);
const ErrorLogger::ErrorMessage errmsg(locationList,
Severity::error,
e.errorMessage,
"cppcheckError",
false);
_errorLogger.reportErr(errmsg);
}
}
Settings &CppCheck::settings()
{
return _settings;
}
//---------------------------------------------------------------------------
void CppCheck::reportErr(const ErrorLogger::ErrorMessage &msg)
{
std::string errmsg = msg.toString(_settings._verbose);
if (errmsg.empty())
return;
// Alert only about unique errors
if (std::find(_errorList.begin(), _errorList.end(), errmsg) != _errorList.end())
return;
if (_settings.debugFalsePositive) {
// Don't print out error
_errorList.push_back(errmsg);
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);
if (_settings._verbose) {
errmsg += "\n Defines=\'" + cfg + "\'\n";
}
_errorLogger.reportErr(msg);
}
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::reportInfo(const ErrorLogger::ErrorMessage &msg)
{
_errorLogger.reportInfo(msg);
}
void CppCheck::reportStatus(unsigned int /*fileindex*/, unsigned int /*filecount*/, size_t /*sizedone*/, size_t /*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);
}