Improved addon execution errorhandling (#5451)

This commit is contained in:
Oliver Stöneberg 2023-09-20 10:40:57 +02:00 committed by GitHub
parent 58a7519cbb
commit a43b55a0ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 427 additions and 100 deletions

View File

@ -10,6 +10,35 @@ Addons are scripts that analyses Cppcheck dump files to check compatibility with
Checks Linux system for [year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem) safety. This required [modified environment](https://github.com/3adev/y2038). See complete description [here](https://github.com/danmar/cppcheck/blob/main/addons/doc/y2038.txt). Checks Linux system for [year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem) safety. This required [modified environment](https://github.com/3adev/y2038). See complete description [here](https://github.com/danmar/cppcheck/blob/main/addons/doc/y2038.txt).
+ [threadsafety.py](https://github.com/danmar/cppcheck/blob/main/addons/threadsafety.py) + [threadsafety.py](https://github.com/danmar/cppcheck/blob/main/addons/threadsafety.py)
Analyse Cppcheck dump files to locate threadsafety issues like static local objects used by multiple threads. Analyse Cppcheck dump files to locate threadsafety issues like static local objects used by multiple threads.
+ [naming.py](https://github.com/danmar/cppcheck/blob/main/addons/naming.py)
Enforces naming conventions across the code.
+ [namingng.py](https://github.com/danmar/cppcheck/blob/main/addons/namingng.py)
Enforces naming conventions across the code. Enhanced version with support for type prefixes in variable and function names.
+ [findcasts.py](https://github.com/danmar/cppcheck/blob/main/addons/findcasts.py)
Locates casts in the code.
+ [misc.py](https://github.com/danmar/cppcheck/blob/main/addons/misc.py)
Performs miscellaneous checks.
### Other files
- doc
Additional files for documentation generation.
- tests
Contains various unit tests for the addons.
- cppcheck.py
Internal helper used by Cppcheck binary to run the addons.
- cppcheckdata.doxyfile
Configuration file for documentation generation.
- cppcheckdata.py
Helper class for reading Cppcheck dump files within an addon.
- misra_9.py
Implementation of the MISRA 9.x rules used by `misra` addon.
- naming.json
Example configuration for `namingng` addon.
- ROS_naming.json
Example configuration for the `namingng` addon enforcing the [ROS naming convention for C++ ](http://wiki.ros.org/CppStyleGuide#Files).
- runaddon.py
Internal helper used by Cppcheck binary to run the addons.
## Usage ## Usage

View File

@ -540,7 +540,7 @@ bool CppCheckExecutor::tryLoadLibrary(Library& destination, const std::string& b
*/ */
// cppcheck-suppress passedByValue - used as callback so we need to preserve the signature // cppcheck-suppress passedByValue - used as callback so we need to preserve the signature
// NOLINTNEXTLINE(performance-unnecessary-value-param) - used as callback so we need to preserve the signature // NOLINTNEXTLINE(performance-unnecessary-value-param) - used as callback so we need to preserve the signature
bool CppCheckExecutor::executeCommand(std::string exe, std::vector<std::string> args, std::string redirect, std::string &output_) int CppCheckExecutor::executeCommand(std::string exe, std::vector<std::string> args, std::string redirect, std::string &output_)
{ {
output_.clear(); output_.clear();
@ -567,9 +567,12 @@ bool CppCheckExecutor::executeCommand(std::string exe, std::vector<std::string>
#else #else
FILE *p = popen(cmd.c_str(), "r"); FILE *p = popen(cmd.c_str(), "r");
#endif #endif
//std::cout << "invoking command '" << cmd << "'" << std::endl;
if (!p) { if (!p) {
// TODO: read errno // TODO: how to provide to caller?
return false; //const int err = errno;
//std::cout << "popen() errno " << std::to_string(err) << std::endl;
return -1;
} }
char buffer[1024]; char buffer[1024];
while (fgets(buffer, sizeof(buffer), p) != nullptr) while (fgets(buffer, sizeof(buffer), p) != nullptr)
@ -581,13 +584,19 @@ bool CppCheckExecutor::executeCommand(std::string exe, std::vector<std::string>
const int res = pclose(p); const int res = pclose(p);
#endif #endif
if (res == -1) { // error occured if (res == -1) { // error occured
// TODO: read errno // TODO: how to provide to caller?
return false; //const int err = errno;
//std::cout << "pclose() errno " << std::to_string(err) << std::endl;
return res;
} }
if (res != 0) { // process failed #if !defined(WIN32) && !defined(__MINGW32__)
// TODO: need to get error details if (WIFEXITED(res)) {
return false; return WEXITSTATUS(res);
} }
return true; if (WIFSIGNALED(res)) {
return WTERMSIG(res);
}
#endif
return res;
} }

View File

@ -99,9 +99,9 @@ public:
static bool tryLoadLibrary(Library& destination, const std::string& basepath, const char* filename); static bool tryLoadLibrary(Library& destination, const std::string& basepath, const char* filename);
/** /**
* Execute a shell command and read the output from it. Returns true if command terminated successfully. * Execute a shell command and read the output from it. Returns exitcode of the executed command,.
*/ */
static bool executeCommand(std::string exe, std::vector<std::string> args, std::string redirect, std::string &output_); static int executeCommand(std::string exe, std::vector<std::string> args, std::string redirect, std::string &output_);
protected: protected:

View File

@ -57,7 +57,7 @@
#endif #endif
// NOLINTNEXTLINE(performance-unnecessary-value-param) - used as callback so we need to preserve the signature // NOLINTNEXTLINE(performance-unnecessary-value-param) - used as callback so we need to preserve the signature
static bool executeCommand(std::string exe, std::vector<std::string> args, std::string redirect, std::string &output) // cppcheck-suppress passedByValue static int executeCommand(std::string exe, std::vector<std::string> args, std::string redirect, std::string &output) // cppcheck-suppress passedByValue
{ {
output.clear(); output.clear();
@ -80,7 +80,7 @@ static bool executeCommand(std::string exe, std::vector<std::string> args, std::
std::ofstream fout(redirect.substr(3)); std::ofstream fout(redirect.substr(3));
fout << process.readAllStandardError().toStdString(); fout << process.readAllStandardError().toStdString();
} }
return process.exitCode() == 0; return process.exitCode();
} }

View File

@ -180,16 +180,16 @@ namespace {
std::string getAddonInfo(const std::string &fileName, const std::string &exename) { std::string getAddonInfo(const std::string &fileName, const std::string &exename) {
if (fileName[0] == '{') { if (fileName[0] == '{') {
std::istringstream in(fileName);
picojson::value json; picojson::value json;
in >> json; const std::string err = picojson::parse(json, fileName);
(void)err; // TODO: report
return parseAddonInfo(json, fileName, exename); return parseAddonInfo(json, fileName, exename);
} }
if (fileName.find('.') == std::string::npos) if (fileName.find('.') == std::string::npos)
return getAddonInfo(fileName + ".py", exename); return getAddonInfo(fileName + ".py", exename);
if (endsWith(fileName, ".py")) { if (endsWith(fileName, ".py")) {
scriptFile = getFullPath(fileName, exename); scriptFile = Path::fromNativeSeparators(getFullPath(fileName, exename));
if (scriptFile.empty()) if (scriptFile.empty())
return "Did not find addon " + fileName; return "Did not find addon " + fileName;
@ -328,11 +328,35 @@ static void createDumpFile(const Settings& settings,
<< "/>" << '\n'; << "/>" << '\n';
} }
static std::string executeAddon(const AddonInfo &addonInfo, static std::string detectPython(const CppCheck::ExecuteCmdFn &executeCommand)
const std::string &defaultPythonExe, {
const std::string &file, #ifdef _WIN32
const std::string &premiumArgs, const char *py_exes[] = { "python3.exe", "python.exe" };
const std::function<bool(std::string,std::vector<std::string>,std::string,std::string&)> &executeCommand) #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";
if (system(cmd.c_str()) != 0) {
// TODO: get more detailed error?
break;
}
#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)
{ {
const std::string redirect = "2>&1"; const std::string redirect = "2>&1";
@ -345,28 +369,11 @@ static std::string executeAddon(const AddonInfo &addonInfo,
else if (!defaultPythonExe.empty()) else if (!defaultPythonExe.empty())
pythonExe = cmdFileName(defaultPythonExe); pythonExe = cmdFileName(defaultPythonExe);
else { else {
#ifdef _WIN32 // store in static variable so we only look this up once
const char *py_exes[] = { "python3.exe", "python.exe" }; static const std::string detectedPythonExe = detectPython(executeCommand);
#else if (detectedPythonExe.empty())
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";
if (system(cmd.c_str()) != 0) {
// TODO: get more detailed error?
break;
}
#endif
if (executeCommand(py_exe, split("--version"), redirect, out) && startsWith(out, "Python ") && std::isdigit(out[7])) {
pythonExe = py_exe;
break;
}
}
if (pythonExe.empty())
throw InternalError(nullptr, "Failed to auto detect python"); throw InternalError(nullptr, "Failed to auto detect python");
pythonExe = detectedPythonExe;
} }
std::string args; std::string args;
@ -380,27 +387,62 @@ static std::string executeAddon(const AddonInfo &addonInfo,
args += fileArg; args += fileArg;
std::string result; std::string result;
if (!executeCommand(pythonExe, split(args), redirect, result)) { if (const int exitcode = executeCommand(pythonExe, split(args), redirect, result)) {
std::string message("Failed to execute addon '" + addonInfo.name + "' (command: '" + pythonExe + " " + args + "'). Exitcode is nonzero."); std::string message("Failed to execute addon '" + addonInfo.name + "' - exitcode is " + std::to_string(exitcode));
std::string details = pythonExe + " " + args;
if (result.size() > 2) { if (result.size() > 2) {
message = message + "\n" + message + "\nOutput:\n" + result; details += "\nOutput:\n";
message.resize(message.find_last_not_of("\n\r")); 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); throw InternalError(nullptr, message, details);
} }
std::vector<picojson::value> addonResult;
// Validate output.. // Validate output..
std::istringstream istr(result); std::istringstream istr(result);
std::string line; std::string line;
while (std::getline(istr, line)) { while (std::getline(istr, line)) {
if (!startsWith(line,"Checking ") && !line.empty() && line[0] != '{') { // 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 result.erase(result.find_last_not_of('\n') + 1, std::string::npos); // Remove trailing newlines
throw InternalError(nullptr, "Failed to execute '" + pythonExe + " " + args + "'. " + result); 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 // Valid results
return result; return addonResult;
} }
static std::string getDefinesFlags(const std::string &semicolonSeparatedString) static std::string getDefinesFlags(const std::string &semicolonSeparatedString)
@ -413,7 +455,7 @@ static std::string getDefinesFlags(const std::string &semicolonSeparatedString)
CppCheck::CppCheck(ErrorLogger &errorLogger, CppCheck::CppCheck(ErrorLogger &errorLogger,
bool useGlobalSuppressions, bool useGlobalSuppressions,
std::function<bool(std::string,std::vector<std::string>,std::string,std::string&)> executeCommand) ExecuteCmdFn executeCommand)
: mErrorLogger(errorLogger) : mErrorLogger(errorLogger)
, mUseGlobalSuppressions(useGlobalSuppressions) , mUseGlobalSuppressions(useGlobalSuppressions)
, mExecuteCommand(std::move(executeCommand)) , mExecuteCommand(std::move(executeCommand))
@ -532,7 +574,7 @@ unsigned int CppCheck::check(const std::string &path)
} }
std::string output2; std::string output2;
if (!mExecuteCommand(exe,split(args2),redirect2,output2) || output2.find("TranslationUnitDecl") == std::string::npos) { 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; std::cerr << "Failed to execute '" << exe << " " << args2 << " " << redirect2 << "'" << std::endl;
return 0; return 0;
} }
@ -596,7 +638,8 @@ unsigned int CppCheck::check(const std::string &path)
executeAddons(dumpFile); executeAddons(dumpFile);
} catch (const InternalError &e) { } catch (const InternalError &e) {
internalError(path, "Processing Clang AST dump failed: " + e.errorMessage); const ErrorMessage errmsg = ErrorMessage::fromInternalError(e, nullptr, path, "Bailing out from analysis: Processing Clang AST dump failed");
reportErr(errmsg);
} catch (const TerminateException &) { } catch (const TerminateException &) {
// Analysis is terminated // Analysis is terminated
return mExitCode; return mExitCode;
@ -1022,7 +1065,8 @@ unsigned int CppCheck::checkFile(const std::string& filename, const std::string
} catch (const std::bad_alloc &) { } catch (const std::bad_alloc &) {
internalError(filename, "Checking file failed: out of memory"); internalError(filename, "Checking file failed: out of memory");
} catch (const InternalError &e) { } catch (const InternalError &e) {
internalError(filename, "Checking file failed: " + e.errorMessage); const ErrorMessage errmsg = ErrorMessage::fromInternalError(e, nullptr, filename, "Bailing out from analysis: Checking file failed");
reportErr(errmsg);
} }
if (!mSettings.buildDir.empty()) { if (!mSettings.buildDir.empty()) {
@ -1448,23 +1492,14 @@ void CppCheck::executeAddons(const std::vector<std::string>& files)
if (addonInfo.name != "misra" && !addonInfo.ctu && endsWith(files.back(), ".ctu-info")) if (addonInfo.name != "misra" && !addonInfo.ctu && endsWith(files.back(), ".ctu-info"))
continue; continue;
const std::string results = const std::vector<picojson::value> results =
executeAddon(addonInfo, mSettings.addonPython, fileList.empty() ? files[0] : fileList, mSettings.premiumArgs, mExecuteCommand); executeAddon(addonInfo, mSettings.addonPython, fileList.empty() ? files[0] : fileList, mSettings.premiumArgs, mExecuteCommand);
std::istringstream istr(results);
std::string line;
const bool misraC2023 = mSettings.premiumArgs.find("--misra-c-2023") != std::string::npos; const bool misraC2023 = mSettings.premiumArgs.find("--misra-c-2023") != std::string::npos;
while (std::getline(istr, line)) { for (const picojson::value& res : results) {
if (!startsWith(line,"{")) // TODO: get rid of copy?
continue; // this is a copy so we can access missing fields and get a default value
picojson::value res;
std::istringstream istr2(line);
istr2 >> res;
if (!res.is<picojson::object>())
continue;
picojson::object obj = res.get<picojson::object>(); picojson::object obj = res.get<picojson::object>();
ErrorMessage errmsg; ErrorMessage errmsg;
@ -1519,7 +1554,8 @@ void CppCheck::executeAddonsWholeProgram(const std::map<std::string, std::size_t
try { try {
executeAddons(ctuInfoFiles); executeAddons(ctuInfoFiles);
} catch (const InternalError& e) { } catch (const InternalError& e) {
internalError("", "Whole program analysis failed: " + e.errorMessage); const ErrorMessage errmsg = ErrorMessage::fromInternalError(e, nullptr, "", "Bailing out from analysis: Whole program analysis failed");
reportErr(errmsg);
} }
if (mSettings.buildDir.empty()) { if (mSettings.buildDir.empty()) {
@ -1687,8 +1723,8 @@ void CppCheck::analyseClangTidy(const ImportProject::FileSettings &fileSettings)
const std::string args = "-quiet -checks=*,-clang-analyzer-*,-llvm* \"" + fileSettings.filename + "\" -- " + allIncludes + allDefines; const std::string args = "-quiet -checks=*,-clang-analyzer-*,-llvm* \"" + fileSettings.filename + "\" -- " + allIncludes + allDefines;
std::string output; std::string output;
if (!mExecuteCommand(exe, split(args), emptyString, output)) { if (const int exitcode = mExecuteCommand(exe, split(args), emptyString, output)) {
std::cerr << "Failed to execute '" << exe << "'" << std::endl; std::cerr << "Failed to execute '" << exe << "' (exitcode: " << std::to_string(exitcode) << ")" << std::endl;
return; return;
} }

View File

@ -50,12 +50,14 @@ class Tokenizer;
*/ */
class CPPCHECKLIB CppCheck : ErrorLogger { class CPPCHECKLIB CppCheck : ErrorLogger {
public: public:
using ExecuteCmdFn = std::function<int (std::string,std::vector<std::string>,std::string,std::string&)>;
/** /**
* @brief Constructor. * @brief Constructor.
*/ */
CppCheck(ErrorLogger &errorLogger, CppCheck(ErrorLogger &errorLogger,
bool useGlobalSuppressions, bool useGlobalSuppressions,
std::function<bool(std::string,std::vector<std::string>,std::string,std::string&)> executeCommand); ExecuteCmdFn executeCommand);
/** /**
* @brief Destructor. * @brief Destructor.
@ -230,7 +232,7 @@ private:
AnalyzerInformation mAnalyzerInformation; AnalyzerInformation mAnalyzerInformation;
/** Callback for executing a shell command (exe, args, output) */ /** Callback for executing a shell command (exe, args, output) */
std::function<bool(std::string,std::vector<std::string>,std::string,std::string&)> mExecuteCommand; ExecuteCmdFn mExecuteCommand;
std::ofstream mPlistFile; std::ofstream mPlistFile;
}; };

View File

@ -233,7 +233,7 @@ static void serializeString(std::string &oss, const std::string & str)
oss += str; oss += str;
} }
ErrorMessage ErrorMessage::fromInternalError(const InternalError &internalError, const TokenList *tokenList, const std::string &filename) ErrorMessage ErrorMessage::fromInternalError(const InternalError &internalError, const TokenList *tokenList, const std::string &filename, const std::string& msg)
{ {
if (internalError.token) if (internalError.token)
assert(tokenList != nullptr); // we need to make sure we can look up the provided token assert(tokenList != nullptr); // we need to make sure we can look up the provided token
@ -253,9 +253,12 @@ ErrorMessage ErrorMessage::fromInternalError(const InternalError &internalError,
ErrorMessage errmsg(std::move(locationList), ErrorMessage errmsg(std::move(locationList),
tokenList ? tokenList->getSourceFilePath() : filename, tokenList ? tokenList->getSourceFilePath() : filename,
Severity::error, Severity::error,
internalError.errorMessage, (msg.empty() ? "" : (msg + ": ")) + internalError.errorMessage,
internalError.id, internalError.id,
Certainty::normal); Certainty::normal);
// TODO: find a better way
if (!internalError.details.empty())
errmsg.mVerboseMessage = errmsg.mVerboseMessage + ": " + internalError.details;
return errmsg; return errmsg;
} }

View File

@ -198,7 +198,7 @@ public:
return mSymbolNames; return mSymbolNames;
} }
static ErrorMessage fromInternalError(const InternalError &internalError, const TokenList *tokenList, const std::string &filename); static ErrorMessage fromInternalError(const InternalError &internalError, const TokenList *tokenList, const std::string &filename, const std::string& msg = emptyString);
private: private:
static std::string fixInvalidChars(const std::string& raw); static std::string fixInvalidChars(const std::string& raw);

View File

@ -18,31 +18,32 @@
#include "errortypes.h" #include "errortypes.h"
InternalError::InternalError(const Token *tok, std::string errorMsg, Type type) : static std::string typeToString(InternalError::Type type)
token(tok), errorMessage(std::move(errorMsg)), type(type)
{ {
switch (type) { switch (type) {
case AST: case InternalError::Type::AST:
id = "internalAstError"; return "internalAstError";
break; case InternalError::Type::SYNTAX:
case SYNTAX: return "syntaxError";
id = "syntaxError"; case InternalError::Type::UNKNOWN_MACRO:
break; return "unknownMacro";
case UNKNOWN_MACRO: case InternalError::Type::INTERNAL:
id = "unknownMacro"; return "internalError";
break; case InternalError::Type::LIMIT:
case INTERNAL: return "cppcheckLimit";
id = "cppcheckError"; case InternalError::Type::INSTANTIATION:
break; return "instantiationError";
case LIMIT:
id = "cppcheckLimit";
break;
case INSTANTIATION:
id = "instantiationError";
break;
} }
} }
InternalError::InternalError(const Token *tok, std::string errorMsg, Type type) :
InternalError(tok, std::move(errorMsg), "", type)
{}
InternalError::InternalError(const Token *tok, std::string errorMsg, std::string details, Type type) :
token(tok), errorMessage(std::move(errorMsg)), details(std::move(details)), type(type), id(typeToString(type))
{}
std::string Severity::toString(Severity::SeverityType severity) std::string Severity::toString(Severity::SeverityType severity)
{ {
switch (severity) { switch (severity) {

View File

@ -33,11 +33,15 @@
class Token; class Token;
/** @brief Simple container to be thrown when internal error is detected. */ /** @brief Simple container to be thrown when internal error is detected. */
struct InternalError { struct CPPCHECKLIB InternalError {
enum Type {AST, SYNTAX, UNKNOWN_MACRO, INTERNAL, LIMIT, INSTANTIATION}; enum Type {AST, SYNTAX, UNKNOWN_MACRO, INTERNAL, LIMIT, INSTANTIATION};
InternalError(const Token *tok, std::string errorMsg, Type type = INTERNAL); InternalError(const Token *tok, std::string errorMsg, Type type = INTERNAL);
InternalError(const Token *tok, std::string errorMsg, std::string details, Type type = INTERNAL);
const Token *token; const Token *token;
std::string errorMessage; std::string errorMessage;
std::string details;
Type type; Type type;
std::string id; std::string id;
}; };

View File

@ -207,10 +207,8 @@ def test_execute_addon_failure_2(tmpdir):
args = ['--addon=naming', '--addon-python=python5.x', test_file] args = ['--addon=naming', '--addon-python=python5.x', test_file]
_, _, stderr = cppcheck(args) _, _, stderr = cppcheck(args)
# /tmp/pytest-of-sshuser/pytest-215/test_execute_addon_failure_20/test.cpp:0:0: error: Bailing out from analysis: Checking file failed: Failed to execute addon 'naming' (command: 'python5.x /mnt/s/GitHub/cppcheck-fw/addons/runaddon.py /mnt/s/GitHub/cppcheck-fw/addons/naming.py --cli /tmp/pytest-of-sshuser/pytest-215/test_execute_addon_failure_20/test.cpp.7306.dump'). Exitcode is nonzero. [internalError]\n\n^\n ec = 1 if os.name == 'nt' else 127
# "C:\\Users\\Quotenjugendlicher\\AppData\\Local\\Temp\\pytest-of-Quotenjugendlicher\\pytest-15\\test_execute_addon_failure_20\\test.cpp:0:0: error: Bailing out from analysis: Checking file failed: Failed to execute addon (command: 'python5.x S:\\GitHub\\cppcheck-fw\\bin\\debug\\addons\\runaddon.py S:\\GitHub\\cppcheck-fw\\bin\\debug\\addons\\naming.py --cli C:\\Users\\Quotenjugendlicher\\AppData\\Local\\Temp\\pytest-of-Quotenjugendlicher\\pytest-15\\test_execute_addon_failure_20\\test.cpp.9892.dump'). Exitcode is nonzero. [internalError]\n\n^\n assert stderr == "{}:0:0: error: Bailing out from analysis: Checking file failed: Failed to execute addon 'naming' - exitcode is {} [internalError]\n\n^\n".format(test_file, ec)
assert stderr.startswith('{}:0:0: error: Bailing out from analysis: Checking file failed: Failed to execute addon \'naming\' (command: \'python5.x '.format(test_file))
assert stderr.endswith(' [internalError]\n\n^\n')
# TODO: find a test case which always fails # TODO: find a test case which always fails
@ -232,3 +230,217 @@ void f() {
_, _, stderr = cppcheck(args) _, _, stderr = cppcheck(args)
assert stderr == '{}:0:0: error: Bailing from out analysis: Checking file failed: converting \'1f\' to integer failed - not an integer [internalError]\n\n^\n'.format(test_file) assert stderr == '{}:0:0: error: Bailing from out analysis: Checking file failed: converting \'1f\' to integer failed - not an integer [internalError]\n\n^\n'.format(test_file)
def test_addon_misra(tmpdir):
test_file = os.path.join(tmpdir, 'test.cpp')
with open(test_file, 'wt') as f:
f.write("""
typedef int MISRA_5_6_VIOLATION;
""")
args = ['--addon=misra', '--enable=all', test_file]
exitcode, stdout, stderr = cppcheck(args)
assert exitcode == 0
lines = stdout.splitlines()
assert lines == [
'Checking {} ...'.format(test_file)
]
assert stderr == '{}:2:1: style: misra violation (use --rule-texts=<file> to get proper output) [misra-c2012-2.3]\ntypedef int MISRA_5_6_VIOLATION;\n^\n'.format(test_file)
def test_addon_y2038(tmpdir):
test_file = os.path.join(tmpdir, 'test.cpp')
# TODO: trigger warning
with open(test_file, 'wt') as f:
f.write("""
typedef int MISRA_5_6_VIOLATION;
""")
args = ['--addon=y2038', '--enable=all', test_file]
exitcode, stdout, stderr = cppcheck(args)
assert exitcode == 0
lines = stdout.splitlines()
assert lines == [
'Checking {} ...'.format(test_file)
]
assert stderr == ''
def test_addon_threadsafety(tmpdir):
test_file = os.path.join(tmpdir, 'test.cpp')
# TODO: trigger warning
with open(test_file, 'wt') as f:
f.write("""
typedef int MISRA_5_6_VIOLATION;
""")
args = ['--addon=y2038', '--enable=all', test_file]
exitcode, stdout, stderr = cppcheck(args)
assert exitcode == 0
lines = stdout.splitlines()
assert lines == [
'Checking {} ...'.format(test_file)
]
assert stderr == ''
def test_addon_naming(tmpdir):
test_file = os.path.join(tmpdir, 'test.cpp')
# TODO: trigger warning
with open(test_file, 'wt') as f:
f.write("""
typedef int MISRA_5_6_VIOLATION;
""")
args = ['--addon=y2038', '--enable=all', test_file]
exitcode, stdout, stderr = cppcheck(args)
assert exitcode == 0
lines = stdout.splitlines()
assert lines == [
'Checking {} ...'.format(test_file)
]
assert stderr == ''
def test_addon_namingng(tmpdir):
test_file = os.path.join(tmpdir, 'test.cpp')
# TODO: trigger warning
with open(test_file, 'wt') as f:
f.write("""
typedef int MISRA_5_6_VIOLATION;
""")
args = ['--addon=y2038', '--enable=all', test_file]
exitcode, stdout, stderr = cppcheck(args)
assert exitcode == 0
lines = stdout.splitlines()
assert lines == [
'Checking {} ...'.format(test_file)
]
assert stderr == ''
def test_invalid_addon_json(tmpdir):
addon_file = os.path.join(tmpdir, 'addon1.json')
with open(addon_file, 'wt') as f:
f.write("""
""")
test_file = os.path.join(tmpdir, 'file.cpp')
with open(test_file, 'wt') as f:
f.write("""
typedef int MISRA_5_6_VIOLATION;
""")
args = ['--addon={}'.format(addon_file), '--enable=all', test_file]
exitcode, stdout, stderr = cppcheck(args)
assert exitcode == 0 # TODO: needs to be 1
lines = stdout.splitlines()
assert lines == [
'Checking {} ...'.format(test_file),
'Loading {} failed. syntax error at line 2 near: '.format(addon_file),
'Loading {} failed. syntax error at line 2 near: '.format(addon_file)
]
assert stderr == ''
def test_invalid_addon_py(tmpdir):
addon_file = os.path.join(tmpdir, 'addon1.py')
with open(addon_file, 'wt') as f:
f.write("""
raise Exception()
""")
test_file = os.path.join(tmpdir, 'file.cpp')
with open(test_file, 'wt') as f:
f.write("""
typedef int MISRA_5_6_VIOLATION;
""")
args = ['--addon={}'.format(addon_file), '--enable=all', test_file]
exitcode, stdout, stderr = cppcheck(args)
assert exitcode == 0 # TODO: needs to be 1
lines = stdout.splitlines()
assert lines == [
'Checking {} ...'.format(test_file)
]
assert stderr == "{}:0:0: error: Bailing out from analysis: Checking file failed: Failed to execute addon 'addon1' - exitcode is 1 [internalError]\n\n^\n".format(test_file)
def test_invalid_addon_py_verbose(tmpdir):
addon_file = os.path.join(tmpdir, 'addon1.py')
with open(addon_file, 'wt') as f:
f.write("""
raise Exception()
""")
test_file = os.path.join(tmpdir, 'file.cpp')
with open(test_file, 'wt') as f:
f.write("""
typedef int MISRA_5_6_VIOLATION;
""")
args = ['--addon={}'.format(addon_file), '--enable=all', '--verbose', test_file]
exitcode, stdout, stderr = cppcheck(args)
assert exitcode == 0 # TODO: needs to be 1
lines = stdout.splitlines()
assert lines == [
'Checking {} ...'.format(test_file),
'Defines:',
'Undefines:',
'Includes:',
'Platform:native'
]
"""
/tmp/pytest-of-user/pytest-11/test_invalid_addon_py_20/file.cpp:0:0: error: Bailing out from analysis: Checking file failed: Failed to execute addon 'addon1' - exitcode is 1: python3 /home/user/CLionProjects/cppcheck/addons/runaddon.py /tmp/pytest-of-user/pytest-11/test_invalid_addon_py_20/addon1.py --cli /tmp/pytest-of-user/pytest-11/test_invalid_addon_py_20/file.cpp.24762.dump
Output:
Traceback (most recent call last):
File "/home/user/CLionProjects/cppcheck/addons/runaddon.py", line 8, in <module>
runpy.run_path(addon, run_name='__main__')
File "<frozen runpy>", line 291, in run_path
File "<frozen runpy>", line 98, in _run_module_code
File "<frozen runpy>", line 88, in _run_code
File "/tmp/pytest-of-user/pytest-11/test_invalid_addon_py_20/addon1.py", line 2, in <module>
raise Exception()
Exceptio [internalError]
"""
# /tmp/pytest-of-user/pytest-10/test_invalid_addon_py_20/file.cpp:0:0: error: Bailing out from analysis: Checking file failed: Failed to execute addon 'addon1' - exitcode is 256.: python3 /home/user/CLionProjects/cppcheck/addons/runaddon.py /tmp/pytest-of-user/pytest-10/test_invalid_addon_py_20/addon1.py --cli /tmp/pytest-of-user/pytest-10/test_invalid_addon_py_20/file.cpp.24637.dump
assert stderr.startswith("{}:0:0: error: Bailing out from analysis: Checking file failed: Failed to execute addon 'addon1' - exitcode is 1: ".format(test_file))
assert stderr.count('Output:\nTraceback')
assert stderr.endswith('raise Exception()\nException [internalError]\n\n^\n')
def test_addon_result(tmpdir):
addon_file = os.path.join(tmpdir, 'addon1.py')
with open(addon_file, 'wt') as f:
f.write("""
print("Checking ...")
print("")
print('{"file": "test.cpp", "linenr": 1, "column": 1, "severity": "style", "message": "msg", "addon": "addon1", "errorId": "id", "extra": ""}')
print('{"loc": [{"file": "test.cpp", "linenr": 1, "column": 1, "info": ""}], "severity": "style", "message": "msg", "addon": "addon1", "errorId": "id", "extra": ""}')
""")
test_file = os.path.join(tmpdir, 'file.cpp')
with open(test_file, 'wt') as f:
f.write("""
typedef int MISRA_5_6_VIOLATION;
""")
args = ['--addon={}'.format(addon_file), '--enable=all', test_file]
exitcode, stdout, stderr = cppcheck(args)
assert exitcode == 0 # TODO: needs to be 1
lines = stdout.splitlines()
assert lines == [
'Checking {} ...'.format(test_file)
]
assert stderr == 'test.cpp:1:1: style: msg [addon1-id]\n\n^\n'

View File

@ -44,6 +44,7 @@ private:
TEST_CASE(ErrorMessageConstructLocations); TEST_CASE(ErrorMessageConstructLocations);
TEST_CASE(ErrorMessageVerbose); TEST_CASE(ErrorMessageVerbose);
TEST_CASE(ErrorMessageVerboseLocations); TEST_CASE(ErrorMessageVerboseLocations);
TEST_CASE(ErrorMessageFromInternalError);
TEST_CASE(CustomFormat); TEST_CASE(CustomFormat);
TEST_CASE(CustomFormat2); TEST_CASE(CustomFormat2);
TEST_CASE(CustomFormatLocations); TEST_CASE(CustomFormatLocations);
@ -153,6 +154,36 @@ private:
ASSERT_EQUALS("[foo.cpp:5] -> [bar.cpp:8]: (error) Verbose error", msg.toString(true)); ASSERT_EQUALS("[foo.cpp:5] -> [bar.cpp:8]: (error) Verbose error", msg.toString(true));
} }
void ErrorMessageFromInternalError() const {
// TODO: test with token
{
InternalError internalError(nullptr, "message", InternalError::INTERNAL);
const auto msg = ErrorMessage::fromInternalError(internalError, nullptr, "file.c");
ASSERT_EQUALS(1, msg.callStack.size());
const auto &loc = *msg.callStack.cbegin();
ASSERT_EQUALS(0, loc.fileIndex);
ASSERT_EQUALS(0, loc.line);
ASSERT_EQUALS(0, loc.column);
ASSERT_EQUALS("message", msg.shortMessage());
ASSERT_EQUALS("message", msg.verboseMessage());
ASSERT_EQUALS("[file.c:0]: (error) message", msg.toString(false));
ASSERT_EQUALS("[file.c:0]: (error) message", msg.toString(true));
}
{
InternalError internalError(nullptr, "message", "details", InternalError::INTERNAL);
const auto msg = ErrorMessage::fromInternalError(internalError, nullptr, "file.cpp", "msg");
ASSERT_EQUALS(1, msg.callStack.size());
const auto &loc = *msg.callStack.cbegin();
ASSERT_EQUALS(0, loc.fileIndex);
ASSERT_EQUALS(0, loc.line);
ASSERT_EQUALS(0, loc.column);
ASSERT_EQUALS("msg: message", msg.shortMessage());
ASSERT_EQUALS("msg: message: details", msg.verboseMessage());
ASSERT_EQUALS("[file.cpp:0]: (error) msg: message", msg.toString(false));
ASSERT_EQUALS("[file.cpp:0]: (error) msg: message: details", msg.toString(true));
}
}
void CustomFormat() const { void CustomFormat() const {
std::list<ErrorMessage::FileLocation> locs(1, fooCpp5); std::list<ErrorMessage::FileLocation> locs(1, fooCpp5);
ErrorMessage msg(locs, emptyString, Severity::error, "Programming error.\nVerbose error", "errorId", Certainty::normal); ErrorMessage msg(locs, emptyString, Severity::error, "Programming error.\nVerbose error", "errorId", Certainty::normal);

View File

@ -113,7 +113,7 @@ private:
executeCommandCalled = true; executeCommandCalled = true;
exe = std::move(e); exe = std::move(e);
args = std::move(a); args = std::move(a);
return true; return EXIT_SUCCESS;
}); });
cppcheck.settings() = settings; cppcheck.settings() = settings;