cppcheck/lib/cppcheck.cpp

1863 lines
74 KiB
C++
Raw Permalink Normal View History

2010-04-13 19:25:08 +02:00
/*
* Cppcheck - A tool for static C/C++ code analysis
2023-01-28 10:16:34 +01:00
* Copyright (C) 2007-2023 Cppcheck team.
2010-04-13 19:25:08 +02:00
*
* 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/>.
*/
2010-04-13 19:25:08 +02:00
#include "cppcheck.h"
#include "addoninfo.h"
2010-04-13 19:25:08 +02:00
#include "check.h"
2016-10-29 12:18:11 +02:00
#include "checkunusedfunctions.h"
#include "clangimport.h"
2021-07-08 21:21:35 +02:00
#include "color.h"
2018-12-25 21:11:23 +01:00
#include "ctu.h"
#include "errortypes.h"
#include "filesettings.h"
2017-05-27 04:33:47 +02:00
#include "library.h"
#include "path.h"
#include "platform.h"
#include "preprocessor.h"
#include "standards.h"
2017-05-27 04:33:47 +02:00
#include "suppressions.h"
#include "timer.h"
#include "token.h"
#include "tokenize.h"
2017-05-27 04:33:47 +02:00
#include "tokenlist.h"
#include "utils.h"
#include "valueflow.h"
#include "version.h"
2016-10-29 12:18:11 +02:00
2010-04-13 19:25:08 +02:00
#include <algorithm>
#include <cassert>
#include <cstdio>
#include <cstdint>
2017-05-27 04:33:47 +02:00
#include <cstring>
#include <cctype>
#include <cstdlib>
#include <ctime>
#include <exception>
#include <fstream>
#include <iostream> // <- TEMPORARY
2017-05-27 04:33:47 +02:00
#include <new>
#include <set>
#include <sstream> // IWYU pragma: keep
#include <stdexcept>
#include <string>
#include <unordered_set>
#include <utility>
2017-05-27 04:33:47 +02:00
#include <vector>
dump: Fix concurrency problem with dump files (#4757) * dump: Fix concurrency problem with dump files This adds the process ID for the cppcheck process to the filenames of the .dump and .ctu-info files that the process generates. So lib/cppcheck.cpp.dump becomes lib/cppcheck.cpp.<PID>.dump For example: lib/cppcheck.cpp.2637871.dump The reason for this change is that if there is a buildsystem which supports concurrency, multiple instances of cppcheck may be run for the same file. For example, if the same file is compiled in multiple build variants, or for multiple targets. If running the MISRA plugin over such a project with concurrency enabled in the buildsystem, the plugin ends up crashing as multiple jobs attempt to create/trample/delete the same files while other jobs are using them. For more information see: https://sourceforge.net/p/cppcheck/discussion/general/thread/02c757b4af/ * dump: Include pid in filename if dump not explicit Only change the dump and ctu-info filenames to include the PID if they are being generated due to an addon. This means that existing scripts that use `--dump` will still work if they depend on the previous naming behaviour. The more robust filenames containing the pid will be used when the dump files are used as an internal implementation detail for passing data to addons. However this means that anything that does explicitly use `--dump` will be susceptible to concurrency problems. * test: Update addon dump file test to account for pid This test causes a dump file to be created by enabling the misra addon. Since the dump files now include the cppcheck process pid this test had to be updated to account for the change.
2023-02-13 20:54:21 +01:00
#ifndef _WIN32
#include <unistd.h>
#else
#include <process.h>
#endif
#include "json.h"
#include <simplecpp.h>
#include "xml.h"
2010-04-13 19:25:08 +02:00
#ifdef HAVE_RULES
#ifdef _WIN32
2010-12-12 11:56:22 +01:00
#define PCRE_STATIC
#endif
2010-12-12 11:56:22 +01:00
#include <pcre.h>
2010-12-31 10:24:51 +01:00
#endif
2010-12-12 11:56:22 +01:00
class SymbolDatabase;
static constexpr char Version[] = CPPCHECK_VERSION_STRING;
static constexpr char ExtraVersion[] = "";
static constexpr char FILELIST[] = "cppcheck-addon-ctu-file-list";
static TimerResults s_timerResults;
2010-04-13 19:25:08 +02:00
// CWE ids used
static const CWE CWE398(398U); // Indicator of Poor Code Quality
// File deleter
namespace {
class FilesDeleter {
public:
FilesDeleter() = default;
~FilesDeleter() {
for (const std::string& fileName: mFilenames)
std::remove(fileName.c_str());
}
void addFile(const std::string& fileName) {
mFilenames.push_back(fileName);
}
private:
std::vector<std::string> mFilenames;
};
}
static std::string cmdFileName(std::string f)
{
2020-05-19 08:14:56 +02:00
f = Path::toNativeSeparators(f);
if (f.find(' ') != std::string::npos)
return "\"" + f + "\"";
return f;
}
static std::vector<std::string> split(const std::string &str, const std::string &sep=" ")
2020-05-19 08:14:56 +02:00
{
std::vector<std::string> ret;
for (std::string::size_type startPos = 0U; startPos < str.size();) {
startPos = str.find_first_not_of(sep, startPos);
if (startPos == std::string::npos)
break;
if (str[startPos] == '\"') {
const std::string::size_type endPos = str.find('\"', startPos + 1);
ret.push_back(str.substr(startPos + 1, endPos - startPos - 1));
startPos = (endPos < str.size()) ? (endPos + 1) : endPos;
continue;
}
const std::string::size_type endPos = str.find(sep, startPos + 1);
ret.push_back(str.substr(startPos, endPos - startPos));
startPos = endPos;
}
return ret;
2020-05-19 08:14:56 +02:00
}
dump: Fix concurrency problem with dump files (#4757) * dump: Fix concurrency problem with dump files This adds the process ID for the cppcheck process to the filenames of the .dump and .ctu-info files that the process generates. So lib/cppcheck.cpp.dump becomes lib/cppcheck.cpp.<PID>.dump For example: lib/cppcheck.cpp.2637871.dump The reason for this change is that if there is a buildsystem which supports concurrency, multiple instances of cppcheck may be run for the same file. For example, if the same file is compiled in multiple build variants, or for multiple targets. If running the MISRA plugin over such a project with concurrency enabled in the buildsystem, the plugin ends up crashing as multiple jobs attempt to create/trample/delete the same files while other jobs are using them. For more information see: https://sourceforge.net/p/cppcheck/discussion/general/thread/02c757b4af/ * dump: Include pid in filename if dump not explicit Only change the dump and ctu-info filenames to include the PID if they are being generated due to an addon. This means that existing scripts that use `--dump` will still work if they depend on the previous naming behaviour. The more robust filenames containing the pid will be used when the dump files are used as an internal implementation detail for passing data to addons. However this means that anything that does explicitly use `--dump` will be susceptible to concurrency problems. * test: Update addon dump file test to account for pid This test causes a dump file to be created by enabling the misra addon. Since the dump files now include the cppcheck process pid this test had to be updated to account for the change.
2023-02-13 20:54:21 +01:00
static int getPid()
{
#ifndef _WIN32
return getpid();
#else
return _getpid();
#endif
}
2021-07-07 10:58:13 +02:00
static std::string getDumpFileName(const Settings& settings, const std::string& filename)
{
if (!settings.dumpFile.empty())
return settings.dumpFile;
dump: Fix concurrency problem with dump files (#4757) * dump: Fix concurrency problem with dump files This adds the process ID for the cppcheck process to the filenames of the .dump and .ctu-info files that the process generates. So lib/cppcheck.cpp.dump becomes lib/cppcheck.cpp.<PID>.dump For example: lib/cppcheck.cpp.2637871.dump The reason for this change is that if there is a buildsystem which supports concurrency, multiple instances of cppcheck may be run for the same file. For example, if the same file is compiled in multiple build variants, or for multiple targets. If running the MISRA plugin over such a project with concurrency enabled in the buildsystem, the plugin ends up crashing as multiple jobs attempt to create/trample/delete the same files while other jobs are using them. For more information see: https://sourceforge.net/p/cppcheck/discussion/general/thread/02c757b4af/ * dump: Include pid in filename if dump not explicit Only change the dump and ctu-info filenames to include the PID if they are being generated due to an addon. This means that existing scripts that use `--dump` will still work if they depend on the previous naming behaviour. The more robust filenames containing the pid will be used when the dump files are used as an internal implementation detail for passing data to addons. However this means that anything that does explicitly use `--dump` will be susceptible to concurrency problems. * test: Update addon dump file test to account for pid This test causes a dump file to be created by enabling the misra addon. Since the dump files now include the cppcheck process pid this test had to be updated to account for the change.
2023-02-13 20:54:21 +01:00
std::string extension;
if (settings.dump)
extension = ".dump";
else
extension = "." + std::to_string(getPid()) + ".dump";
2021-07-07 10:58:13 +02:00
if (!settings.dump && !settings.buildDir.empty())
dump: Fix concurrency problem with dump files (#4757) * dump: Fix concurrency problem with dump files This adds the process ID for the cppcheck process to the filenames of the .dump and .ctu-info files that the process generates. So lib/cppcheck.cpp.dump becomes lib/cppcheck.cpp.<PID>.dump For example: lib/cppcheck.cpp.2637871.dump The reason for this change is that if there is a buildsystem which supports concurrency, multiple instances of cppcheck may be run for the same file. For example, if the same file is compiled in multiple build variants, or for multiple targets. If running the MISRA plugin over such a project with concurrency enabled in the buildsystem, the plugin ends up crashing as multiple jobs attempt to create/trample/delete the same files while other jobs are using them. For more information see: https://sourceforge.net/p/cppcheck/discussion/general/thread/02c757b4af/ * dump: Include pid in filename if dump not explicit Only change the dump and ctu-info filenames to include the PID if they are being generated due to an addon. This means that existing scripts that use `--dump` will still work if they depend on the previous naming behaviour. The more robust filenames containing the pid will be used when the dump files are used as an internal implementation detail for passing data to addons. However this means that anything that does explicitly use `--dump` will be susceptible to concurrency problems. * test: Update addon dump file test to account for pid This test causes a dump file to be created by enabling the misra addon. Since the dump files now include the cppcheck process pid this test had to be updated to account for the change.
2023-02-13 20:54:21 +01:00
return AnalyzerInformation::getAnalyzerInfoFile(settings.buildDir, filename, emptyString) + extension;
return filename + extension;
2021-07-07 10:58:13 +02:00
}
static std::string getCtuInfoFileName(const std::string &dumpFile)
{
return dumpFile.substr(0, dumpFile.size()-4) + "ctu-info";
}
static void createDumpFile(const Settings& settings,
const std::string& filename,
std::ofstream& fdump,
std::string& dumpFile)
{
if (!settings.dump && settings.addons.empty())
return;
2021-07-07 10:58:13 +02:00
dumpFile = getDumpFileName(settings, filename);
fdump.open(dumpFile);
if (!fdump.is_open())
return;
2021-07-07 10:58:13 +02:00
{
std::ofstream fout(getCtuInfoFileName(dumpFile));
}
2022-11-20 12:29:56 +01:00
std::string language;
switch (settings.enforcedLang) {
case Settings::Language::C:
language = " language=\"c\"";
break;
case Settings::Language::CPP:
language = " language=\"cpp\"";
break;
case Settings::Language::None:
if (Path::isCPP(filename))
language = " language=\"cpp\"";
else if (Path::isC(filename))
language = " language=\"c\"";
break;
}
fdump << "<?xml version=\"1.0\"?>\n";
fdump << "<dumps" << language << ">\n";
fdump << " <platform"
<< " name=\"" << settings.platform.toString() << '\"'
<< " char_bit=\"" << settings.platform.char_bit << '\"'
<< " short_bit=\"" << settings.platform.short_bit << '\"'
<< " int_bit=\"" << settings.platform.int_bit << '\"'
<< " long_bit=\"" << settings.platform.long_bit << '\"'
<< " long_long_bit=\"" << settings.platform.long_long_bit << '\"'
<< " pointer_bit=\"" << (settings.platform.sizeof_pointer * settings.platform.char_bit) << '\"'
<< "/>" << '\n';
}
static std::string detectPython(const CppCheck::ExecuteCmdFn &executeCommand)
{
#ifdef _WIN32
const char *py_exes[] = { "python3.exe", "python.exe" };
#else
const char *py_exes[] = { "python3", "python" };
#endif
for (const char* py_exe : py_exes) {
std::string out;
#ifdef _MSC_VER
// FIXME: hack to avoid debug assertion with _popen() in executeCommand() for non-existing commands
const std::string cmd = std::string(py_exe) + " --version >NUL 2>&1";
if (system(cmd.c_str()) != 0) {
// TODO: get more detailed error?
continue;
}
#endif
if (executeCommand(py_exe, split("--version"), "2>&1", out) == EXIT_SUCCESS && startsWith(out, "Python ") && std::isdigit(out[7])) {
return py_exe;
}
}
return "";
}
static std::vector<picojson::value> executeAddon(const AddonInfo &addonInfo,
const std::string &defaultPythonExe,
const std::string &file,
const std::string &premiumArgs,
const CppCheck::ExecuteCmdFn &executeCommand)
2020-04-05 10:57:51 +02:00
{
const std::string redirect = "2>&1";
std::string pythonExe;
2021-10-11 19:26:51 +02:00
if (!addonInfo.executable.empty())
pythonExe = addonInfo.executable;
else if (!addonInfo.python.empty())
2020-05-19 08:14:56 +02:00
pythonExe = cmdFileName(addonInfo.python);
else if (!defaultPythonExe.empty())
pythonExe = cmdFileName(defaultPythonExe);
else {
// store in static variable so we only look this up once
static const std::string detectedPythonExe = detectPython(executeCommand);
if (detectedPythonExe.empty())
2020-05-19 08:14:56 +02:00
throw InternalError(nullptr, "Failed to auto detect python");
pythonExe = detectedPythonExe;
}
2021-10-11 19:26:51 +02:00
std::string args;
if (addonInfo.executable.empty())
args = cmdFileName(addonInfo.runScript) + " " + cmdFileName(addonInfo.scriptFile);
args += std::string(args.empty() ? "" : " ") + "--cli" + addonInfo.args;
2022-08-22 21:11:28 +02:00
if (!premiumArgs.empty() && !addonInfo.executable.empty())
args += " " + premiumArgs;
2021-10-11 19:26:51 +02:00
const bool is_file_list = (file.find(FILELIST) != std::string::npos);
const std::string fileArg = (is_file_list ? " --file-list " : " ") + cmdFileName(file);
2021-10-11 19:26:51 +02:00
args += fileArg;
2019-04-14 15:00:03 +02:00
std::string result;
if (const int exitcode = executeCommand(pythonExe, split(args), redirect, result)) {
std::string message("Failed to execute addon '" + addonInfo.name + "' - exitcode is " + std::to_string(exitcode));
std::string details = pythonExe + " " + args;
if (result.size() > 2) {
details += "\nOutput:\n";
details += result;
const auto pos = details.find_last_not_of("\n\r");
if (pos != std::string::npos)
details.resize(pos + 1);
}
throw InternalError(nullptr, message, details);
}
std::vector<picojson::value> addonResult;
// Validate output..
std::istringstream istr(result);
std::string line;
while (std::getline(istr, line)) {
// TODO: also bail out?
if (line.empty()) {
//std::cout << "addon '" << addonInfo.name << "' result contains empty line" << std::endl;
continue;
}
// TODO: get rid of this
if (startsWith(line,"Checking ")) {
//std::cout << "addon '" << addonInfo.name << "' result contains 'Checking ' line" << std::endl;
continue;
}
if (line[0] != '{') {
//std::cout << "addon '" << addonInfo.name << "' result is not a JSON" << std::endl;
result.erase(result.find_last_not_of('\n') + 1, std::string::npos); // Remove trailing newlines
2020-05-19 08:14:56 +02:00
throw InternalError(nullptr, "Failed to execute '" + pythonExe + " " + args + "'. " + result);
}
//std::cout << "addon '" << addonInfo.name << "' result is " << line << std::endl;
// TODO: make these failures?
picojson::value res;
const std::string err = picojson::parse(res, line);
if (!err.empty()) {
//std::cout << "addon '" << addonInfo.name << "' result is not a valid JSON (" << err << ")" << std::endl;
continue;
}
if (!res.is<picojson::object>()) {
//std::cout << "addon '" << addonInfo.name << "' result is not a JSON object" << std::endl;
continue;
}
addonResult.emplace_back(std::move(res));
}
// Valid results
return addonResult;
2019-04-14 15:00:03 +02:00
}
static std::string getDefinesFlags(const std::string &semicolonSeparatedString)
{
std::string flags;
for (const std::string &d: split(semicolonSeparatedString, ";"))
flags += "-D" + d + " ";
return flags;
}
CppCheck::CppCheck(ErrorLogger &errorLogger,
bool useGlobalSuppressions,
ExecuteCmdFn executeCommand)
: mErrorLogger(errorLogger)
, mUseGlobalSuppressions(useGlobalSuppressions)
, mExecuteCommand(std::move(executeCommand))
2021-08-07 20:51:18 +02:00
{}
2010-04-13 19:25:08 +02:00
CppCheck::~CppCheck()
{
2018-06-17 07:36:05 +02:00
while (!mFileInfo.empty()) {
2018-06-17 07:40:13 +02:00
delete mFileInfo.back();
mFileInfo.pop_back();
}
if (mPlistFile.is_open()) {
mPlistFile << ErrorLogger::plistFooter();
mPlistFile.close();
}
2010-04-13 19:25:08 +02:00
}
const char * CppCheck::version()
2010-04-13 19:25:08 +02:00
{
return Version;
}
const char * CppCheck::extraVersion()
{
return ExtraVersion;
2010-04-13 19:25:08 +02:00
}
static bool reportClangErrors(std::istream &is, const std::function<void(const ErrorMessage&)>& reportErr, std::vector<ErrorMessage> &warnings)
{
std::string line;
while (std::getline(is, line)) {
if (line.empty() || line[0] == ' ' || line[0] == '`' || line[0] == '-')
continue;
std::string::size_type pos3 = line.find(": error: ");
if (pos3 == std::string::npos)
pos3 = line.find(": fatal error:");
if (pos3 == std::string::npos)
2021-12-11 12:42:15 +01:00
pos3 = line.find(": warning:");
if (pos3 == std::string::npos)
continue;
// file:line:column: error: ....
const std::string::size_type pos2 = line.rfind(':', pos3 - 1);
const std::string::size_type pos1 = line.rfind(':', pos2 - 1);
if (pos1 >= pos2 || pos2 >= pos3)
continue;
const std::string filename = line.substr(0, pos1);
const std::string linenr = line.substr(pos1+1, pos2-pos1-1);
const std::string colnr = line.substr(pos2+1, pos3-pos2-1);
const std::string msg = line.substr(line.find(':', pos3+1) + 2);
const std::string locFile = Path::toNativeSeparators(filename);
ErrorMessage::FileLocation loc;
loc.setfile(locFile);
loc.line = strToInt<int>(linenr);
loc.column = strToInt<unsigned int>(colnr);
ErrorMessage errmsg({std::move(loc)},
locFile,
Severity::error,
msg,
"syntaxError",
Certainty::normal);
2021-12-11 12:42:15 +01:00
if (line.compare(pos3, 10, ": warning:") == 0) {
warnings.push_back(std::move(errmsg));
2021-12-11 12:42:15 +01:00
continue;
}
reportErr(errmsg);
return true;
}
return false;
}
unsigned int CppCheck::check(const std::string &path)
2010-04-13 19:25:08 +02:00
{
if (mSettings.clang) {
if (!mSettings.quiet)
mErrorLogger.reportOut(std::string("Checking ") + path + " ...", Color::FgGreen);
const std::string lang = Path::isCPP(path) ? "-x c++" : "-x c";
const std::string analyzerInfo = mSettings.buildDir.empty() ? std::string() : AnalyzerInformation::getAnalyzerInfoFile(mSettings.buildDir, path, emptyString);
const std::string clangcmd = analyzerInfo + ".clang-cmd";
const std::string clangStderr = analyzerInfo + ".clang-stderr";
const std::string clangAst = analyzerInfo + ".clang-ast";
std::string exe = mSettings.clangExecutable;
#ifdef _WIN32
// append .exe if it is not a path
if (Path::fromNativeSeparators(mSettings.clangExecutable).find('/') == std::string::npos) {
exe += ".exe";
}
#endif
std::string flags(lang + " ");
if (Path::isCPP(path) && !mSettings.standards.stdValue.empty())
flags += "-std=" + mSettings.standards.stdValue + " ";
for (const std::string &i: mSettings.includePaths)
flags += "-I" + i + " ";
flags += getDefinesFlags(mSettings.userDefines);
const std::string args2 = "-fsyntax-only -Xclang -ast-dump -fno-color-diagnostics " + flags + path;
const std::string redirect2 = analyzerInfo.empty() ? std::string("2>&1") : ("2> " + clangStderr);
if (!mSettings.buildDir.empty()) {
std::ofstream fout(clangcmd);
fout << exe << " " << args2 << " " << redirect2 << std::endl;
} else if (mSettings.verbose && !mSettings.quiet) {
mErrorLogger.reportOut(exe + " " + args2);
}
std::string output2;
if (mExecuteCommand(exe,split(args2),redirect2,output2) != EXIT_SUCCESS || output2.find("TranslationUnitDecl") == std::string::npos) {
std::cerr << "Failed to execute '" << exe << " " << args2 << " " << redirect2 << "'" << std::endl;
return 0;
}
// Ensure there are not syntax errors...
2021-12-11 12:42:15 +01:00
std::vector<ErrorMessage> compilerWarnings;
if (!mSettings.buildDir.empty()) {
std::ifstream fin(clangStderr);
auto reportError = [this](const ErrorMessage& errorMessage) {
reportErr(errorMessage);
};
if (reportClangErrors(fin, reportError, compilerWarnings))
return 0;
} else {
std::istringstream istr(output2);
auto reportError = [this](const ErrorMessage& errorMessage) {
reportErr(errorMessage);
};
if (reportClangErrors(istr, reportError, compilerWarnings))
return 0;
}
if (!mSettings.buildDir.empty()) {
std::ofstream fout(clangAst);
fout << output2 << std::endl;
}
try {
std::istringstream ast(output2);
Tokenizer tokenizer(&mSettings, this);
tokenizer.list.appendFileIfNew(path);
clangimport::parseClangAstDump(&tokenizer, ast);
ValueFlow::setValues(tokenizer.list,
const_cast<SymbolDatabase&>(*tokenizer.getSymbolDatabase()),
this,
&mSettings,
&s_timerResults);
if (mSettings.debugnormal)
tokenizer.printDebugOutput(1);
checkNormalTokens(tokenizer);
// create dumpfile
std::ofstream fdump;
std::string dumpFile;
createDumpFile(mSettings, path, fdump, dumpFile);
if (fdump.is_open()) {
fdump << "<dump cfg=\"\">\n";
2021-12-11 12:42:15 +01:00
for (const ErrorMessage& errmsg: compilerWarnings)
fdump << " <clang-warning file=\"" << toxml(errmsg.callStack.front().getfile()) << "\" line=\"" << errmsg.callStack.front().line << "\" column=\"" << errmsg.callStack.front().column << "\" message=\"" << toxml(errmsg.shortMessage()) << "\"/>\n";
fdump << " <standards>\n";
fdump << " <c version=\"" << mSettings.standards.getC() << "\"/>\n";
fdump << " <cpp version=\"" << mSettings.standards.getCPP() << "\"/>\n";
fdump << " </standards>\n";
tokenizer.dump(fdump);
fdump << "</dump>\n";
fdump << "</dumps>\n";
fdump.close();
}
// run addons
executeAddons(dumpFile, path);
} catch (const InternalError &e) {
const ErrorMessage errmsg = ErrorMessage::fromInternalError(e, nullptr, path, "Bailing out from analysis: Processing Clang AST dump failed");
reportErr(errmsg);
} catch (const TerminateException &) {
// Analysis is terminated
return mExitCode;
} catch (const std::exception &e) {
internalError(path, std::string("Processing Clang AST dump failed: ") + e.what());
}
return mExitCode;
}
return checkFile(Path::simplifyPath(path), emptyString);
2010-04-13 19:25:08 +02:00
}
unsigned int CppCheck::check(const std::string &path, const std::string &content)
2010-04-13 19:25:08 +02:00
{
std::istringstream iss(content);
return checkFile(Path::simplifyPath(path), emptyString, &iss);
2010-04-13 19:25:08 +02:00
}
unsigned int CppCheck::check(const FileSettings &fs)
{
CppCheck temp(mErrorLogger, mUseGlobalSuppressions, mExecuteCommand);
temp.mSettings = mSettings;
if (!temp.mSettings.userDefines.empty())
temp.mSettings.userDefines += ';';
if (mSettings.clang)
temp.mSettings.userDefines += fs.defines;
else
temp.mSettings.userDefines += fs.cppcheckDefines();
temp.mSettings.includePaths = fs.includePaths;
temp.mSettings.userUndefs.insert(fs.undefs.cbegin(), fs.undefs.cend());
if (fs.standard.find("++") != std::string::npos)
temp.mSettings.standards.setCPP(fs.standard);
else if (!fs.standard.empty())
temp.mSettings.standards.setC(fs.standard);
if (fs.platformType != Platform::Type::Unspecified)
temp.mSettings.platform.set(fs.platformType);
if (mSettings.clang) {
temp.mSettings.includePaths.insert(temp.mSettings.includePaths.end(), fs.systemIncludePaths.cbegin(), fs.systemIncludePaths.cend());
return temp.check(Path::simplifyPath(fs.filename));
}
const unsigned int returnValue = temp.checkFile(Path::simplifyPath(fs.filename), fs.cfg);
mSettings.nomsg.addSuppressions(temp.mSettings.nomsg.getSuppressions());
return returnValue;
}
static simplecpp::TokenList createTokenList(const std::string& filename, std::vector<std::string>& files, simplecpp::OutputList* outputList, std::istream* fileStream)
{
if (fileStream)
return {*fileStream, files, filename, outputList};
return {filename, files, outputList};
}
unsigned int CppCheck::checkFile(const std::string& filename, const std::string &cfgname, std::istream* fileStream)
2010-04-13 19:25:08 +02:00
{
2018-06-17 07:43:25 +02:00
mExitCode = 0;
2010-04-13 19:25:08 +02:00
FilesDeleter filesDeleter;
if (Settings::terminated())
2018-06-17 07:43:25 +02:00
return mExitCode;
const Timer fileTotalTimer(mSettings.showtime == SHOWTIME_MODES::SHOWTIME_FILE_TOTAL, filename);
if (!mSettings.quiet) {
std::string fixedpath = Path::simplifyPath(filename);
fixedpath = Path::toNativeSeparators(fixedpath);
2021-07-08 21:21:35 +02:00
mErrorLogger.reportOut(std::string("Checking ") + fixedpath + ' ' + cfgname + std::string("..."), Color::FgGreen);
if (mSettings.verbose) {
mErrorLogger.reportOut("Defines:" + mSettings.userDefines);
std::string undefs;
for (const std::string& U : mSettings.userUndefs) {
if (!undefs.empty())
undefs += ';';
undefs += ' ' + U;
}
mErrorLogger.reportOut("Undefines:" + undefs);
std::string includePaths;
for (const std::string &I : mSettings.includePaths)
includePaths += " -I" + I;
mErrorLogger.reportOut("Includes:" + includePaths);
mErrorLogger.reportOut(std::string("Platform:") + mSettings.platform.toString());
}
}
2010-04-13 19:25:08 +02:00
if (mPlistFile.is_open()) {
mPlistFile << ErrorLogger::plistFooter();
mPlistFile.close();
2017-05-16 14:07:23 +02:00
}
CheckUnusedFunctions checkUnusedFunctions(nullptr, nullptr, nullptr);
2011-10-13 20:53:06 +02:00
try {
Preprocessor preprocessor(mSettings, this);
2016-07-20 12:21:00 +02:00
std::set<std::string> configurations;
simplecpp::OutputList outputList;
std::vector<std::string> files;
simplecpp::TokenList tokens1 = createTokenList(filename, files, &outputList, fileStream);
// If there is a syntax error, report it and stop
const auto output_it = std::find_if(outputList.cbegin(), outputList.cend(), [](const simplecpp::Output &output){
return Preprocessor::hasErrors(output);
});
if (output_it != outputList.cend()) {
const simplecpp::Output &output = *output_it;
std::string file = Path::fromNativeSeparators(output.location.file());
if (mSettings.relativePaths)
file = Path::getRelativePath(file, mSettings.basePaths);
const ErrorMessage::FileLocation loc1(file, output.location.line, output.location.col);
std::list<ErrorMessage::FileLocation> callstack(1, loc1);
ErrorMessage errmsg(callstack,
"",
Severity::error,
output.msg,
"syntaxError",
Certainty::normal);
reportErr(errmsg);
return mExitCode;
}
2020-05-23 07:16:49 +02:00
if (mSettings.library.markupFile(filename)) {
Tokenizer tokenizer(&mSettings, this, &preprocessor);
tokenizer.createTokens(std::move(tokens1));
checkUnusedFunctions.getFileInfo(&tokenizer, &mSettings);
return EXIT_SUCCESS;
}
if (!preprocessor.loadFiles(tokens1, files))
return mExitCode;
if (!mSettings.plistOutput.empty()) {
2017-05-16 14:07:23 +02:00
std::string filename2;
if (filename.find('/') != std::string::npos)
filename2 = filename.substr(filename.rfind('/') + 1);
2017-05-16 14:07:23 +02:00
else
filename2 = filename;
const std::size_t fileNameHash = std::hash<std::string> {}(filename);
2020-11-13 15:52:24 +01:00
filename2 = mSettings.plistOutput + filename2.substr(0, filename2.find('.')) + "_" + std::to_string(fileNameHash) + ".plist";
mPlistFile.open(filename2);
mPlistFile << ErrorLogger::plistHeader(version(), files);
2017-05-16 14:07:23 +02:00
}
std::string dumpProlog;
if (mSettings.dump || !mSettings.addons.empty()) {
dumpProlog += " <rawtokens>\n";
for (unsigned int i = 0; i < files.size(); ++i) {
dumpProlog += " <file index=\"";
dumpProlog += std::to_string(i);
dumpProlog += "\" name=\"";
dumpProlog += ErrorLogger::toxml(files[i]);
dumpProlog += "\"/>\n";
}
for (const simplecpp::Token *tok = tokens1.cfront(); tok; tok = tok->next) {
dumpProlog += " <tok ";
dumpProlog += "fileIndex=\"";
dumpProlog += std::to_string(tok->location.fileIndex);
dumpProlog += "\" ";
dumpProlog += "linenr=\"";
dumpProlog += std::to_string(tok->location.line);
dumpProlog += "\" ";
dumpProlog +="column=\"";
dumpProlog += std::to_string(tok->location.col);
dumpProlog += "\" ";
dumpProlog += "str=\"";
dumpProlog += ErrorLogger::toxml(tok->str());
dumpProlog += "\"";
dumpProlog += "/>\n";
}
dumpProlog += " </rawtokens>\n";
}
2017-04-14 10:46:35 +02:00
// Parse comments and then remove them
preprocessor.inlineSuppressions(tokens1, mSettings.nomsg);
if (mSettings.dump || !mSettings.addons.empty()) {
std::ostringstream oss;
mSettings.nomsg.dump(oss);
dumpProlog += oss.str();
}
tokens1.removeComments();
preprocessor.removeComments();
2016-07-20 12:21:00 +02:00
if (!mSettings.buildDir.empty()) {
// Get toolinfo
std::ostringstream toolinfo;
toolinfo << CPPCHECK_VERSION_STRING;
toolinfo << (mSettings.severity.isEnabled(Severity::warning) ? 'w' : ' ');
toolinfo << (mSettings.severity.isEnabled(Severity::style) ? 's' : ' ');
toolinfo << (mSettings.severity.isEnabled(Severity::performance) ? 'p' : ' ');
toolinfo << (mSettings.severity.isEnabled(Severity::portability) ? 'p' : ' ');
toolinfo << (mSettings.severity.isEnabled(Severity::information) ? 'i' : ' ');
toolinfo << mSettings.userDefines;
mSettings.nomsg.dump(toolinfo);
// Calculate hash so it can be compared with old hash / future hashes
const std::size_t hash = preprocessor.calculateHash(tokens1, toolinfo.str());
2020-05-23 07:16:49 +02:00
std::list<ErrorMessage> errors;
if (!mAnalyzerInformation.analyzeFile(mSettings.buildDir, filename, cfgname, hash, errors)) {
2016-10-29 12:18:11 +02:00
while (!errors.empty()) {
reportErr(errors.front());
errors.pop_front();
}
2018-06-17 07:43:25 +02:00
return mExitCode; // known results => no need to reanalyze file
2016-10-29 12:18:11 +02:00
}
}
// write dump file xml prolog
std::ofstream fdump;
std::string dumpFile;
createDumpFile(mSettings, filename, fdump, dumpFile);
if (fdump.is_open()) {
fdump << dumpProlog;
if (!mSettings.dump)
filesDeleter.addFile(dumpFile);
}
// Get directives
preprocessor.setDirectives(tokens1);
2017-05-17 21:58:46 +02:00
preprocessor.simplifyPragmaAsm(&tokens1);
2016-07-25 14:52:23 +02:00
preprocessor.setPlatformInfo(&tokens1);
2016-07-20 12:21:00 +02:00
// Get configurations..
if ((mSettings.checkAllConfigurations && mSettings.userDefines.empty()) || mSettings.force) {
Timer t("Preprocessor::getConfigs", mSettings.showtime, &s_timerResults);
2016-07-20 12:21:00 +02:00
configurations = preprocessor.getConfigs(tokens1);
} else {
configurations.insert(mSettings.userDefines);
}
2010-04-13 19:25:08 +02:00
if (mSettings.checkConfiguration) {
for (const std::string &config : configurations)
(void)preprocessor.getcode(tokens1, config, files, true);
return 0;
}
#ifdef HAVE_RULES
// Run define rules on raw code
const auto rules_it = std::find_if(mSettings.rules.cbegin(), mSettings.rules.cend(), [](const Settings::Rule& rule) {
return rule.tokenlist == "define";
});
if (rules_it != mSettings.rules.cend()) {
2016-07-28 15:35:41 +02:00
std::string code;
const std::list<Directive> &directives = preprocessor.getDirectives();
for (const Directive &dir : directives) {
if (startsWith(dir.str,"#define ") || startsWith(dir.str,"#include "))
code += "#line " + std::to_string(dir.linenr) + " \"" + dir.file + "\"\n" + dir.str + '\n';
2016-07-20 12:21:00 +02:00
}
Tokenizer tokenizer2(&mSettings, this);
2016-07-28 15:35:41 +02:00
std::istringstream istr2(code);
tokenizer2.list.createTokens(istr2);
executeRules("define", tokenizer2);
}
#endif
2010-04-13 19:25:08 +02:00
if (!mSettings.force && configurations.size() > mSettings.maxConfigs) {
if (mSettings.severity.isEnabled(Severity::information)) {
tooManyConfigsError(Path::toNativeSeparators(filename),configurations.size());
} else {
2018-06-17 19:20:07 +02:00
mTooManyConfigs = true;
}
}
std::set<unsigned long long> hashes;
2020-12-27 21:05:31 +01:00
int checkCount = 0;
bool hasValidConfig = false;
std::list<std::string> configurationError;
2018-11-05 09:49:28 +01:00
for (const std::string &currCfg : configurations) {
// bail out if terminated
if (Settings::terminated())
break;
// Check only a few configurations (default 12), after that bail out, unless --force
// was used.
if (!mSettings.force && ++checkCount > mSettings.maxConfigs)
break;
2010-04-13 19:25:08 +02:00
if (!mSettings.userDefines.empty()) {
2019-02-03 19:49:27 +01:00
mCurrentConfig = mSettings.userDefines;
const std::vector<std::string> v1(split(mSettings.userDefines, ";"));
for (const std::string &cfg: split(currCfg, ";")) {
if (std::find(v1.cbegin(), v1.cend(), cfg) == v1.cend()) {
2019-02-03 19:49:27 +01:00
mCurrentConfig += ";" + cfg;
}
}
} else {
mCurrentConfig = currCfg;
}
if (mSettings.preprocessOnly) {
Timer t("Preprocessor::getcode", mSettings.showtime, &s_timerResults);
2018-06-17 07:40:13 +02:00
std::string codeWithoutCfg = preprocessor.getcode(tokens1, mCurrentConfig, files, true);
t.stop();
if (startsWith(codeWithoutCfg,"#file"))
2016-01-02 11:48:36 +01:00
codeWithoutCfg.insert(0U, "//");
std::string::size_type pos = 0;
while ((pos = codeWithoutCfg.find("\n#file",pos)) != std::string::npos)
codeWithoutCfg.insert(pos+1U, "//");
pos = 0;
while ((pos = codeWithoutCfg.find("\n#endfile",pos)) != std::string::npos)
codeWithoutCfg.insert(pos+1U, "//");
pos = 0;
while ((pos = codeWithoutCfg.find(Preprocessor::macroChar,pos)) != std::string::npos)
codeWithoutCfg[pos] = ' ';
reportOut(codeWithoutCfg);
continue;
}
Tokenizer tokenizer(&mSettings, this, &preprocessor);
2019-07-31 22:35:51 +02:00
if (mSettings.showtime != SHOWTIME_MODES::SHOWTIME_NONE)
tokenizer.setTimerResults(&s_timerResults);
try {
// Create tokens, skip rest of iteration if failed
{
Timer timer("Tokenizer::createTokens", mSettings.showtime, &s_timerResults);
simplecpp::TokenList tokensP = preprocessor.preprocess(tokens1, mCurrentConfig, files, true);
tokenizer.createTokens(std::move(tokensP));
}
hasValidConfig = true;
// locations macros
mLocationMacros.clear();
for (const Token* tok = tokenizer.tokens(); tok; tok = tok->next()) {
if (!tok->getMacroName().empty())
mLocationMacros[Location(files[tok->fileIndex()], tok->linenr())].emplace(tok->getMacroName());
}
// If only errors are printed, print filename after the check
2018-11-05 09:49:28 +01:00
if (!mSettings.quiet && (!mCurrentConfig.empty() || checkCount > 1)) {
std::string fixedpath = Path::simplifyPath(filename);
fixedpath = Path::toNativeSeparators(fixedpath);
2021-07-08 21:21:35 +02:00
mErrorLogger.reportOut("Checking " + fixedpath + ": " + mCurrentConfig + "...", Color::FgGreen);
}
if (!tokenizer.tokens())
continue;
// skip rest of iteration if just checking configuration
if (mSettings.checkConfiguration)
continue;
// Check raw tokens
checkRawTokens(tokenizer);
// Simplify tokens into normal form, skip rest of iteration if failed
2023-04-11 19:52:55 +02:00
if (!tokenizer.simplifyTokens1(mCurrentConfig))
continue;
// dump xml if --dump
if ((mSettings.dump || !mSettings.addons.empty()) && fdump.is_open()) {
2018-06-17 07:40:13 +02:00
fdump << "<dump cfg=\"" << ErrorLogger::toxml(mCurrentConfig) << "\">" << std::endl;
fdump << " <standards>" << std::endl;
fdump << " <c version=\"" << mSettings.standards.getC() << "\"/>" << std::endl;
fdump << " <cpp version=\"" << mSettings.standards.getCPP() << "\"/>" << std::endl;
fdump << " </standards>" << std::endl;
preprocessor.dump(fdump);
tokenizer.dump(fdump);
2015-12-14 20:05:17 +01:00
fdump << "</dump>" << std::endl;
}
// Need to call this even if the hash will skip this configuration
mSettings.nomsg.markUnmatchedInlineSuppressionsAsChecked(tokenizer);
// Skip if we already met the same simplified token list
if (mSettings.force || mSettings.maxConfigs > 1) {
const std::size_t hash = tokenizer.list.calculateHash();
if (hashes.find(hash) != hashes.end()) {
2018-08-05 11:19:20 +02:00
if (mSettings.debugwarnings)
2018-06-17 07:40:13 +02:00
purgedConfigurationMessage(filename, mCurrentConfig);
continue;
}
hashes.insert(hash);
}
// Check normal tokens
checkNormalTokens(tokenizer);
// Analyze info..
if (!mSettings.buildDir.empty())
checkUnusedFunctions.parseTokens(tokenizer, filename.c_str(), &mSettings);
#ifdef HAVE_RULES
// handling of "simple" rules has been removed.
if (hasRule("simple"))
throw InternalError(nullptr, "Handling of \"simple\" rules has been removed in Cppcheck. Use --addon instead.");
#endif
} catch (const simplecpp::Output &o) {
// #error etc during preprocessing
configurationError.push_back((mCurrentConfig.empty() ? "\'\'" : mCurrentConfig) + " : [" + o.location.file() + ':' + std::to_string(o.location.line) + "] " + o.msg);
--checkCount; // don't count invalid configurations
if (!hasValidConfig && currCfg == *configurations.rbegin()) {
// If there is no valid configuration then report error..
std::string file = Path::fromNativeSeparators(o.location.file());
if (mSettings.relativePaths)
file = Path::getRelativePath(file, mSettings.basePaths);
const ErrorMessage::FileLocation loc1(file, o.location.line, o.location.col);
std::list<ErrorMessage::FileLocation> callstack(1, loc1);
ErrorMessage errmsg(callstack,
filename,
Severity::error,
o.msg,
"preprocessorErrorDirective",
Certainty::normal);
reportErr(errmsg);
}
continue;
} catch (const TerminateException &) {
// Analysis is terminated
return mExitCode;
} catch (const InternalError &e) {
ErrorMessage errmsg = ErrorMessage::fromInternalError(e, &tokenizer.list, filename);
reportErr(errmsg);
}
2010-04-13 19:25:08 +02:00
}
if (!hasValidConfig && configurations.size() > 1 && mSettings.severity.isEnabled(Severity::information)) {
std::string msg;
msg = "This file is not analyzed. Cppcheck failed to extract a valid configuration. Use -v for more details.";
msg += "\nThis file is not analyzed. Cppcheck failed to extract a valid configuration. The tested configurations have these preprocessor errors:";
for (const std::string &s : configurationError)
msg += '\n' + s;
const std::string locFile = Path::toNativeSeparators(filename);
2020-05-23 07:16:49 +02:00
ErrorMessage::FileLocation loc;
loc.setfile(locFile);
ErrorMessage errmsg({std::move(loc)},
locFile,
2020-05-23 07:16:49 +02:00
Severity::information,
msg,
"noValidConfiguration",
Certainty::normal);
reportErr(errmsg);
}
// dumped all configs, close root </dumps> element now
if (fdump.is_open()) {
fdump << "</dumps>" << std::endl;
fdump.close();
}
executeAddons(dumpFile, Path::simplifyPath(filename));
} catch (const TerminateException &) {
// Analysis is terminated
return mExitCode;
} catch (const std::runtime_error &e) {
internalError(filename, std::string("Checking file failed: ") + e.what());
} catch (const std::bad_alloc &) {
internalError(filename, "Checking file failed: out of memory");
} catch (const InternalError &e) {
const ErrorMessage errmsg = ErrorMessage::fromInternalError(e, nullptr, filename, "Bailing out from analysis: Checking file failed");
reportErr(errmsg);
}
if (!mSettings.buildDir.empty()) {
mAnalyzerInformation.setFileInfo("CheckUnusedFunctions", checkUnusedFunctions.analyzerInfo());
mAnalyzerInformation.close();
}
2016-10-29 12:18:11 +02:00
// In jointSuppressionReport mode, unmatched suppressions are
// collected after all files are processed
if (!mSettings.useSingleJob() && (mSettings.severity.isEnabled(Severity::information) || mSettings.checkConfiguration)) {
Suppressions::reportUnmatchedSuppressions(mSettings.nomsg.getUnmatchedLocalSuppressions(filename, isUnusedFunctionCheckEnabled()), *this);
}
2010-04-13 19:25:08 +02:00
2018-06-16 23:31:16 +02:00
mErrorList.clear();
if (mSettings.showtime == SHOWTIME_MODES::SHOWTIME_FILE || mSettings.showtime == SHOWTIME_MODES::SHOWTIME_TOP5_FILE)
printTimerResults(mSettings.showtime);
2018-06-17 07:43:25 +02:00
return mExitCode;
}
// TODO: replace with ErrorMessage::fromInternalError()
void CppCheck::internalError(const std::string &filename, const std::string &msg)
{
const std::string fullmsg("Bailing out from analysis: " + msg);
const ErrorMessage::FileLocation loc1(filename, 0, 0);
std::list<ErrorMessage::FileLocation> callstack(1, loc1);
ErrorMessage errmsg(callstack,
emptyString,
Severity::error,
fullmsg,
"internalError",
Certainty::normal);
mErrorLogger.reportErr(errmsg);
}
2010-04-13 19:25:08 +02:00
//---------------------------------------------------------------------------
// CppCheck - A function that checks a raw token list
2010-04-13 19:25:08 +02:00
//---------------------------------------------------------------------------
void CppCheck::checkRawTokens(const Tokenizer &tokenizer)
2010-04-13 19:25:08 +02:00
{
#ifdef HAVE_RULES
// Execute rules for "raw" code
executeRules("raw", tokenizer);
#else
(void)tokenizer;
#endif
}
2010-04-13 19:25:08 +02:00
//---------------------------------------------------------------------------
// CppCheck - A function that checks a normal token list
//---------------------------------------------------------------------------
void CppCheck::checkNormalTokens(const Tokenizer &tokenizer)
{
// TODO: this should actually be the behavior if only "--enable=unusedFunction" is specified - see #10648
const char* unusedFunctionOnly = std::getenv("UNUSEDFUNCTION_ONLY");
const bool doUnusedFunctionOnly = unusedFunctionOnly && (std::strcmp(unusedFunctionOnly, "1") == 0);
const std::time_t maxTime = mSettings.checksMaxTime > 0 ? std::time(nullptr) + mSettings.checksMaxTime : 0;
// call all "runChecks" in all registered Check classes
// cppcheck-suppress shadowFunction - TODO: fix this
for (Check *check : Check::instances()) {
if (Settings::terminated())
return;
2016-02-11 16:10:52 +01:00
if (maxTime > 0 && std::time(nullptr) > maxTime) {
if (mSettings.debugwarnings) {
ErrorMessage::FileLocation loc;
loc.setfile(tokenizer.list.getFiles()[0]);
ErrorMessage errmsg({std::move(loc)},
emptyString,
Severity::debug,
"Checks maximum time exceeded",
"checksMaxTime",
Certainty::normal);
reportErr(errmsg);
}
return;
}
if (doUnusedFunctionOnly && dynamic_cast<CheckUnusedFunctions*>(check) == nullptr)
continue;
Timer timerRunChecks(check->name() + "::runChecks", mSettings.showtime, &s_timerResults);
check->runChecks(tokenizer, this);
}
if (mSettings.clang)
// TODO: Use CTU for Clang analysis
return;
2018-12-25 21:11:23 +01:00
if (mSettings.useSingleJob() || !mSettings.buildDir.empty()) {
// Analyse the tokens..
if (CTU::FileInfo * const fi1 = CTU::getFileInfo(&tokenizer)) {
if (!mSettings.buildDir.empty())
mAnalyzerInformation.setFileInfo("ctu", fi1->toString());
if (mSettings.useSingleJob())
mFileInfo.push_back(fi1);
else
delete fi1;
}
2018-12-25 21:11:23 +01:00
// cppcheck-suppress shadowFunction - TODO: fix this
for (const Check *check : Check::instances()) {
if (doUnusedFunctionOnly && dynamic_cast<const CheckUnusedFunctions*>(check) == nullptr)
continue;
if (Check::FileInfo * const fi = check->getFileInfo(&tokenizer, &mSettings)) {
if (!mSettings.buildDir.empty())
mAnalyzerInformation.setFileInfo(check->name(), fi->toString());
if (mSettings.useSingleJob())
mFileInfo.push_back(fi);
else
delete fi;
}
2016-10-29 12:18:11 +02:00
}
2020-01-14 21:17:07 +01:00
}
#ifdef HAVE_RULES
executeRules("normal", tokenizer);
#endif
}
2010-04-13 19:25:08 +02:00
//---------------------------------------------------------------------------
2010-12-12 11:56:22 +01:00
#ifdef HAVE_RULES
2019-03-17 08:19:56 +01:00
bool CppCheck::hasRule(const std::string &tokenlist) const
{
return std::any_of(mSettings.rules.cbegin(), mSettings.rules.cend(), [&](const Settings::Rule& rule) {
return rule.tokenlist == tokenlist;
});
2010-04-13 19:25:08 +02:00
}
static const char * pcreErrorCodeToString(const int pcreExecRet)
{
switch (pcreExecRet) {
case PCRE_ERROR_NULL:
return "Either code or subject was passed as NULL, or ovector was NULL "
"and ovecsize was not zero (PCRE_ERROR_NULL)";
case PCRE_ERROR_BADOPTION:
return "An unrecognized bit was set in the options argument (PCRE_ERROR_BADOPTION)";
case PCRE_ERROR_BADMAGIC:
return "PCRE stores a 4-byte \"magic number\" at the start of the compiled code, "
"to catch the case when it is passed a junk pointer and to detect when a "
"pattern that was compiled in an environment of one endianness is run in "
"an environment with the other endianness. This is the error that PCRE "
"gives when the magic number is not present (PCRE_ERROR_BADMAGIC)";
case PCRE_ERROR_UNKNOWN_NODE:
return "While running the pattern match, an unknown item was encountered in the "
"compiled pattern. This error could be caused by a bug in PCRE or by "
"overwriting of the compiled pattern (PCRE_ERROR_UNKNOWN_NODE)";
case PCRE_ERROR_NOMEMORY:
return "If a pattern contains back references, but the ovector that is passed "
"to pcre_exec() is not big enough to remember the referenced substrings, "
"PCRE gets a block of memory at the start of matching to use for this purpose. "
"If the call via pcre_malloc() fails, this error is given. The memory is "
"automatically freed at the end of matching. This error is also given if "
"pcre_stack_malloc() fails in pcre_exec(). "
"This can happen only when PCRE has been compiled with "
"--disable-stack-for-recursion (PCRE_ERROR_NOMEMORY)";
case PCRE_ERROR_NOSUBSTRING:
return "This error is used by the pcre_copy_substring(), pcre_get_substring(), "
"and pcre_get_substring_list() functions (see below). "
"It is never returned by pcre_exec() (PCRE_ERROR_NOSUBSTRING)";
case PCRE_ERROR_MATCHLIMIT:
return "The backtracking limit, as specified by the match_limit field in a pcre_extra "
"structure (or defaulted) was reached. "
"See the description above (PCRE_ERROR_MATCHLIMIT)";
case PCRE_ERROR_CALLOUT:
return "This error is never generated by pcre_exec() itself. "
"It is provided for use by callout functions that want to yield a distinctive "
"error code. See the pcrecallout documentation for details (PCRE_ERROR_CALLOUT)";
case PCRE_ERROR_BADUTF8:
return "A string that contains an invalid UTF-8 byte sequence was passed as a subject, "
"and the PCRE_NO_UTF8_CHECK option was not set. If the size of the output vector "
"(ovecsize) is at least 2, the byte offset to the start of the the invalid UTF-8 "
"character is placed in the first element, and a reason code is placed in the "
"second element. The reason codes are listed in the following section. For "
"backward compatibility, if PCRE_PARTIAL_HARD is set and the problem is a truncated "
"UTF-8 character at the end of the subject (reason codes 1 to 5), "
"PCRE_ERROR_SHORTUTF8 is returned instead of PCRE_ERROR_BADUTF8";
case PCRE_ERROR_BADUTF8_OFFSET:
return "The UTF-8 byte sequence that was passed as a subject was checked and found to "
"be valid (the PCRE_NO_UTF8_CHECK option was not set), but the value of "
"startoffset did not point to the beginning of a UTF-8 character or the end of "
"the subject (PCRE_ERROR_BADUTF8_OFFSET)";
case PCRE_ERROR_PARTIAL:
return "The subject string did not match, but it did match partially. See the "
"pcrepartial documentation for details of partial matching (PCRE_ERROR_PARTIAL)";
case PCRE_ERROR_BADPARTIAL:
return "This code is no longer in use. It was formerly returned when the PCRE_PARTIAL "
"option was used with a compiled pattern containing items that were not supported "
"for partial matching. From release 8.00 onwards, there are no restrictions on "
"partial matching (PCRE_ERROR_BADPARTIAL)";
case PCRE_ERROR_INTERNAL:
return "An unexpected internal error has occurred. This error could be caused by a bug "
"in PCRE or by overwriting of the compiled pattern (PCRE_ERROR_INTERNAL)";
case PCRE_ERROR_BADCOUNT:
2021-08-07 20:51:18 +02:00
return "This error is given if the value of the ovecsize argument is negative "
"(PCRE_ERROR_BADCOUNT)";
case PCRE_ERROR_RECURSIONLIMIT:
return "The internal recursion limit, as specified by the match_limit_recursion "
"field in a pcre_extra structure (or defaulted) was reached. "
"See the description above (PCRE_ERROR_RECURSIONLIMIT)";
case PCRE_ERROR_DFA_UITEM:
return "PCRE_ERROR_DFA_UITEM";
case PCRE_ERROR_DFA_UCOND:
return "PCRE_ERROR_DFA_UCOND";
case PCRE_ERROR_DFA_WSSIZE:
return "PCRE_ERROR_DFA_WSSIZE";
case PCRE_ERROR_DFA_RECURSE:
return "PCRE_ERROR_DFA_RECURSE";
case PCRE_ERROR_NULLWSLIMIT:
return "PCRE_ERROR_NULLWSLIMIT";
case PCRE_ERROR_BADNEWLINE:
return "An invalid combination of PCRE_NEWLINE_xxx options was "
"given (PCRE_ERROR_BADNEWLINE)";
case PCRE_ERROR_BADOFFSET:
return "The value of startoffset was negative or greater than the length "
"of the subject, that is, the value in length (PCRE_ERROR_BADOFFSET)";
case PCRE_ERROR_SHORTUTF8:
return "This error is returned instead of PCRE_ERROR_BADUTF8 when the subject "
"string ends with a truncated UTF-8 character and the PCRE_PARTIAL_HARD option is set. "
"Information about the failure is returned as for PCRE_ERROR_BADUTF8. "
"It is in fact sufficient to detect this case, but this special error code for "
"PCRE_PARTIAL_HARD precedes the implementation of returned information; "
"it is retained for backwards compatibility (PCRE_ERROR_SHORTUTF8)";
case PCRE_ERROR_RECURSELOOP:
return "This error is returned when pcre_exec() detects a recursion loop "
"within the pattern. Specifically, it means that either the whole pattern "
"or a subpattern has been called recursively for the second time at the same "
"position in the subject string. Some simple patterns that might do this "
"are detected and faulted at compile time, but more complicated cases, "
"in particular mutual recursions between two different subpatterns, "
"cannot be detected until run time (PCRE_ERROR_RECURSELOOP)";
case PCRE_ERROR_JIT_STACKLIMIT:
return "This error is returned when a pattern that was successfully studied "
"using a JIT compile option is being matched, but the memory available "
"for the just-in-time processing stack is not large enough. See the pcrejit "
"documentation for more details (PCRE_ERROR_JIT_STACKLIMIT)";
case PCRE_ERROR_BADMODE:
return "This error is given if a pattern that was compiled by the 8-bit library "
"is passed to a 16-bit or 32-bit library function, or vice versa (PCRE_ERROR_BADMODE)";
case PCRE_ERROR_BADENDIANNESS:
return "This error is given if a pattern that was compiled and saved is reloaded on a "
"host with different endianness. The utility function pcre_pattern_to_host_byte_order() "
"can be used to convert such a pattern so that it runs on the new host (PCRE_ERROR_BADENDIANNESS)";
case PCRE_ERROR_DFA_BADRESTART:
return "PCRE_ERROR_DFA_BADRESTART";
2018-09-23 17:04:52 +02:00
#if PCRE_MAJOR >= 8 && PCRE_MINOR >= 32
case PCRE_ERROR_BADLENGTH:
2018-09-23 17:04:52 +02:00
return "This error is given if pcre_exec() is called with a negative value for the length argument (PCRE_ERROR_BADLENGTH)";
case PCRE_ERROR_JIT_BADOPTION:
return "This error is returned when a pattern that was successfully studied using a JIT compile "
"option is being matched, but the matching mode (partial or complete match) does not correspond "
"to any JIT compilation mode. When the JIT fast path function is used, this error may be "
"also given for invalid options. See the pcrejit documentation for more details (PCRE_ERROR_JIT_BADOPTION)";
2018-09-23 17:04:52 +02:00
#endif
}
return "";
}
void CppCheck::executeRules(const std::string &tokenlist, const Tokenizer &tokenizer)
{
// There is no rule to execute
2019-03-17 08:19:56 +01:00
if (!hasRule(tokenlist))
return;
// Write all tokens in a string that can be parsed by pcre
std::ostringstream ostr;
for (const Token *tok = tokenizer.tokens(); tok; tok = tok->next())
ostr << " " << tok->str();
const std::string str(ostr.str());
2019-03-17 08:19:56 +01:00
for (const Settings::Rule &rule : mSettings.rules) {
if (rule.pattern.empty() || rule.id.empty() || rule.severity == Severity::none || rule.tokenlist != tokenlist)
continue;
if (!mSettings.quiet) {
2021-07-08 21:21:35 +02:00
reportOut("Processing rule: " + rule.pattern, Color::FgGreen);
}
const char *pcreCompileErrorStr = nullptr;
int erroffset = 0;
pcre * const re = pcre_compile(rule.pattern.c_str(),0,&pcreCompileErrorStr,&erroffset,nullptr);
if (!re) {
if (pcreCompileErrorStr) {
const std::string msg = "pcre_compile failed: " + std::string(pcreCompileErrorStr);
2020-05-23 07:16:49 +02:00
const ErrorMessage errmsg(std::list<ErrorMessage::FileLocation>(),
2020-05-23 07:30:22 +02:00
emptyString,
Severity::error,
msg,
"pcre_compile",
Certainty::normal);
reportErr(errmsg);
}
continue;
}
// Optimize the regex, but only if PCRE_CONFIG_JIT is available
#ifdef PCRE_CONFIG_JIT
const char *pcreStudyErrorStr = nullptr;
pcre_extra * const pcreExtra = pcre_study(re, PCRE_STUDY_JIT_COMPILE, &pcreStudyErrorStr);
// pcre_study() returns NULL for both errors and when it can not optimize the regex.
// The last argument is how one checks for errors.
// It is NULL if everything works, and points to an error string otherwise.
if (pcreStudyErrorStr) {
const std::string msg = "pcre_study failed: " + std::string(pcreStudyErrorStr);
2020-05-23 07:16:49 +02:00
const ErrorMessage errmsg(std::list<ErrorMessage::FileLocation>(),
2020-05-23 07:30:22 +02:00
emptyString,
Severity::error,
msg,
"pcre_study",
Certainty::normal);
reportErr(errmsg);
// pcre_compile() worked, but pcre_study() returned an error. Free the resources allocated by pcre_compile().
pcre_free(re);
continue;
}
#else
const pcre_extra * const pcreExtra = nullptr;
#endif
int pos = 0;
int ovector[30]= {0};
while (pos < (int)str.size()) {
const int pcreExecRet = pcre_exec(re, pcreExtra, str.c_str(), (int)str.size(), pos, 0, ovector, 30);
if (pcreExecRet < 0) {
const std::string errorMessage = pcreErrorCodeToString(pcreExecRet);
if (!errorMessage.empty()) {
2020-05-23 07:16:49 +02:00
const ErrorMessage errmsg(std::list<ErrorMessage::FileLocation>(),
2020-05-23 07:30:22 +02:00
emptyString,
Severity::error,
std::string("pcre_exec failed: ") + errorMessage,
"pcre_exec",
Certainty::normal);
reportErr(errmsg);
}
break;
}
const unsigned int pos1 = (unsigned int)ovector[0];
const unsigned int pos2 = (unsigned int)ovector[1];
// jump to the end of the match for the next pcre_exec
pos = (int)pos2;
// determine location..
2020-05-23 07:16:49 +02:00
ErrorMessage::FileLocation loc;
loc.setfile(tokenizer.list.getSourceFilePath());
loc.line = 0;
std::size_t len = 0;
for (const Token *tok = tokenizer.tokens(); tok; tok = tok->next()) {
len = len + 1U + tok->str().size();
if (len > pos1) {
loc.setfile(tokenizer.list.getFiles().at(tok->fileIndex()));
loc.line = tok->linenr();
break;
}
}
2020-05-23 07:16:49 +02:00
const std::list<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 ErrorMessage errmsg(callStack, tokenizer.list.getSourceFilePath(), rule.severity, summary, rule.id, Certainty::normal);
// Report error
reportErr(errmsg);
}
pcre_free(re);
#ifdef PCRE_CONFIG_JIT
// Free up the EXTRA PCRE value (may be NULL at this point)
if (pcreExtra) {
pcre_free_study(pcreExtra);
}
#endif
}
}
#endif
void CppCheck::executeAddons(const std::string& dumpFile, const std::string& file0)
{
2021-07-07 10:58:13 +02:00
if (!dumpFile.empty()) {
std::vector<std::string> f{dumpFile};
executeAddons(f, file0);
2021-07-07 10:58:13 +02:00
}
}
void CppCheck::executeAddons(const std::vector<std::string>& files, const std::string& file0)
2021-07-07 10:58:13 +02:00
{
if (mSettings.addons.empty() || files.empty())
return;
FilesDeleter filesDeleter;
std::string fileList;
if (files.size() >= 2 || endsWith(files[0], ".ctu-info")) {
fileList = Path::getPathFromFilename(files[0]) + FILELIST + std::to_string(getPid());
filesDeleter.addFile(fileList);
std::ofstream fout(fileList);
for (const std::string& f: files)
fout << f << std::endl;
}
// ensure all addons have already been resolved - TODO: remove when settings are const after creation
assert(mSettings.addonInfos.size() == mSettings.addons.size());
for (const AddonInfo &addonInfo : mSettings.addonInfos) {
if (addonInfo.name != "misra" && !addonInfo.ctu && endsWith(files.back(), ".ctu-info"))
2021-07-07 10:58:13 +02:00
continue;
const std::vector<picojson::value> results =
2022-08-22 21:11:28 +02:00
executeAddon(addonInfo, mSettings.addonPython, fileList.empty() ? files[0] : fileList, mSettings.premiumArgs, mExecuteCommand);
const bool misraC2023 = mSettings.premiumArgs.find("--misra-c-2023") != std::string::npos;
for (const picojson::value& res : results) {
// TODO: get rid of copy?
// this is a copy so we can access missing fields and get a default value
2021-07-07 10:58:13 +02:00
picojson::object obj = res.get<picojson::object>();
2021-07-07 10:58:13 +02:00
ErrorMessage errmsg;
if (obj.count("file") > 0) {
std::string fileName = obj["file"].get<std::string>();
const int64_t lineNumber = obj["linenr"].get<int64_t>();
const int64_t column = obj["column"].get<int64_t>();
errmsg.callStack.emplace_back(std::move(fileName), lineNumber, column);
} else if (obj.count("loc") > 0) {
for (const picojson::value &locvalue: obj["loc"].get<picojson::array>()) {
picojson::object loc = locvalue.get<picojson::object>();
std::string fileName = loc["file"].get<std::string>();
const int64_t lineNumber = loc["linenr"].get<int64_t>();
const int64_t column = loc["column"].get<int64_t>();
const std::string info = loc["info"].get<std::string>();
errmsg.callStack.emplace_back(std::move(fileName), info, lineNumber, column);
}
}
2021-07-07 10:58:13 +02:00
errmsg.id = obj["addon"].get<std::string>() + "-" + obj["errorId"].get<std::string>();
if (misraC2023 && startsWith(errmsg.id, "misra-c2012-"))
errmsg.id = "misra-c2023-" + errmsg.id.substr(12);
2021-07-07 10:58:13 +02:00
const std::string text = obj["message"].get<std::string>();
errmsg.setmsg(text);
const std::string severity = obj["severity"].get<std::string>();
errmsg.severity = severityFromString(severity);
if (errmsg.severity == Severity::none || errmsg.severity == Severity::internal) {
if (!endsWith(errmsg.id, "-logChecker"))
continue;
errmsg.severity = Severity::internal;
}
else if (!mSettings.severity.isEnabled(errmsg.severity))
2021-07-07 10:58:13 +02:00
continue;
errmsg.file0 = file0;
2021-07-07 10:58:13 +02:00
reportErr(errmsg);
}
2021-07-07 10:58:13 +02:00
}
}
void CppCheck::executeAddonsWholeProgram(const std::list<std::pair<std::string, std::size_t>> &files)
2021-07-07 10:58:13 +02:00
{
if (mSettings.addons.empty())
return;
std::vector<std::string> ctuInfoFiles;
for (const auto &f: files) {
2021-07-07 10:58:13 +02:00
const std::string &dumpFileName = getDumpFileName(mSettings, f.first);
ctuInfoFiles.push_back(getCtuInfoFileName(dumpFileName));
}
try {
executeAddons(ctuInfoFiles, "");
2022-07-07 19:45:47 +02:00
} catch (const InternalError& e) {
const ErrorMessage errmsg = ErrorMessage::fromInternalError(e, nullptr, "", "Bailing out from analysis: Whole program analysis failed");
reportErr(errmsg);
}
if (mSettings.buildDir.empty()) {
for (const std::string &f: ctuInfoFiles)
std::remove(f.c_str());
}
}
Settings &CppCheck::settings()
2010-04-13 19:25:08 +02:00
{
return mSettings;
2010-04-13 19:25:08 +02:00
}
2020-12-27 21:05:31 +01:00
void CppCheck::tooManyConfigsError(const std::string &file, const int numberOfConfigurations)
{
if (!mSettings.severity.isEnabled(Severity::information) && !mTooManyConfigs)
return;
2018-06-17 19:20:07 +02:00
mTooManyConfigs = false;
if (mSettings.severity.isEnabled(Severity::information) && file.empty())
return;
2020-05-23 07:16:49 +02:00
std::list<ErrorMessage::FileLocation> loclist;
if (!file.empty()) {
loclist.emplace_back(file);
}
std::ostringstream msg;
msg << "Too many #ifdef configurations - cppcheck only checks " << mSettings.maxConfigs;
if (numberOfConfigurations > mSettings.maxConfigs)
msg << " of " << numberOfConfigurations << " configurations. Use --force to check all configurations.\n";
if (file.empty())
msg << " configurations. Use --force to check all configurations. For more details, use --enable=information.\n";
msg << "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.";
if (file.empty())
msg << " For more details, use --enable=information.";
2020-05-23 07:16:49 +02:00
ErrorMessage errmsg(loclist,
emptyString,
Severity::information,
msg.str(),
"toomanyconfigs", CWE398,
Certainty::normal);
reportErr(errmsg);
}
void CppCheck::purgedConfigurationMessage(const std::string &file, const std::string& configuration)
{
2018-06-17 19:20:07 +02:00
mTooManyConfigs = false;
if (mSettings.severity.isEnabled(Severity::information) && file.empty())
return;
2020-05-23 07:16:49 +02:00
std::list<ErrorMessage::FileLocation> loclist;
if (!file.empty()) {
loclist.emplace_back(file);
}
2020-05-23 07:16:49 +02:00
ErrorMessage errmsg(loclist,
emptyString,
Severity::information,
"The configuration '" + configuration + "' was not checked because its code equals another one.",
"purgedConfiguration",
Certainty::normal);
reportErr(errmsg);
}
2010-04-13 19:25:08 +02:00
//---------------------------------------------------------------------------
// TODO: part of this logic is duplicated in Executor::hasToLog()
2020-05-23 07:16:49 +02:00
void CppCheck::reportErr(const ErrorMessage &msg)
2010-04-13 19:25:08 +02:00
{
if (msg.severity == Severity::internal) {
mErrorLogger.reportErr(msg);
2013-10-20 14:09:10 +02:00
return;
}
2013-10-20 14:09:10 +02:00
if (!mSettings.library.reportErrors(msg.file0))
return;
2010-04-13 19:25:08 +02:00
std::set<std::string> macroNames;
if (!msg.callStack.empty()) {
const std::string &file = msg.callStack.back().getfile(false);
int lineNumber = msg.callStack.back().line;
const auto it = mLocationMacros.find(Location(file, lineNumber));
if (it != mLocationMacros.cend())
macroNames = it->second;
}
// TODO: only convert if necessary
const auto errorMessage = Suppressions::ErrorMessage::fromErrorMessage(msg, macroNames);
if (mSettings.nomsg.isSuppressed(errorMessage, mUseGlobalSuppressions)) {
// Safety: Report critical errors to ErrorLogger
if (mSettings.safety && ErrorLogger::isCriticalErrorId(msg.id)) {
mExitCode = 1;
2010-04-13 19:25:08 +02:00
if (mSettings.nomsg.isSuppressedExplicitly(errorMessage, mUseGlobalSuppressions)) {
// Report with internal severity to signal that there is this critical error but
// it is suppressed
ErrorMessage temp(msg);
temp.severity = Severity::internal;
mErrorLogger.reportErr(temp);
} else {
// Report critical error that is not explicitly suppressed
mErrorLogger.reportErr(msg);
}
}
return;
}
2010-04-13 19:25:08 +02:00
// TODO: there should be no need for the verbose and default messages here
std::string errmsg = msg.toString(mSettings.verbose);
if (errmsg.empty())
return;
// Alert only about unique errors.
// This makes sure the errors of a single check() call are unique.
// TODO: get rid of this? This is forwarded to another ErrorLogger which is also doing this
if (!mErrorList.emplace(std::move(errmsg)).second)
return;
if (!mSettings.buildDir.empty())
mAnalyzerInformation.reportErr(msg);
2019-05-01 11:54:13 +02:00
if (!mSettings.nofail.isSuppressed(errorMessage) && !mSettings.nomsg.isSuppressed(errorMessage)) {
2018-06-17 07:43:25 +02:00
mExitCode = 1;
2019-05-01 11:54:13 +02:00
}
2010-04-13 19:25:08 +02:00
mErrorLogger.reportErr(msg);
// check if plistOutput should be populated and the current output file is open and the error is not suppressed
if (!mSettings.plistOutput.empty() && mPlistFile.is_open() && !mSettings.nomsg.isSuppressed(errorMessage)) {
// add error to plist output file
mPlistFile << ErrorLogger::plistData(msg);
2017-05-16 14:07:23 +02:00
}
2010-04-13 19:25:08 +02:00
}
2021-07-08 21:21:35 +02:00
void CppCheck::reportOut(const std::string &outmsg, Color c)
2010-04-13 19:25:08 +02:00
{
2021-07-08 21:21:35 +02:00
mErrorLogger.reportOut(outmsg, c);
2010-04-13 19:25:08 +02:00
}
void CppCheck::reportProgress(const std::string &filename, const char stage[], const std::size_t value)
2010-04-13 19:25:08 +02:00
{
mErrorLogger.reportProgress(filename, stage, value);
2010-04-13 19:25:08 +02:00
}
void CppCheck::getErrorMessages(ErrorLogger &errorlogger)
{
Settings s;
s.severity.enable(Severity::warning);
s.severity.enable(Severity::style);
s.severity.enable(Severity::portability);
s.severity.enable(Severity::performance);
s.severity.enable(Severity::information);
CppCheck cppcheck(errorlogger, true, nullptr);
cppcheck.purgedConfigurationMessage(emptyString,emptyString);
cppcheck.mTooManyConfigs = true;
cppcheck.tooManyConfigsError(emptyString,0U);
// TODO: add functions to get remaining error messages
2010-04-13 19:25:08 +02:00
// call all "getErrorMessages" in all registered Check classes
for (std::list<Check *>::const_iterator it = Check::instances().cbegin(); it != Check::instances().cend(); ++it)
(*it)->getErrorMessages(&errorlogger, &s);
2010-04-13 19:25:08 +02:00
Preprocessor::getErrorMessages(&errorlogger, &s);
2010-04-13 19:25:08 +02:00
}
void CppCheck::analyseClangTidy(const FileSettings &fileSettings)
2020-01-30 07:14:17 +01:00
{
std::string allIncludes;
2020-01-30 07:14:17 +01:00
for (const std::string &inc : fileSettings.includePaths) {
allIncludes = allIncludes + "-I\"" + inc + "\" ";
}
const std::string allDefines = getDefinesFlags(fileSettings.defines);
2020-01-30 07:14:17 +01:00
#ifdef _WIN32
constexpr char exe[] = "clang-tidy.exe";
#else
constexpr char exe[] = "clang-tidy";
#endif
const std::string args = "-quiet -checks=*,-clang-analyzer-*,-llvm* \"" + fileSettings.filename + "\" -- " + allIncludes + allDefines;
std::string output;
if (const int exitcode = mExecuteCommand(exe, split(args), emptyString, output)) {
std::cerr << "Failed to execute '" << exe << "' (exitcode: " << std::to_string(exitcode) << ")" << std::endl;
2020-01-31 14:13:52 +01:00
return;
2020-01-30 07:14:17 +01:00
}
// parse output and create error messages
std::istringstream istr(output);
2020-01-30 07:14:17 +01:00
std::string line;
2020-01-31 14:13:52 +01:00
if (!mSettings.buildDir.empty()) {
const std::string analyzerInfoFile = AnalyzerInformation::getAnalyzerInfoFile(mSettings.buildDir, fileSettings.filename, emptyString);
2020-01-30 07:14:17 +01:00
std::ofstream fcmd(analyzerInfoFile + ".clang-tidy-cmd");
fcmd << istr.str();
}
while (std::getline(istr, line)) {
if (line.find("error") == std::string::npos && line.find("warning") == std::string::npos)
continue;
std::size_t endColumnPos = line.find(": error:");
if (endColumnPos == std::string::npos) {
endColumnPos = line.find(": warning:");
}
const std::size_t endLinePos = line.rfind(':', endColumnPos-1);
const std::size_t endNamePos = line.rfind(':', endLinePos - 1);
2020-01-30 07:14:17 +01:00
const std::size_t endMsgTypePos = line.find(':', endColumnPos + 2);
const std::size_t endErrorPos = line.rfind('[', std::string::npos);
2020-01-31 14:13:52 +01:00
if (endLinePos==std::string::npos || endNamePos==std::string::npos || endMsgTypePos==std::string::npos || endErrorPos==std::string::npos)
2020-01-31 14:10:27 +01:00
continue;
2020-01-31 14:13:52 +01:00
2020-01-30 07:14:17 +01:00
const std::string lineNumString = line.substr(endNamePos + 1, endLinePos - endNamePos - 1);
const std::string columnNumString = line.substr(endLinePos + 1, endColumnPos - endLinePos - 1);
const std::string messageString = line.substr(endMsgTypePos + 1, endErrorPos - endMsgTypePos - 1);
const std::string errorString = line.substr(endErrorPos, line.length());
std::string fixedpath = Path::simplifyPath(line.substr(0, endNamePos));
const int64_t lineNumber = strToInt<int64_t>(lineNumString);
const int64_t column = strToInt<int64_t>(columnNumString);
2020-01-30 07:14:17 +01:00
fixedpath = Path::toNativeSeparators(fixedpath);
2020-05-23 07:16:49 +02:00
ErrorMessage errmsg;
errmsg.callStack.emplace_back(fixedpath, lineNumber, column);
2020-01-30 07:14:17 +01:00
errmsg.id = "clang-tidy-" + errorString.substr(1, errorString.length() - 2);
if (errmsg.id.find("performance") != std::string::npos)
errmsg.severity = Severity::performance;
2020-01-30 07:14:17 +01:00
else if (errmsg.id.find("portability") != std::string::npos)
errmsg.severity = Severity::portability;
2020-01-30 07:14:17 +01:00
else if (errmsg.id.find("cert") != std::string::npos || errmsg.id.find("misc") != std::string::npos || errmsg.id.find("unused") != std::string::npos)
errmsg.severity = Severity::warning;
2020-01-30 07:14:17 +01:00
else
errmsg.severity = Severity::style;
2020-01-30 07:14:17 +01:00
errmsg.file0 = fixedpath;
errmsg.setmsg(messageString);
reportErr(errmsg);
}
}
bool CppCheck::analyseWholeProgram()
{
bool errors = false;
2019-02-03 17:51:02 +01:00
// Init CTU
CTU::maxCtuDepth = mSettings.maxCtuDepth;
// Analyse the tokens
2018-12-25 21:11:23 +01:00
CTU::FileInfo ctu;
for (const Check::FileInfo *fi : mFileInfo) {
const CTU::FileInfo *fi2 = dynamic_cast<const CTU::FileInfo *>(fi);
if (fi2) {
ctu.functionCalls.insert(ctu.functionCalls.end(), fi2->functionCalls.cbegin(), fi2->functionCalls.cend());
ctu.nestedCalls.insert(ctu.nestedCalls.end(), fi2->nestedCalls.cbegin(), fi2->nestedCalls.cend());
2018-12-25 21:11:23 +01:00
}
}
// cppcheck-suppress shadowFunction - TODO: fix this
2018-12-25 21:11:23 +01:00
for (Check *check : Check::instances())
errors |= check->analyseWholeProgram(&ctu, mFileInfo, mSettings, *this); // TODO: ctu
2018-06-17 07:43:25 +02:00
return errors && (mExitCode > 0);
}
void CppCheck::analyseWholeProgram(const std::string &buildDir, const std::list<std::pair<std::string, std::size_t>> &files, const std::list<FileSettings>& fileSettings)
{
executeAddonsWholeProgram(files); // TODO: pass FileSettings
if (buildDir.empty()) {
removeCtuInfoFiles(files, fileSettings);
return;
}
if (mSettings.checks.isEnabled(Checks::unusedFunction))
CheckUnusedFunctions::analyseWholeProgram(mSettings, this, buildDir);
std::list<Check::FileInfo*> fileInfoList;
2018-12-25 21:11:23 +01:00
CTU::FileInfo ctuFileInfo;
// Load all analyzer info data..
const std::string filesTxt(buildDir + "/files.txt");
std::ifstream fin(filesTxt);
std::string filesTxtLine;
while (std::getline(fin, filesTxtLine)) {
const std::string::size_type firstColon = filesTxtLine.find(':');
if (firstColon == std::string::npos)
continue;
const std::string::size_type lastColon = filesTxtLine.rfind(':');
if (firstColon == lastColon)
continue;
const std::string xmlfile = buildDir + '/' + filesTxtLine.substr(0,firstColon);
2017-03-30 11:20:04 +02:00
//const std::string sourcefile = filesTxtLine.substr(lastColon+1);
tinyxml2::XMLDocument doc;
const tinyxml2::XMLError error = doc.LoadFile(xmlfile.c_str());
if (error != tinyxml2::XML_SUCCESS)
continue;
const tinyxml2::XMLElement * const rootNode = doc.FirstChildElement();
if (rootNode == nullptr)
continue;
for (const tinyxml2::XMLElement *e = rootNode->FirstChildElement(); e; e = e->NextSiblingElement()) {
if (std::strcmp(e->Name(), "FileInfo") != 0)
continue;
const char *checkClassAttr = e->Attribute("check");
if (!checkClassAttr)
continue;
2018-12-25 21:11:23 +01:00
if (std::strcmp(checkClassAttr, "ctu") == 0) {
ctuFileInfo.loadFromXml(e);
continue;
}
// cppcheck-suppress shadowFunction - TODO: fix this
for (const Check *check : Check::instances()) {
2018-12-25 21:11:23 +01:00
if (checkClassAttr == check->name())
fileInfoList.push_back(check->loadFileInfoFromXml(e));
}
}
}
2019-02-03 17:51:02 +01:00
// Set CTU max depth
CTU::maxCtuDepth = mSettings.maxCtuDepth;
// Analyse the tokens
// cppcheck-suppress shadowFunction - TODO: fix this
2018-12-25 21:11:23 +01:00
for (Check *check : Check::instances())
check->analyseWholeProgram(&ctuFileInfo, fileInfoList, mSettings, *this);
2018-12-25 21:11:23 +01:00
for (Check::FileInfo *fi : fileInfoList)
delete fi;
}
2016-10-28 12:10:19 +02:00
bool CppCheck::isUnusedFunctionCheckEnabled() const
{
return (mSettings.useSingleJob() && mSettings.checks.isEnabled(Checks::unusedFunction));
}
void CppCheck::removeCtuInfoFiles(const std::list<std::pair<std::string, std::size_t>> &files, const std::list<FileSettings>& fileSettings)
{
if (mSettings.buildDir.empty()) {
for (const auto& f: files) {
const std::string &dumpFileName = getDumpFileName(mSettings, f.first);
const std::string &ctuInfoFileName = getCtuInfoFileName(dumpFileName);
std::remove(ctuInfoFileName.c_str());
}
for (const auto& fs: fileSettings) {
const std::string &dumpFileName = getDumpFileName(mSettings, fs.filename);
const std::string &ctuInfoFileName = getCtuInfoFileName(dumpFileName);
std::remove(ctuInfoFileName.c_str());
}
}
}
// cppcheck-suppress unusedFunction - only used in tests
void CppCheck::resetTimerResults()
{
s_timerResults.reset();
}
void CppCheck::printTimerResults(SHOWTIME_MODES mode)
{
s_timerResults.showResults(mode);
}