Fixed #9723 (GUI: inline suppressions does not work for addons)

This commit is contained in:
Daniel Marjamäki 2020-05-17 16:50:02 +02:00
parent d0e2fd240b
commit 547d1b158e
10 changed files with 85 additions and 203 deletions

View File

@ -269,75 +269,6 @@ void CheckThread::runAddonsAndTools(const ImportProject::FileSettings *fileSetti
} }
parseClangErrors(addon, fileName, errout); parseClangErrors(addon, fileName, errout);
} else {
const QString python = CheckThread::pythonCmd();
if (python.isEmpty())
continue;
const QString addonFilePath = CheckThread::getAddonFilePath(mDataDir, addon + ".py");
if (addonFilePath.isEmpty())
continue;
if (dumpFile.isEmpty()) {
const std::string buildDir = mCppcheck.settings().buildDir;
mCppcheck.settings().buildDir.clear();
mCppcheck.settings().dump = true;
if (!buildDir.empty()) {
mCppcheck.settings().dumpFile = AnalyzerInformation::getAnalyzerInfoFile(buildDir, fileName.toStdString(), fileSettings ? fileSettings->cfg : std::string()) + ".dump";
dumpFile = QString::fromStdString(mCppcheck.settings().dumpFile);
} else {
dumpFile = fileName + ".dump";
}
if (fileSettings)
mCppcheck.check(*fileSettings);
else
mCppcheck.check(fileName.toStdString());
mCppcheck.settings().dump = false;
mCppcheck.settings().dumpFile.clear();
mCppcheck.settings().buildDir = buildDir;
}
QStringList args;
args << addonFilePath << "--cli" << dumpFile;
if (addon == "misra" && !mMisraFile.isEmpty() && QFileInfo(mMisraFile).exists()) {
if (mMisraFile.endsWith(".pdf", Qt::CaseInsensitive))
args << "--misra-pdf=" + mMisraFile;
else
args << "--rule-texts=" + mMisraFile;
}
qDebug() << python << args;
QProcess process;
QProcessEnvironment env = process.processEnvironment();
if (!env.contains("PYTHONHOME") && !python.startsWith("python")) {
env.insert("PYTHONHOME", QFileInfo(python).canonicalPath());
process.setProcessEnvironment(env);
}
process.start(python, args);
if (!process.waitForFinished()) {
const QString errMsg("ERROR: Process '" + python + " " + args.join(" ") +
"' did not finish successfully: " + process.errorString());
qWarning() << errMsg;
mResult.reportOut(errMsg.toStdString());
}
const QByteArray errout = process.readAllStandardError();
if (process.exitCode() != 0 && !errout.isEmpty()) {
const QString errMsg("ERROR: Process '" + python + " " + args.join(" ") +
"' failed with error code " +
QString::number(process.exitCode()) + ": '" +
process.errorString() +
"'\nError output: " + errout);
qWarning() << errMsg;
mResult.reportOut(errMsg.toStdString());
}
const QString output(process.readAllStandardOutput());
QFile f(dumpFile + '-' + addon + "-results");
if (f.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&f);
out << output;
f.close();
}
parseAddonErrors(output, addon);
} }
} }
@ -352,47 +283,6 @@ void CheckThread::stop()
mCppcheck.terminate(); mCppcheck.terminate();
} }
void CheckThread::parseAddonErrors(QString err, const QString &tool)
{
Q_UNUSED(tool)
QTextStream in(&err, QIODevice::ReadOnly);
while (!in.atEnd()) {
QString line = in.readLine();
if (!line.startsWith("{"))
continue;
const QJsonDocument doc = QJsonDocument::fromJson(line.toLocal8Bit());
const QJsonObject obj = doc.object();
/*
msg = { 'file': location.file,
'linenr': location.linenr,
'column': location.column,
'severity': severity,
'message': message,
'addon': addon,
'errorId': errorId,
'extra': extra}
*/
const std::string &filename = obj["file"].toString().toStdString();
const int lineNumber = obj["linenr"].toInt();
const int column = obj["column"].toInt();
const std::string severity = obj["severity"].toString().toStdString();
const std::string message = obj["message"].toString().toStdString();
const std::string id = (obj["addon"].toString() + "-" + obj["errorId"].toString()).toStdString();
std::list<ErrorLogger::ErrorMessage::FileLocation> callstack;
callstack.push_back(ErrorLogger::ErrorMessage::FileLocation(filename, lineNumber, column));
ErrorLogger::ErrorMessage errmsg(callstack, filename, Severity::fromString(severity), message, id, false);
if (isSuppressed(errmsg.toSuppressionsErrorMessage()))
continue;
mResult.reportErr(errmsg);
}
}
void CheckThread::parseClangErrors(const QString &tool, const QString &file0, QString err) void CheckThread::parseClangErrors(const QString &tool, const QString &file0, QString err)
{ {
QList<ErrorItem> errorItems; QList<ErrorItem> errorItems;
@ -548,45 +438,3 @@ QString CheckThread::clangTidyCmd()
return QString(); return QString();
} }
QString CheckThread::pythonCmd()
{
QString path = QSettings().value(SETTINGS_PYTHON_PATH).toString();
if (!path.isEmpty())
return path;
path = "python";
#ifdef Q_OS_WIN
path += ".exe";
#endif
QProcess process;
process.start(path, QStringList() << "--version");
process.waitForFinished();
if (process.exitCode() == 0)
return path;
return QString();
}
QString CheckThread::getAddonFilePath(const QString &dataDir, const QString &addonFile)
{
const QStringList paths = QStringList() << "/" << "/addons/" << "/../addons/";
if (!dataDir.isEmpty()) {
foreach (const QString p, paths) {
const QString filePath(dataDir + p + addonFile);
if (QFileInfo(filePath).exists())
return filePath;
}
}
const QString appPath = QApplication::applicationDirPath();
foreach (const QString p, paths) {
const QString filePath(appPath + p + addonFile);
if (QFileInfo(filePath).exists())
return filePath;
}
return QString();
}

View File

@ -57,10 +57,6 @@ public:
mAddonsAndTools = addonsAndTools; mAddonsAndTools = addonsAndTools;
} }
void setMisraFile(const QString &misraFile) {
mMisraFile = misraFile;
}
void setDataDir(const QString &dataDir) { void setDataDir(const QString &dataDir) {
mDataDir = dataDir; mDataDir = dataDir;
} }
@ -93,18 +89,6 @@ public:
*/ */
static QString clangTidyCmd(); static QString clangTidyCmd();
/**
* Determine command to run python
* \return Command to run python, empty if it is not found
*/
static QString pythonCmd();
/**
* Look for addon and return path
* \return path to addon if found, empty if it is not found
*/
static QString getAddonFilePath(const QString &dataDir, const QString &addonFile);
signals: signals:
/** /**
@ -144,7 +128,6 @@ protected:
private: private:
void runAddonsAndTools(const ImportProject::FileSettings *fileSettings, const QString &fileName); void runAddonsAndTools(const ImportProject::FileSettings *fileSettings, const QString &fileName);
void parseAddonErrors(QString err, const QString &tool);
void parseClangErrors(const QString &tool, const QString &file0, QString err); void parseClangErrors(const QString &tool, const QString &file0, QString err);
bool isSuppressed(const Suppressions::ErrorMessage &errorMessage) const; bool isSuppressed(const Suppressions::ErrorMessage &errorMessage) const;
@ -155,7 +138,6 @@ private:
QString mDataDir; QString mDataDir;
QStringList mClangIncludePaths; QStringList mClangIncludePaths;
QList<Suppressions::Suppression> mSuppressions; QList<Suppressions::Suppression> mSuppressions;
QString mMisraFile;
}; };
/// @} /// @}
#endif // CHECKTHREAD_H #endif // CHECKTHREAD_H

View File

@ -451,7 +451,7 @@ void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLibrary, cons
//mThread->SetanalyzeProject(true); //mThread->SetanalyzeProject(true);
if (mProjectFile) { if (mProjectFile) {
mThread->setAddonsAndTools(mProjectFile->getAddonsAndTools(), mSettings->value(SETTINGS_MISRA_FILE).toString()); mThread->setAddonsAndTools(mProjectFile->getAddonsAndTools());
QString clangHeaders = mSettings->value(SETTINGS_VS_INCLUDE_PATHS).toString(); QString clangHeaders = mSettings->value(SETTINGS_VS_INCLUDE_PATHS).toString();
mThread->setClangIncludePaths(clangHeaders.split(";")); mThread->setClangIncludePaths(clangHeaders.split(";"));
mThread->setSuppressions(mProjectFile->getSuppressions()); mThread->setSuppressions(mProjectFile->getSuppressions());
@ -494,7 +494,7 @@ void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLibrar
mThread->setFiles(fileNames); mThread->setFiles(fileNames);
if (mProjectFile && !checkConfiguration) if (mProjectFile && !checkConfiguration)
mThread->setAddonsAndTools(mProjectFile->getAddonsAndTools(), mSettings->value(SETTINGS_MISRA_FILE).toString()); mThread->setAddonsAndTools(mProjectFile->getAddonsAndTools());
mThread->setSuppressions(mProjectFile ? mProjectFile->getSuppressions() : QList<Suppressions::Suppression>()); mThread->setSuppressions(mProjectFile ? mProjectFile->getSuppressions() : QList<Suppressions::Suppression>());
QDir inf(mCurrentDirectory); QDir inf(mCurrentDirectory);
const QString checkPath = inf.canonicalPath(); const QString checkPath = inf.canonicalPath();
@ -916,6 +916,30 @@ Settings MainWindow::getCppcheckSettings()
result.safeChecks.externalVariables = mProjectFile->safeChecks.externalVariables; result.safeChecks.externalVariables = mProjectFile->safeChecks.externalVariables;
foreach (QString s, mProjectFile->getCheckUnknownFunctionReturn()) foreach (QString s, mProjectFile->getCheckUnknownFunctionReturn())
result.checkUnknownFunctionReturn.insert(s.toStdString()); result.checkUnknownFunctionReturn.insert(s.toStdString());
QString filesDir(getDataDir(mSettings));
const QString pythonCmd = mSettings->value(SETTINGS_PYTHON_PATH).toString();
foreach (QString addon, mProjectFile->getAddons()) {
QString addonFilePath = ProjectFile::getAddonFilePath(filesDir, addon);
if (addonFilePath.isEmpty())
continue;
QString json;
json += "{ \"script\":\"" + addonFilePath + "\"";
if (!pythonCmd.isEmpty())
json += ", \"python\":\"" + pythonCmd + "\"";
QString misraFile = mSettings->value(SETTINGS_MISRA_FILE).toString();
if (addon == "misra" && !misraFile.isEmpty()) {
QString arg;
if (misraFile.endsWith(".pdf", Qt::CaseInsensitive))
arg = "--misra-pdf=" + misraFile;
else
arg = "--rule-texts=" + misraFile;
json += ", \"args\":[\"" + arg + "\"]";
}
json += " }";
result.addons.push_back(json.toStdString());
}
} }
// Include directories (and files) are searched in listed order. // Include directories (and files) are searched in listed order.
@ -1518,7 +1542,7 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile, const bool check
QDir::setCurrent(inf.absolutePath()); QDir::setCurrent(inf.absolutePath());
mThread->setAddonsAndTools(projectFile->getAddonsAndTools(), mSettings->value(SETTINGS_MISRA_FILE).toString()); mThread->setAddonsAndTools(projectFile->getAddonsAndTools());
mUI.mResults->setTags(projectFile->getTags()); mUI.mResults->setTags(projectFile->getTags());
// If the root path is not given or is not "current dir", use project // If the root path is not given or is not "current dir", use project

View File

@ -995,3 +995,21 @@ void ProjectFile::SafeChecks::saveToXml(QXmlStreamWriter &xmlWriter) const
} }
xmlWriter.writeEndElement(); xmlWriter.writeEndElement();
} }
QString ProjectFile::getAddonFilePath(QString filesDir, QString addon)
{
if (!filesDir.endsWith("/"))
filesDir += "/";
QStringList searchPaths;
searchPaths << filesDir << (filesDir + "addons/") << (filesDir + "../addons/");
foreach (QString path, searchPaths) {
QString f = path + addon + ".py";
if (QFile(f).exists())
return f;
}
return QString();
}

View File

@ -167,6 +167,8 @@ public:
return mAddons; return mAddons;
} }
static QString getAddonFilePath(QString filesDir, QString addon);
/** /**
* @brief Get list of addons and tools. * @brief Get list of addons and tools.
* @return list of addons and tools. * @return list of addons and tools.

View File

@ -234,7 +234,7 @@ static void updateAddonCheckBox(QCheckBox *cb, const ProjectFile *projectFile, c
{ {
if (projectFile) if (projectFile)
cb->setChecked(projectFile->getAddons().contains(addon)); cb->setChecked(projectFile->getAddons().contains(addon));
if (CheckThread::getAddonFilePath(dataDir, addon + ".py").isEmpty()) { if (ProjectFile::getAddonFilePath(dataDir, addon).isEmpty()) {
cb->setEnabled(false); cb->setEnabled(false);
cb->setText(cb->text() + QObject::tr(" (Not found)")); cb->setText(cb->text() + QObject::tr(" (Not found)"));
} }

View File

@ -94,7 +94,6 @@ void ThreadHandler::check(const Settings &settings)
for (int i = 0; i < mRunningThreadCount; i++) { for (int i = 0; i < mRunningThreadCount; i++) {
mThreads[i]->setAddonsAndTools(mAddonsAndTools); mThreads[i]->setAddonsAndTools(mAddonsAndTools);
mThreads[i]->setMisraFile(mMisraFile);
mThreads[i]->setSuppressions(mSuppressions); mThreads[i]->setSuppressions(mSuppressions);
mThreads[i]->setClangIncludePaths(mClangIncludePaths); mThreads[i]->setClangIncludePaths(mClangIncludePaths);
mThreads[i]->setDataDir(mDataDir); mThreads[i]->setDataDir(mDataDir);

View File

@ -72,9 +72,8 @@ public:
*/ */
void saveSettings(QSettings &settings) const; void saveSettings(QSettings &settings) const;
void setAddonsAndTools(const QStringList &addonsAndTools, const QString &misraFile) { void setAddonsAndTools(const QStringList &addonsAndTools) {
mAddonsAndTools = addonsAndTools; mAddonsAndTools = addonsAndTools;
mMisraFile = misraFile;
} }
void setSuppressions(const QList<Suppressions::Suppression> &s) { void setSuppressions(const QList<Suppressions::Suppression> &s) {
@ -262,7 +261,6 @@ protected:
QStringList mClangIncludePaths; QStringList mClangIncludePaths;
QString mDataDir; QString mDataDir;
QString mMisraFile;
private: private:
/** /**

View File

@ -87,7 +87,41 @@ namespace {
return ""; return "";
} }
std::string parseAddonInfo(const picojson::value &json, const std::string &fileName, const std::string &exename) {
std::string json_error = picojson::get_last_error();
if (!json_error.empty()) {
return "Loading " + fileName + " failed. " + json_error;
}
if (!json.is<picojson::object>())
return "Loading " + fileName + " failed. Bad json.";
picojson::object obj = json.get<picojson::object>();
if (obj.count("args")) {
if (!obj["args"].is<picojson::array>())
return "Loading " + fileName + " failed. args must be array.";
for (const picojson::value &v : obj["args"].get<picojson::array>())
args += " " + v.get<std::string>();
}
if (obj.count("python")) {
// Python was defined in the config file
if (obj["python"].is<picojson::array>()) {
return "Loading " + fileName +" failed. python must not be an array.";
}
python = obj["python"].get<std::string>();
} else {
python = "";
}
return getAddonInfo(obj["script"].get<std::string>(), exename);
}
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] == '{') {
std::istringstream in(fileName);
picojson::value json;
in >> json;
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);
@ -117,31 +151,7 @@ namespace {
return "Failed to open " + fileName; return "Failed to open " + fileName;
picojson::value json; picojson::value json;
fin >> json; fin >> json;
std::string json_error = picojson::get_last_error(); return parseAddonInfo(json, fileName, exename);
if (!json_error.empty()) {
return "Loading " + fileName + " failed. " + json_error;
}
if (!json.is<picojson::object>())
return "Loading " + fileName + " failed. Bad json.";
picojson::object obj = json.get<picojson::object>();
if (obj.count("args")) {
if (!obj["args"].is<picojson::array>())
return "Loading " + fileName + " failed. args must be array.";
for (const picojson::value &v : obj["args"].get<picojson::array>())
args += " " + v.get<std::string>();
}
if (obj.count("python")) {
// Python was defined in the config file
if (obj["python"].is<picojson::array>()) {
return "Loading " + fileName +" failed. python must not be an array.";
}
python = obj["python"].get<std::string>();
} else {
python = "";
}
return getAddonInfo(obj["script"].get<std::string>(), exename);
} }
}; };
} }

View File

@ -71,6 +71,7 @@ private:
public: public:
Settings(); Settings();
/** @brief addons, either filename of python/json file or json data */
std::list<std::string> addons; std::list<std::string> addons;
/** @brief Path to the python interpreter to be used to run addons. */ /** @brief Path to the python interpreter to be used to run addons. */