diff --git a/gui/erroritem.cpp b/gui/erroritem.cpp index 2d7aa1c0bbe..ca9e8475b97 100644 --- a/gui/erroritem.cpp +++ b/gui/erroritem.cpp @@ -70,21 +70,22 @@ QString ErrorItem::tool() const QString ErrorItem::toString() const { - QString str = errorPath.back().file + " - " + errorId + " - "; + const int i = getMainLocIndex(); + QString ret = errorPath[i].file + ":" + QString::number(errorPath[i].line) + ":" + QString::number(errorPath[i].column) + ":"; + ret += GuiSeverity::toString(severity); if (inconclusive) - str += "inconclusive "; - str += GuiSeverity::toString(severity) +"\n"; - str += summary + "\n"; - str += message + "\n"; - for (const QErrorPathItem& i : errorPath) { - str += " " + i.file + ": " + QString::number(i.line) + "\n"; + ret += ",inconclusive"; + ret += ": " + summary + " [" + errorId + "]"; + if (errorPath.size() >= 2) { + for (const auto& e: errorPath) + ret += "\n" + e.file + ":" + QString::number(e.line) + ":" + QString::number(e.column) + ":note: " + e.info; } - return str; + return ret; } -bool ErrorItem::sameCID(const ErrorItem &errorItem1, const ErrorItem &errorItem2) +bool ErrorItem::same(const ErrorItem &errorItem1, const ErrorItem &errorItem2) { - if (errorItem1.hash || errorItem2.hash) + if (errorItem1.hash && errorItem2.hash) return errorItem1.hash == errorItem2.hash; // fallback @@ -95,3 +96,19 @@ bool ErrorItem::sameCID(const ErrorItem &errorItem1, const ErrorItem &errorItem2 errorItem1.inconclusive == errorItem2.inconclusive && errorItem1.severity == errorItem2.severity; } + +bool ErrorItem::filterMatch(const QString& filter) const +{ + if (filter.isEmpty()) + return true; + if (summary.contains(filter, Qt::CaseInsensitive) || + message.contains(filter, Qt::CaseInsensitive) || + errorId.contains(filter, Qt::CaseInsensitive) || + classification.contains(filter, Qt::CaseInsensitive)) + return true; + return std::any_of(errorPath.cbegin(), errorPath.cend(), + [filter](const auto& e) { + return e.file.contains(filter, Qt::CaseInsensitive) || + e.info.contains(filter, Qt::CaseInsensitive); + }); +} diff --git a/gui/erroritem.h b/gui/erroritem.h index dfd48fbadfd..a95d9eb2204 100644 --- a/gui/erroritem.h +++ b/gui/erroritem.h @@ -81,6 +81,20 @@ class ErrorItem { QString toString() const; QString tool() const; + int getMainLocIndex() const { + return isClangResult() ? 0 : errorPath.size() - 1; + } + + QString getFile() const { + return errorPath.isEmpty() ? QString() : errorPath[getMainLocIndex()].file; + } + + bool isClangResult() const { + return errorId.startsWith("clang"); + } + + bool filterMatch(const QString& filter) const; + QString file0; QString errorId; Severity severity; @@ -100,33 +114,9 @@ class ErrorItem { QString tags; /** - * Compare "CID" + * Compare Hash and fields */ - static bool sameCID(const ErrorItem &errorItem1, const ErrorItem &errorItem2); + static bool same(const ErrorItem &errorItem1, const ErrorItem &errorItem2); }; - -// NOLINTNEXTLINE(performance-no-int-to-ptr) -Q_DECLARE_METATYPE(ErrorItem) - -/** - * @brief A class containing error data for one shown error line. - */ -class ErrorLine { -public: - QString file; - int line; - QString file0; - QString errorId; - int cwe; - unsigned long long hash; - bool inconclusive; - Severity severity; - QString summary; - QString message; - QString sinceDate; - QString tags; - QString remark; -}; - /// @} #endif // ERRORITEM_H diff --git a/gui/resultitem.cpp b/gui/resultitem.cpp new file mode 100644 index 00000000000..b68830b90ba --- /dev/null +++ b/gui/resultitem.cpp @@ -0,0 +1,23 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2025 Cppcheck team. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resultitem.h" + +ResultItem::ResultItem(QSharedPointer errorItem, Type type, int errorPathIndex) + : errorItem(std::move(errorItem)), mType(type), mErrorPathIndex(errorPathIndex) +{} diff --git a/gui/resultitem.h b/gui/resultitem.h new file mode 100644 index 00000000000..a7669d2a3b4 --- /dev/null +++ b/gui/resultitem.h @@ -0,0 +1,49 @@ +/* -*- C++ -*- + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2025 Cppcheck team. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef RESULTITEM_H +#define RESULTITEM_H + +#include "erroritem.h" +#include +#include + +class ResultItem : public QStandardItem +{ +public: + enum class Type: std::uint8_t {file, message, note}; + + ResultItem(QSharedPointer errorItem, Type type, int errorPathIndex); + QSharedPointer errorItem; + bool hidden{}; + + QErrorPathItem getErrorPathItem() const { + if (!errorItem || mErrorPathIndex < 0 || mErrorPathIndex >= errorItem->errorPath.size()) + return {}; + return errorItem->errorPath[mErrorPathIndex]; + } + + Type getType() const { + return mType; + } +private: + const Type mType; + const int mErrorPathIndex; +}; + +#endif // RESULTITEM_H diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index e5e2ff9ead2..12191b05cbd 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -28,6 +28,7 @@ #include "path.h" #include "projectfile.h" #include "report.h" +#include "resultitem.h" #include "showtypes.h" #include "suppressions.h" #include "threadhandler.h" @@ -59,31 +60,11 @@ #include #include #include -#include #include #include -#include -#include #include #include -static constexpr char COLUMN[] = "column"; -static constexpr char CWE[] = "cwe"; -static constexpr char ERRORID[] = "id"; -static constexpr char FILENAME[] = "file"; -static constexpr char FILE0[] = "file0"; -static constexpr char HASH[] = "hash"; -static constexpr char HIDE[] = "hide"; -static constexpr char INCONCLUSIVE[] = "inconclusive"; -static constexpr char LINE[] = "line"; -static constexpr char MESSAGE[] = "message"; -static constexpr char REMARK[] = "remark"; -static constexpr char SEVERITY[] = "severity"; -static constexpr char SINCEDATE[] = "sinceDate"; -static constexpr char SYMBOLNAMES[] = "symbolNames"; -static constexpr char SUMMARY[] = "summary"; -static constexpr char TAGS[] = "tags"; - // These must match column headers given in ResultsTree::translate() static constexpr int COLUMN_FILE = 0; static constexpr int COLUMN_LINE = 1; @@ -172,19 +153,17 @@ void ResultsTree::setReportType(ReportType reportType) { mGuideline = createGuidelineMapping(reportType); for (int i = 0; i < mModel->rowCount(); ++i) { - const QStandardItem *fileItem = mModel->item(i, COLUMN_FILE); + auto *fileItem = dynamic_cast(mModel->item(i, COLUMN_FILE)); if (!fileItem) continue; for (int j = 0; j < fileItem->rowCount(); ++j) { - const auto& childdata = fileItem->child(j,0)->data().toMap(); - const QString& errorId = childdata[ERRORID].toString(); - Severity severity = ShowTypes::ShowTypeToSeverity(ShowTypes::VariantToShowType(childdata[SEVERITY])); - const QString& guideline = getGuideline(mReportType, mGuideline, errorId, severity); - const QString& classification = getClassification(mReportType, guideline); - fileItem->child(j, COLUMN_CERT_LEVEL)->setText(classification); - fileItem->child(j, COLUMN_CERT_RULE)->setText(guideline); - fileItem->child(j, COLUMN_MISRA_CLASSIFICATION)->setText(classification); - fileItem->child(j, COLUMN_MISRA_GUIDELINE)->setText(guideline); + QSharedPointer& errorItem = dynamic_cast(fileItem->child(j,0))->errorItem; + errorItem->guideline = getGuideline(mReportType, mGuideline, errorItem->errorId, errorItem->severity); + errorItem->classification = getClassification(mReportType, errorItem->guideline); + fileItem->child(j, COLUMN_CERT_LEVEL)->setText(errorItem->classification); + fileItem->child(j, COLUMN_CERT_RULE)->setText(errorItem->guideline); + fileItem->child(j, COLUMN_MISRA_CLASSIFICATION)->setText(errorItem->classification); + fileItem->child(j, COLUMN_MISRA_GUIDELINE)->setText(errorItem->guideline); } } @@ -221,163 +200,93 @@ void ResultsTree::initialize(QSettings *settings, ApplicationList *list, ThreadH loadSettings(); } +ResultItem *ResultsTree::createNormalItem(const QString &text, QSharedPointer errorItem, ResultItem::Type type, int errorPathIndex) +{ + auto *item = new ResultItem(std::move(errorItem), type, errorPathIndex); + item->setText(text); + item->setEditable(false); + return item; +} -QStandardItem *ResultsTree::createNormalItem(const QString &name) +ResultItem *ResultsTree::createFilenameItem(const QSharedPointer& errorItem, ResultItem::Type type, int errorPathIndex) { - auto *item = new QStandardItem(name); - item->setData(name, Qt::ToolTipRole); + auto *item = new ResultItem(errorItem, type, errorPathIndex); + item->setText(QDir::toNativeSeparators(stripPath(errorItem->errorPath[errorPathIndex].file, false))); item->setEditable(false); return item; } -QStandardItem *ResultsTree::createCheckboxItem(bool checked) +ResultItem *ResultsTree::createCheckboxItem(bool checked, QSharedPointer errorItem, ResultItem::Type type, int errorPathIndex) { - auto *item = new QStandardItem; + auto *item = new ResultItem(std::move(errorItem), type, errorPathIndex); item->setCheckable(true); item->setCheckState(checked ? Qt::Checked : Qt::Unchecked); item->setEnabled(false); return item; } -QStandardItem *ResultsTree::createLineNumberItem(const QString &linenumber) +ResultItem *ResultsTree::createLineNumberItem(int linenumber, QSharedPointer errorItem, ResultItem::Type type, int errorPathIndex) { - auto *item = new QStandardItem(); - item->setData(QVariant(linenumber.toInt()), Qt::DisplayRole); - item->setToolTip(linenumber); + auto *item = new ResultItem(std::move(errorItem), type, errorPathIndex); + item->setText(QString::number(linenumber)); item->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); item->setEditable(false); return item; } -bool ResultsTree::addErrorItem(const ErrorItem &item) +bool ResultsTree::addErrorItem(const ErrorItem& errorItem) { - if (item.errorPath.isEmpty()) { + if (errorItem.errorPath.isEmpty()) return false; - } - - const QErrorPathItem &loc = item.errorId.startsWith("clang") ? item.errorPath.front() : item.errorPath.back(); - QString realfile = stripPath(loc.file, false); - if (realfile.isEmpty()) { - realfile = tr("Undefined file"); - } - - bool showItem = true; + QSharedPointer errorItemPtr{new ErrorItem(errorItem)}; - // Ids that are temporarily hidden.. - if (mHiddenMessageId.contains(item.errorId)) - showItem = false; - - //If specified, filter on summary, message, filename, and id - if (showItem && !mFilter.isEmpty()) { - if (!item.summary.contains(mFilter, Qt::CaseInsensitive) && - !item.message.contains(mFilter, Qt::CaseInsensitive) && - !item.errorPath.back().file.contains(mFilter, Qt::CaseInsensitive) && - !item.errorId.contains(mFilter, Qt::CaseInsensitive)) { - showItem = false; - } + if (mReportType != ReportType::normal) { + errorItemPtr->guideline = getGuideline(mReportType, mGuideline, errorItemPtr->errorId, errorItemPtr->severity); + errorItemPtr->classification = getClassification(mReportType, errorItemPtr->guideline); } - if (showItem) { - if (mReportType == ReportType::normal) - showItem = mShowSeverities.isShown(item.severity); - else { - const QString& guideline = getGuideline(mReportType, mGuideline, item.errorId, item.severity); - const QString& classification = getClassification(mReportType, guideline); - showItem = !classification.isEmpty() && mShowSeverities.isShown(getSeverityFromClassification(classification)); - } - } + const bool showItem = !isErrorItemHidden(errorItemPtr); // if there is at least one error that is not hidden, we have a visible error mVisibleErrors |= showItem; - ErrorLine line; - line.file = realfile; - line.line = loc.line; - line.errorId = item.errorId; - line.cwe = item.cwe; - line.hash = item.hash; - line.inconclusive = item.inconclusive; - line.summary = item.summary; - line.message = item.message; - line.severity = item.severity; - line.sinceDate = item.sinceDate; - if (const ProjectFile *activeProject = ProjectFile::getActiveProject()) { - line.tags = activeProject->getWarningTags(item.hash); - } - line.remark = item.remark; + if (const ProjectFile *activeProject = ProjectFile::getActiveProject()) + errorItemPtr->tags = activeProject->getWarningTags(errorItemPtr->hash); //Create the base item for the error and ensure it has a proper //file item as a parent - QStandardItem* fileItem = ensureFileItem(loc.file, item.file0, !showItem); - QStandardItem* stditem = addBacktraceFiles(fileItem, - line, - !showItem, - severityToIcon(line.severity), - false); + ResultItem* fileItem = ensureFileItem(errorItemPtr, !showItem); + ResultItem* stditem = addBacktraceFiles(fileItem, + errorItemPtr, + !showItem, + severityToIcon(errorItemPtr->severity), + ResultItem::Type::message, + errorItemPtr->getMainLocIndex()); if (!stditem) return false; - //Add user data to that item - QMap itemdata; - itemdata[SEVERITY] = ShowTypes::SeverityToShowType(item.severity); - itemdata[SUMMARY] = item.summary; - itemdata[MESSAGE] = item.message; - itemdata[FILENAME] = loc.file; - itemdata[LINE] = loc.line; - itemdata[COLUMN] = loc.column; - itemdata[ERRORID] = item.errorId; - itemdata[CWE] = item.cwe; - itemdata[HASH] = item.hash; - itemdata[INCONCLUSIVE] = item.inconclusive; - itemdata[FILE0] = stripPath(item.file0, true); - itemdata[SINCEDATE] = item.sinceDate; - itemdata[SYMBOLNAMES] = item.symbolNames; - itemdata[TAGS] = line.tags; - itemdata[REMARK] = line.remark; - itemdata[HIDE] = false; - stditem->setData(QVariant(itemdata)); - //Add backtrace files as children - if (item.errorPath.size() > 1) { - for (int i = 0; i < item.errorPath.size(); i++) { - const QErrorPathItem &e = item.errorPath[i]; - line.file = e.file; - line.line = e.line; - line.message = line.summary = e.info; - QStandardItem *child_item = addBacktraceFiles(stditem, - line, - false, - ":images/go-down.png", - true); - if (!child_item) - continue; - - // Add user data to that item - QMap child_data; - child_data[SEVERITY] = ShowTypes::SeverityToShowType(line.severity); - child_data[SUMMARY] = line.summary; - child_data[MESSAGE] = line.message; - child_data[FILENAME] = e.file; - child_data[LINE] = e.line; - child_data[COLUMN] = e.column; - child_data[ERRORID] = line.errorId; - child_data[CWE] = line.cwe; - child_data[HASH] = line.hash; - child_data[INCONCLUSIVE] = line.inconclusive; - child_data[SYMBOLNAMES] = item.symbolNames; - child_item->setData(QVariant(child_data)); + if (errorItemPtr->errorPath.size() > 1) { + for (int i = 0; i < errorItemPtr->errorPath.size(); i++) { + addBacktraceFiles(stditem, + errorItemPtr, + false, + ":images/go-down.png", + ResultItem::Type::note, + i); } } return true; } -QStandardItem *ResultsTree::addBacktraceFiles(QStandardItem *parent, - const ErrorLine &item, - const bool hide, - const QString &icon, - bool childOfMessage) +ResultItem *ResultsTree::addBacktraceFiles(ResultItem *parent, + const QSharedPointer& errorItem, + const bool hide, + const QString &icon, + ResultItem::Type type, + int errorPathIndex) { if (!parent) return nullptr; @@ -385,56 +294,52 @@ QStandardItem *ResultsTree::addBacktraceFiles(QStandardItem *parent, //TODO message has parameter names so we'll need changes to the core //cppcheck so we can get proper translations - const QString itemSeverity = childOfMessage ? tr("note") : severityToTranslatedString(item.severity); + const bool childOfMessage = (type == ResultItem::Type::note); + const QString itemSeverity = childOfMessage ? tr("note") : severityToTranslatedString(errorItem->severity); + + const auto& loc = errorItem->errorPath[errorPathIndex]; // Check for duplicate rows and don't add them if found - for (int i = 0; i < parent->rowCount(); i++) { + for (int i = 0; i < errorPathIndex; i++) { // The first column is the file name and is always the same - - // the third column is the line number so check it first - if (parent->child(i, COLUMN_LINE)->text() == QString::number(item.line)) { - // the second column is the severity so check it next - if (parent->child(i, COLUMN_SEVERITY)->text() == itemSeverity) { - // the sixth column is the summary so check it last - if (parent->child(i, COLUMN_SUMMARY)->text() == item.summary) { - // this row matches so don't add it - return nullptr; - } - } - } + const auto& e = errorItem->errorPath[i]; + if (loc.line == e.line && loc.info == e.info) + return nullptr; } - QMap columns; - const QString guideline = getGuideline(mReportType, mGuideline, item.errorId, item.severity); - const QString classification = getClassification(mReportType, guideline); - columns[COLUMN_CERT_LEVEL] = createNormalItem(classification); - columns[COLUMN_CERT_RULE] = createNormalItem(guideline); - columns[COLUMN_CWE] = createNormalItem(item.cwe > 0 ? QString::number(item.cwe) : QString()); - columns[COLUMN_FILE] = createNormalItem(QDir::toNativeSeparators(item.file)); - columns[COLUMN_ID] = createNormalItem(childOfMessage ? QString() : item.errorId); - columns[COLUMN_INCONCLUSIVE] = childOfMessage ? createNormalItem(QString()) : createCheckboxItem(item.inconclusive); - columns[COLUMN_LINE] = createLineNumberItem(QString::number(item.line)); - columns[COLUMN_MISRA_CLASSIFICATION] = createNormalItem(classification); - columns[COLUMN_MISRA_GUIDELINE] = createNormalItem(guideline); - columns[COLUMN_SEVERITY] = createNormalItem(itemSeverity); - columns[COLUMN_SINCE_DATE] = createNormalItem(item.sinceDate); - columns[COLUMN_SUMMARY] = createNormalItem(item.summary); - columns[COLUMN_TAGS] = createNormalItem(item.tags); + const QString text = childOfMessage ? loc.info : errorItem->summary; const int numberOfColumns = getLabels().size(); + QList columns(numberOfColumns); + columns[COLUMN_FILE] = createFilenameItem(errorItem, type, errorPathIndex); + columns[COLUMN_LINE] = createLineNumberItem(loc.line, errorItem, type, errorPathIndex); + columns[COLUMN_SEVERITY] = createNormalItem(itemSeverity, errorItem, type, errorPathIndex); + columns[COLUMN_SUMMARY] = createNormalItem(text, errorItem, type, errorPathIndex); + if (type == ResultItem::Type::message) { + columns[COLUMN_CERT_LEVEL] = createNormalItem(errorItem->classification, errorItem, type, errorPathIndex); + columns[COLUMN_CERT_RULE] = createNormalItem(errorItem->guideline, errorItem, type, errorPathIndex); + columns[COLUMN_CWE] = createNormalItem(errorItem->cwe > 0 ? QString::number(errorItem->cwe) : QString(), errorItem, type, errorPathIndex); + columns[COLUMN_ID] = createNormalItem(errorItem->errorId, errorItem, type, errorPathIndex); + columns[COLUMN_INCONCLUSIVE] = createCheckboxItem(errorItem->inconclusive, errorItem, type, errorPathIndex); + columns[COLUMN_MISRA_CLASSIFICATION] = createNormalItem(errorItem->classification, errorItem, type, errorPathIndex); + columns[COLUMN_MISRA_GUIDELINE] = createNormalItem(errorItem->guideline, errorItem, type, errorPathIndex); + columns[COLUMN_SINCE_DATE] = createNormalItem(errorItem->sinceDate, errorItem, type, errorPathIndex); + columns[COLUMN_TAGS] = createNormalItem(errorItem->tags, errorItem, type, errorPathIndex); + } + QList list; for (int i = 0; i < numberOfColumns; ++i) - list << columns[i]; + list << (columns[i] ? columns[i] : createNormalItem(QString(), errorItem, type, errorPathIndex)); parent->appendRow(list); setRowHidden(parent->rowCount() - 1, parent->index(), hide); if (!icon.isEmpty()) { - list[0]->setIcon(QIcon(icon)); + list[COLUMN_FILE]->setIcon(QIcon(icon)); } - return list[0]; + return columns[COLUMN_FILE]; } QString ResultsTree::severityToTranslatedString(Severity severity) @@ -470,7 +375,7 @@ QString ResultsTree::severityToTranslatedString(Severity severity) } } -QStandardItem *ResultsTree::findFileItem(const QString &name) const +ResultItem *ResultsTree::findFileItem(const QString &name) const { // The first column contains the file name. In Windows we can get filenames // "header.h" and "Header.h" and must compare them as identical. @@ -481,7 +386,7 @@ QStandardItem *ResultsTree::findFileItem(const QString &name) const #else if (mModel->item(i, COLUMN_FILE)->text() == name) #endif - return mModel->item(i, COLUMN_FILE); + return dynamic_cast(mModel->item(i, COLUMN_FILE)); } return nullptr; } @@ -504,16 +409,15 @@ void ResultsTree::clear() void ResultsTree::clear(const QString &filename) { - const QString stripped = stripPath(filename, false); + const QString stripped = QDir::toNativeSeparators(stripPath(filename, false)); for (int i = 0; i < mModel->rowCount(); ++i) { - const QStandardItem *fileItem = mModel->item(i, COLUMN_FILE); + const auto *fileItem = dynamic_cast(mModel->item(i, COLUMN_FILE)); if (!fileItem) continue; - QVariantMap fitemdata = fileItem->data().toMap(); - if (stripped == fitemdata[FILENAME].toString() || - filename == fitemdata[FILE0].toString()) { + if (stripped == fileItem->text() || + filename == fileItem->errorItem->file0) { mModel->removeRow(i); break; } @@ -523,13 +427,12 @@ void ResultsTree::clear(const QString &filename) void ResultsTree::clearRecheckFile(const QString &filename) { for (int i = 0; i < mModel->rowCount(); ++i) { - const QStandardItem *fileItem = mModel->item(i, COLUMN_FILE); + const auto *fileItem = dynamic_cast(mModel->item(i, COLUMN_FILE)); if (!fileItem) continue; QString actualfile((!mCheckPath.isEmpty() && filename.startsWith(mCheckPath)) ? filename.mid(mCheckPath.length() + 1) : filename); - QVariantMap fitemdata = fileItem->data().toMap(); - QString storedfile = fitemdata[FILENAME].toString(); + QString storedfile = fileItem->getErrorPathItem().file; storedfile = ((!mCheckPath.isEmpty() && storedfile.startsWith(mCheckPath)) ? storedfile.mid(mCheckPath.length() + 1) : storedfile); if (actualfile == storedfile) { mModel->removeRow(i); @@ -605,7 +508,7 @@ void ResultsTree::refreshTree() for (int i = 0; i < filecount; i++) { //Get file i - QStandardItem *fileItem = mModel->item(i, 0); + auto *fileItem = dynamic_cast(mModel->item(i, 0)); if (!fileItem) { continue; } @@ -618,44 +521,13 @@ void ResultsTree::refreshTree() for (int j = 0; j < errorcount; j++) { //Get the error itself - QStandardItem *child = fileItem->child(j, 0); + const auto *child = dynamic_cast(fileItem->child(j, 0)); if (!child) { continue; } - //Get error's user data and convert it to QVariantMap - QVariantMap userdata = child->data().toMap(); - //Check if this error should be hidden - bool hide = userdata[HIDE].toBool() || mHiddenMessageId.contains(userdata[ERRORID].toString()); - - if (!hide) { - if (mReportType == ReportType::normal) - hide = !mShowSeverities.isShown(ShowTypes::VariantToShowType(userdata[SEVERITY])); - else { - const QString& classification = fileItem->child(j, COLUMN_MISRA_CLASSIFICATION)->text(); - hide = classification.isEmpty() || !mShowSeverities.isShown(getSeverityFromClassification(classification)); - } - } - - // If specified, filter on summary, message, filename, and id - if (!hide && !mFilter.isEmpty()) { - if (!userdata[SUMMARY].toString().contains(mFilter, Qt::CaseInsensitive) && - !userdata[MESSAGE].toString().contains(mFilter, Qt::CaseInsensitive) && - !userdata[FILENAME].toString().contains(mFilter, Qt::CaseInsensitive) && - !userdata[ERRORID].toString().contains(mFilter, Qt::CaseInsensitive) && - !fileItem->child(j, COLUMN_MISRA_CLASSIFICATION)->text().contains(mFilter, Qt::CaseInsensitive)) { - hide = true; - } - } - - // Tool filter - if (!hide) { - if (userdata[ERRORID].toString().startsWith("clang")) - hide = !mShowClang; - else - hide = !mShowCppcheck; - } + const bool hide = child->hidden || isErrorItemHidden(child->errorItem); if (!hide) { showFile = true; @@ -672,34 +544,54 @@ void ResultsTree::refreshTree() sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder()); } -QStandardItem *ResultsTree::ensureFileItem(const QString &fullpath, const QString &file0, bool hide) +bool ResultsTree::isErrorItemHidden(const QSharedPointer& errorItem) const { + //Check if this error should be hidden + if (mHiddenMessageId.contains(errorItem->errorId)) + return true; + + bool hide; + if (mReportType == ReportType::normal) + hide = !mShowSeverities.isShown(errorItem->severity); + else + hide = errorItem->classification.isEmpty() || !mShowSeverities.isShown(getSeverityFromClassification(errorItem->classification)); + + // If specified, filter on summary, message, filename, and id + if (!hide && !mFilter.isEmpty()) + hide = !errorItem->filterMatch(mFilter); + + // Tool filter + if (!hide) { + if (errorItem->isClangResult()) + hide = !mShowClang; + else + hide = !mShowCppcheck; + } + + return hide; +} + +ResultItem *ResultsTree::ensureFileItem(const QSharedPointer& errorItem, bool hide) { - QString name = stripPath(fullpath, false); + QString name = QDir::toNativeSeparators(stripPath(errorItem->getFile(), false)); // Since item has path with native separators we must use path with // native separators to find it. - QStandardItem *item = findFileItem(QDir::toNativeSeparators(name)); + ResultItem *fileItem = findFileItem(name); - if (item) { + if (fileItem) { if (!hide) - setRowHidden(item->row(), QModelIndex(), hide); - return item; + setRowHidden(fileItem->row(), QModelIndex(), hide); + return fileItem; } // Ensure shown path is with native separators - name = QDir::toNativeSeparators(name); - item = createNormalItem(name); - item->setIcon(QIcon(":images/text-x-generic.png")); + fileItem = createFilenameItem(errorItem, ResultItem::Type::file, errorItem->getMainLocIndex()); + fileItem->setIcon(QIcon(":images/text-x-generic.png")); - //Add user data to that item - QMap itemdata; - itemdata[FILENAME] = fullpath; - itemdata[FILE0] = file0; - item->setData(QVariant(itemdata)); - mModel->appendRow(item); + mModel->appendRow(fileItem); - setRowHidden(item->row(), QModelIndex(), hide); + setRowHidden(fileItem->row(), QModelIndex(), hide); - return item; + return fileItem; } void ResultsTree::contextMenuEvent(QContextMenuEvent * e) @@ -712,7 +604,7 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e) if (mSelectionModel->selectedRows().count() > 1) multipleSelection = true; - mContextItem = mModel->itemFromIndex(index); + mContextItem = dynamic_cast(mModel->itemFromIndex(index)); //Create a new context menu QMenu menu(this); @@ -788,10 +680,7 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e) auto *suppress = new QAction(tr("Suppress selected id(s)"), &menu); { - QVariantMap itemdata = mContextItem->data().toMap(); - const QString messageId = itemdata[ERRORID].toString(); - - if (selectedResults == 0 || ErrorLogger::isCriticalErrorId(messageId.toStdString())) + if (selectedResults == 0 || ErrorLogger::isCriticalErrorId(mContextItem->errorItem->errorId.toStdString())) suppress->setDisabled(true); } menu.addAction(suppress); @@ -831,13 +720,12 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e) //Start the menu menu.exec(e->globalPos()); index = indexAt(e->pos()); - if (index.isValid()) { - mContextItem = mModel->itemFromIndex(index); - } + if (index.isValid()) + mContextItem = dynamic_cast(mModel->itemFromIndex(index)); } } -void ResultsTree::startApplication(const QStandardItem *target, int application) +void ResultsTree::startApplication(const ResultItem *target, int application) { //If there are no applications specified, tell the user about it if (mApplications->getApplicationCount() == 0) { @@ -863,22 +751,16 @@ void ResultsTree::startApplication(const QStandardItem *target, int application) this); msg.exec(); return; - } if (target && application >= 0 && application < mApplications->getApplicationCount() && target->parent()) { - // Make sure we are working with the first column - if (target->column() != 0) - target = target->parent()->child(target->row(), 0); - - QVariantMap targetdata = target->data().toMap(); + const auto& errorPathItem = target->getErrorPathItem(); //Replace (file) with filename - QString file = targetdata[FILENAME].toString(); - file = QDir::toNativeSeparators(file); + QString file = QDir::toNativeSeparators(errorPathItem.file); qDebug() << "Opening file: " << file; - QFileInfo info(file); + const QFileInfo info(file); if (!info.exists()) { if (info.isAbsolute()) { QMessageBox msgbox(this); @@ -907,11 +789,10 @@ void ResultsTree::startApplication(const QStandardItem *target, int application) QString params = app.getParameters(); params.replace("(file)", file, Qt::CaseInsensitive); - QVariant line = targetdata[LINE]; - params.replace("(line)", QString("%1").arg(line.toInt()), Qt::CaseInsensitive); + params.replace("(line)", QString::number(errorPathItem.line), Qt::CaseInsensitive); - params.replace("(message)", targetdata[MESSAGE].toString(), Qt::CaseInsensitive); - params.replace("(severity)", targetdata[SEVERITY].toString(), Qt::CaseInsensitive); + params.replace("(message)", target->errorItem->message, Qt::CaseInsensitive); + params.replace("(severity)", severityToTranslatedString(target->errorItem->severity), Qt::CaseInsensitive); QString program = app.getPath(); @@ -998,25 +879,17 @@ void ResultsTree::copy() QString text; for (const QModelIndex& index : mSelectionModel->selectedRows()) { - const QStandardItem *item = mModel->itemFromIndex(index); - if (!item->parent()) { - text += item->text() + '\n'; + const auto *item = dynamic_cast(mModel->itemFromIndex(index)); + if (!item) continue; + if (item->getType() == ResultItem::Type::file) + text += item->text() + '\n'; + else if (item->getType() == ResultItem::Type::message) + text += item->errorItem->toString() + '\n'; + else if (item->getType() == ResultItem::Type::note) { + const auto e = item->getErrorPathItem(); + text += e.file + ":" + QString::number(e.line) + ":" + QString::number(e.column) + ":note: " + e.info + '\n'; } - if (item->parent()->parent()) - item = item->parent(); - QVariantMap itemdata = item->data().toMap(); - if (!itemdata.contains("id")) - continue; - QString inconclusive = itemdata[INCONCLUSIVE].toBool() ? ",inconclusive" : ""; - text += itemdata[FILENAME].toString() + ':' + QString::number(itemdata[LINE].toInt()) + ':' + QString::number(itemdata[COLUMN].toInt()) - + ": " - + QString::fromStdString(severityToString(ShowTypes::ShowTypeToSeverity(static_cast(itemdata[SEVERITY].toInt())))) + inconclusive - + ": " - + itemdata[MESSAGE].toString() - + " [" - + itemdata[ERRORID].toString() - + "]\n"; } QClipboard *clipboard = QApplication::clipboard(); @@ -1027,14 +900,13 @@ void ResultsTree::hideResult() { if (!mSelectionModel) return; - - for (QModelIndex index : mSelectionModel->selectedRows()) { - QStandardItem *item = mModel->itemFromIndex(index); - //Set the "hide" flag for this item - QVariantMap itemdata = item->data().toMap(); - itemdata[HIDE] = true; - item->setData(QVariant(itemdata)); - + bool hide = false; + for (const QModelIndex& index : mSelectionModel->selectedRows()) { + auto *item = dynamic_cast(mModel->itemFromIndex(index)); + if (item && item->getType() == ResultItem::Type::message) + hide = item->hidden = true; + } + if (hide) { refreshTree(); emit resultsHidden(true); } @@ -1046,15 +918,15 @@ void ResultsTree::recheckSelectedFiles() return; QStringList selectedItems; - for (QModelIndex index : mSelectionModel->selectedRows()) { - QStandardItem *item = mModel->itemFromIndex(index); + for (const QModelIndex& index : mSelectionModel->selectedRows()) { + const auto *item = dynamic_cast(mModel->itemFromIndex(index)); while (item->parent()) - item = item->parent(); - QVariantMap itemdata = item->data().toMap(); - QString currentFile = itemdata[FILENAME].toString(); + item = dynamic_cast(item->parent()); + const auto e = item->getErrorPathItem(); + const QString currentFile = e.file; if (!currentFile.isEmpty()) { QString fileNameWithCheckPath; - QFileInfo curfileInfo(currentFile); + const QFileInfo curfileInfo(currentFile); if (!curfileInfo.exists() && !mCheckPath.isEmpty() && currentFile.indexOf(mCheckPath) != 0) fileNameWithCheckPath = mCheckPath + "/" + currentFile; else @@ -1065,13 +937,13 @@ void ResultsTree::recheckSelectedFiles() return; } if (Path::isHeader(currentFile.toStdString())) { - if (!itemdata[FILE0].toString().isEmpty() && !selectedItems.contains(itemdata[FILE0].toString())) { - selectedItems<<((!mCheckPath.isEmpty() && (itemdata[FILE0].toString().indexOf(mCheckPath) != 0)) ? (mCheckPath + "/" + itemdata[FILE0].toString()) : itemdata[FILE0].toString()); + if (!item->errorItem->file0.isEmpty() && !selectedItems.contains(item->errorItem->file0)) { + selectedItems << ((!mCheckPath.isEmpty() && (item->errorItem->file0.indexOf(mCheckPath) != 0)) ? (mCheckPath + "/" + item->errorItem->file0) : item->errorItem->file0); if (!selectedItems.contains(fileNameWithCheckPath)) - selectedItems<parent()) + if (!mContextItem || mContextItem->getType() == ResultItem::Type::file) return; - // Make sure we are working with the first column - if (mContextItem->column() != 0) - mContextItem = mContextItem->parent()->child(mContextItem->row(), 0); - QVariantMap itemdata = mContextItem->data().toMap(); - - QString messageId = itemdata[ERRORID].toString(); - - mHiddenMessageId.append(messageId); + mHiddenMessageId.append(mContextItem->errorItem->errorId); refreshTree(); emit resultsHidden(true); @@ -1101,25 +966,19 @@ void ResultsTree::suppressSelectedIds() return; QSet selectedIds; - for (QModelIndex index : mSelectionModel->selectedRows()) { - QStandardItem *item = mModel->itemFromIndex(index); - if (!item->parent()) - continue; - if (item->parent()->parent()) - item = item->parent(); - QVariantMap itemdata = item->data().toMap(); - if (!itemdata.contains("id")) + for (const QModelIndex& index : mSelectionModel->selectedRows()) { + const auto *item = dynamic_cast(mModel->itemFromIndex(index)); + if (!item || item->getType() == ResultItem::Type::file || !item->errorItem) continue; - selectedIds << itemdata[ERRORID].toString(); + selectedIds << item->errorItem->errorId; } // delete all errors with selected message Ids for (int i = 0; i < mModel->rowCount(); i++) { QStandardItem * const file = mModel->item(i, 0); for (int j = 0; j < file->rowCount();) { - QStandardItem *errorItem = file->child(j, 0); - QVariantMap userdata = errorItem->data().toMap(); - if (selectedIds.contains(userdata[ERRORID].toString())) { + const auto *errorItem = dynamic_cast(file->child(j, 0)); + if (errorItem && errorItem->errorItem && selectedIds.contains(errorItem->errorItem->errorId)) { file->removeRow(j); } else { j++; @@ -1129,8 +988,10 @@ void ResultsTree::suppressSelectedIds() mModel->removeRow(file->row()); } - - emit suppressIds(selectedIds.values()); + if (!selectedIds.isEmpty()) { + refreshTree(); // If all visible warnings was suppressed then the file item should be hidden + emit suppressIds(selectedIds.values()); + } } void ResultsTree::suppressHash() @@ -1138,31 +999,26 @@ void ResultsTree::suppressHash() if (!mSelectionModel) return; - // Extract selected warnings - QSet selectedWarnings; + bool changed = false; + ProjectFile *projectFile = ProjectFile::getActiveProject(); + for (QModelIndex index : mSelectionModel->selectedRows()) { - QStandardItem *item = mModel->itemFromIndex(index); - if (!item->parent()) + auto *item = dynamic_cast(mModel->itemFromIndex(index)); + if (!item || item->getType() == ResultItem::Type::file) continue; - while (item->parent()->parent()) - item = item->parent(); - selectedWarnings.insert(item); - } + if (item->getType() == ResultItem::Type::note) + item = dynamic_cast(item->parent()); - bool changed = false; - ProjectFile *projectFile = ProjectFile::getActiveProject(); - for (QStandardItem *item: selectedWarnings) { - QStandardItem *fileItem = item->parent(); - const QVariantMap itemdata = item->data().toMap(); - if (projectFile && itemdata.contains(HASH)) { + // Suppress + if (projectFile && item->errorItem->hash > 0) { SuppressionList::Suppression suppression; - suppression.hash = itemdata[HASH].toULongLong(); - suppression.errorId = itemdata[ERRORID].toString().toStdString(); - suppression.fileName = itemdata[FILENAME].toString().toStdString(); - suppression.lineNumber = itemdata[LINE].toInt(); + suppression.hash = item->errorItem->hash; projectFile->addSuppression(suppression); changed = true; } + + // Remove item + QStandardItem *fileItem = item->parent(); fileItem->removeRow(item->row()); if (fileItem->rowCount() == 0) mModel->removeRow(fileItem->row()); @@ -1174,7 +1030,9 @@ void ResultsTree::suppressHash() void ResultsTree::openContainingFolder() { - QString filePath = getFilePath(mContextItem, true); + if (!mContextItem) + return; + QString filePath = mContextItem->getErrorPathItem().file; if (!filePath.isEmpty()) { filePath = QFileInfo(filePath).absolutePath(); QDesktopServices::openUrl(QUrl::fromLocalFile(filePath)); @@ -1188,15 +1046,15 @@ void ResultsTree::tagSelectedItems(const QString &tag) bool isTagged = false; ProjectFile *currentProject = ProjectFile::getActiveProject(); for (QModelIndex index : mSelectionModel->selectedRows()) { - QStandardItem *item = mModel->itemFromIndex(index); - QVariantMap itemdata = item->data().toMap(); - if (itemdata.contains("tags")) { - itemdata[TAGS] = tag; - item->setData(QVariant(itemdata)); + auto *item = dynamic_cast(mModel->itemFromIndex(index)); + if (item && item->getType() != ResultItem::Type::file) { + if (item->getType() == ResultItem::Type::note) + item = dynamic_cast(item->parent()); + item->errorItem->tags = tag; item->parent()->child(index.row(), COLUMN_TAGS)->setText(tag); - if (currentProject && itemdata.contains(HASH)) { + if (currentProject && item->errorItem->hash > 0) { isTagged = true; - currentProject->setWarningTags(itemdata[HASH].toULongLong(), tag); + currentProject->setWarningTags(item->errorItem->hash, tag); } } } @@ -1211,30 +1069,7 @@ void ResultsTree::context(int application) void ResultsTree::quickStartApplication(const QModelIndex &index) { - startApplication(mModel->itemFromIndex(index)); -} - -QString ResultsTree::getFilePath(const QStandardItem *target, bool fullPath) -{ - if (target) { - // Make sure we are working with the first column - if (target->column() != 0) - target = target->parent()->child(target->row(), 0); - - QVariantMap targetdata = target->data().toMap(); - - //Replace (file) with filename - QString file = targetdata[FILENAME].toString(); - QString pathStr = QDir::toNativeSeparators(file); - if (!fullPath) { - QFileInfo fi(pathStr); - pathStr = fi.fileName(); - } - - return pathStr; - } - - return QString(); + startApplication(dynamic_cast(mModel->itemFromIndex(index))); } QString ResultsTree::severityToIcon(Severity severity) @@ -1263,20 +1098,20 @@ void ResultsTree::saveResults(Report *report) const for (int i = 0; i < mModel->rowCount(); i++) { if (mSaveAllErrors || !isRowHidden(i, QModelIndex())) - saveErrors(report, mModel->item(i, 0)); + saveErrors(report, dynamic_cast(mModel->item(i, 0))); } report->writeFooter(); } -void ResultsTree::saveErrors(Report *report, const QStandardItem *fileItem) const +void ResultsTree::saveErrors(Report *report, const ResultItem *fileItem) const { if (!fileItem) { return; } for (int i = 0; i < fileItem->rowCount(); i++) { - const QStandardItem *error = fileItem->child(i, 0); + const auto *error = dynamic_cast(fileItem->child(i, 0)); if (!error) { continue; @@ -1286,21 +1121,10 @@ void ResultsTree::saveErrors(Report *report, const QStandardItem *fileItem) cons continue; } - ErrorItem item; - readErrorItem(error, &item); - - report->writeError(item); + report->writeError(*error->errorItem); } } -static int indexOf(const QList &list, const ErrorItem &item) -{ - auto it = std::find_if(list.cbegin(), list.cend(), [&](const ErrorItem& e) { - return ErrorItem::sameCID(item, e); - }); - return it == list.cend() ? -1 : static_cast(std::distance(list.cbegin(), it)); -} - void ResultsTree::updateFromOldReport(const QString &filename) { showColumn(COLUMN_SINCE_DATE); @@ -1314,80 +1138,35 @@ void ResultsTree::updateFromOldReport(const QString &filename) // Read current results.. for (int i = 0; i < mModel->rowCount(); i++) { - QStandardItem *fileItem = mModel->item(i,0); + auto *fileItem = dynamic_cast(mModel->item(i,COLUMN_FILE)); for (int j = 0; j < fileItem->rowCount(); j++) { - QStandardItem *error = fileItem->child(j,0); - ErrorItem errorItem; - readErrorItem(error, &errorItem); - const int oldErrorIndex = indexOf(oldErrors, errorItem); - QVariantMap errordata = error->data().toMap(); + auto *error = dynamic_cast(fileItem->child(j,COLUMN_FILE)); + if (!error) + // FIXME.. + continue; + const auto it = std::find_if(oldErrors.cbegin(), + oldErrors.cend(), + [error](const ErrorItem& err) { + return ErrorItem::same(err, *error->errorItem); + }); + const ErrorItem* oldError = (it == oldErrors.cend()) ? nullptr : &*it; // New error .. set the "sinceDate" property - if (oldErrorIndex >= 0 && !oldErrors[oldErrorIndex].sinceDate.isEmpty()) { - errordata[SINCEDATE] = oldErrors[oldErrorIndex].sinceDate; - error->setData(errordata); - fileItem->child(j, COLUMN_SINCE_DATE)->setText(oldErrors[oldErrorIndex].sinceDate); - } else if (oldErrorIndex < 0 || errordata[SINCEDATE].toString().isEmpty()) { + if (oldError && !oldError->sinceDate.isEmpty()) { + error->errorItem->sinceDate = oldError->sinceDate; + fileItem->child(j, COLUMN_SINCE_DATE)->setText(error->errorItem->sinceDate); + } else if (oldError == nullptr || error->errorItem->sinceDate.isEmpty()) { const QString sinceDate = QLocale::system().toString(QDate::currentDate(), QLocale::ShortFormat); - errordata[SINCEDATE] = sinceDate; - error->setData(errordata); + error->errorItem->sinceDate = sinceDate; fileItem->child(j, COLUMN_SINCE_DATE)->setText(sinceDate); - if (oldErrorIndex < 0) - continue; } - if (!errorItem.tags.isEmpty()) - continue; - - const ErrorItem &oldErrorItem = oldErrors[oldErrorIndex]; - errordata[TAGS] = oldErrorItem.tags; - error->setData(errordata); + if (oldError && error->errorItem->tags.isEmpty()) + error->errorItem->tags = oldError->tags; } } } -void ResultsTree::readErrorItem(const QStandardItem *error, ErrorItem *item) const -{ - // Get error's user data - QVariantMap errordata = error->data().toMap(); - - item->severity = ShowTypes::ShowTypeToSeverity(ShowTypes::VariantToShowType(errordata[SEVERITY])); - item->summary = errordata[SUMMARY].toString(); - item->message = errordata[MESSAGE].toString(); - item->errorId = errordata[ERRORID].toString(); - item->cwe = errordata[CWE].toInt(); - item->hash = errordata[HASH].toULongLong(); - item->inconclusive = errordata[INCONCLUSIVE].toBool(); - item->file0 = errordata[FILE0].toString(); - item->sinceDate = errordata[SINCEDATE].toString(); - item->tags = errordata[TAGS].toString(); - item->remark = errordata[REMARK].toString(); - item->classification = error->parent()->child(error->row(), COLUMN_MISRA_CLASSIFICATION)->text(); - item->guideline = error->parent()->child(error->row(), COLUMN_MISRA_GUIDELINE)->text(); - - if (error->rowCount() == 0) { - QErrorPathItem e; - e.file = stripPath(errordata[FILENAME].toString(), true); - e.line = errordata[LINE].toInt(); - e.info = errordata[MESSAGE].toString(); - item->errorPath << e; - } - - for (int j = 0; j < error->rowCount(); j++) { - const QStandardItem *child_error = error->child(j, 0); - //Get error's user data - QVariant child_userdata = child_error->data(); - //Convert it to QVariantMap - QVariantMap child_data = child_userdata.toMap(); - - QErrorPathItem e; - e.file = stripPath(child_data[FILENAME].toString(), true); - e.line = child_data[LINE].toInt(); - e.info = child_data[MESSAGE].toString(); - item->errorPath << e; - } -} - void ResultsTree::updateSettings(bool showFullPath, bool saveFullPath, bool saveAllErrors, @@ -1431,60 +1210,38 @@ QString ResultsTree::stripPath(const QString &path, bool saving) const return dir.relativeFilePath(path); } -void ResultsTree::refreshFilePaths(QStandardItem *item) +void ResultsTree::refreshFilePaths(ResultItem *fileItem) { - if (!item) { + if (!fileItem) return; - } - //Mark that this file's path hasn't been updated yet - bool updated = false; + auto refreshItem = [this](ResultItem* item) { + item->setText(QDir::toNativeSeparators(stripPath(item->getErrorPathItem().file, false))); + }; + + refreshItem(fileItem); //Loop through all errors within this file - for (int i = 0; i < item->rowCount(); i++) { + for (int i = 0; i < fileItem->rowCount(); i++) { //Get error i - QStandardItem *error = item->child(i, 0); + auto *error = dynamic_cast(fileItem->child(i, COLUMN_FILE)); if (!error) { continue; } - //Get error's user data and convert it to QVariantMap - QVariantMap userdata = error->data().toMap(); - - //Get list of files - QString file = userdata[FILENAME].toString(); - //Update this error's text - error->setText(stripPath(file, false)); - - //If this error has backtraces make sure the files list has enough filenames - if (error->hasChildren()) { - //Loop through all files within the error - for (int j = 0; j < error->rowCount(); j++) { - //Get file - QStandardItem *child = error->child(j, 0); - if (!child) { - continue; - } - //Get child's user data - QVariant child_userdata = child->data(); - //Convert it to QVariantMap - QVariantMap child_data = child_userdata.toMap(); + refreshItem(error); - //Get list of files - QString child_files = child_data[FILENAME].toString(); + //Loop through all files within the error + for (int j = 0; j < error->rowCount(); j++) { + //Get file + auto *child = dynamic_cast(error->child(j, COLUMN_FILE)); + if (child) { //Update file's path - child->setText(stripPath(child_files, false)); + refreshItem(child); } } - - //if the main file hasn't been updated yet, update it now - if (!updated) { - updated = true; - item->setText(error->text()); - } - } } @@ -1493,8 +1250,8 @@ void ResultsTree::refreshFilePaths() qDebug("Refreshing file paths"); //Go through all file items (these are parent items that contain the errors) - for (int i = 0; i < mModel->rowCount(); i++) { - refreshFilePaths(mModel->item(i, 0)); + for (int row = 0; row < mModel->rowCount(); row++) { + refreshFilePaths(dynamic_cast(mModel->item(row, COLUMN_FILE))); } } @@ -1534,7 +1291,8 @@ void ResultsTree::showInconclusiveColumn(bool show) void ResultsTree::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { QTreeView::currentChanged(current, previous); - emit treeSelectionChanged(current); + const auto *item = dynamic_cast(mModel->itemFromIndex(current)); + emit treeSelectionChanged(item); } bool ResultsTree::isCertReport() const { diff --git a/gui/resultstree.h b/gui/resultstree.h index e73014fe12e..9537d6d929a 100644 --- a/gui/resultstree.h +++ b/gui/resultstree.h @@ -22,6 +22,7 @@ #include "showtypes.h" #include "checkers.h" +#include "resultitem.h" #include #include @@ -62,9 +63,9 @@ class ResultsTree : public QTreeView { /** * @brief Add a new item to the tree * - * @param item Error item data + * @param errorItem Error item data */ - bool addErrorItem(const ErrorItem &item); + bool addErrorItem(const ErrorItem& errorItem); /** * @brief Clear all errors from the tree @@ -207,6 +208,13 @@ class ResultsTree : public QTreeView { void setReportType(ReportType reportType); + /** + * @brief should errorItem be hidden by filter/severity/etc? + * @param errorItem error item + * @return true if error item should be hidden + */ + bool isErrorItemHidden(const QSharedPointer& errorItem) const; + signals: /** * @brief Signal that results have been hidden or shown @@ -226,11 +234,10 @@ class ResultsTree : public QTreeView { /** * @brief Signal for selection change in result tree. - * - * @param current Model index to specify new selected item. + * @param selectedItem item that was selected */ // NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name) - caused by generated MOC code - void treeSelectionChanged(const QModelIndex ¤t); + void treeSelectionChanged(const ResultItem *selectedItem); /** Suppress Ids */ // NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name) - caused by generated MOC code @@ -331,9 +338,9 @@ protected slots: /** * @brief Hides/shows full file path on all error file items according to mShowFullPath - * @param item Parent item whose children's paths to change + * @param fileItem Parent item whose children's paths to change */ - void refreshFilePaths(QStandardItem *item); + void refreshFilePaths(ResultItem *fileItem); /** @@ -351,7 +358,7 @@ protected slots: * @param report Report that errors are saved to * @param fileItem Item whose errors to save */ - void saveErrors(Report *report, const QStandardItem *fileItem) const; + void saveErrors(Report *report, const ResultItem *fileItem) const; /** * @brief Convert a severity string to a icon filename @@ -367,15 +374,7 @@ protected slots: * @param application Index of the application to open with. Giving -1 * (default value) will open the default application. */ - void startApplication(const QStandardItem *target, int application = -1); - - /** - * @brief Helper function returning the filename/full path of the error tree item \a target. - * - * @param target The error tree item containing the filename/full path - * @param fullPath Whether or not to retrieve the full path or only the filename. - */ - static QString getFilePath(const QStandardItem *target, bool fullPath); + void startApplication(const ResultItem *target, int application = -1); /** * @brief Context menu event (user right clicked on the tree) @@ -388,17 +387,19 @@ protected slots: * @brief Add a new error item beneath a file or a backtrace item beneath an error * * @param parent Parent for the item. Either a file item or an error item - * @param item Error line data + * @param errorItem Error item * @param hide Should this be hidden (true) or shown (false) * @param icon Should a default backtrace item icon be added - * @param childOfMessage Is this a child element of a message? - * @return newly created QStandardItem * + * @param type type of items to create file/message/note + * @param errorPathIndex errorPathIndex + * @return newly created ResultItem * */ - QStandardItem *addBacktraceFiles(QStandardItem *parent, - const ErrorLine &item, - bool hide, - const QString &icon, - bool childOfMessage); + ResultItem *addBacktraceFiles(ResultItem *parent, + const QSharedPointer& errorItem, + bool hide, + const QString &icon, + ResultItem::Type type, + int errorPathIndex); /** * @brief Convert Severity to translated string for GUI. @@ -423,29 +424,50 @@ protected slots: /** * @brief Create new normal item. * - * Normal item has left alignment and text set also as tooltip. - * @param name name for the item - * @return new QStandardItem + * Normal item has left alignment. + * @param text text for the item + * @param errorItem errorItem pointer + * @param type (file/message) + * @param errorPathIndex error path index + * @return new ResultItem */ - static QStandardItem *createNormalItem(const QString &name); + static ResultItem *createNormalItem(const QString &text, QSharedPointer errorItem, ResultItem::Type type, int errorPathIndex); /** - * @brief Create new normal item. + * @brief Create filename item. + * + * filename item has left alignment. Path is stripped and converted to native path separators. + * @param errorItem errorItem pointer + * @param type (file/message) + * @param errorPathIndex error path index + * + * @return new ResultItem + */ + ResultItem *createFilenameItem(const QSharedPointer& errorItem, ResultItem::Type type, int errorPathIndex); + + /** + * @brief Create new checkbox item. * - * Normal item has left alignment and text set also as tooltip. + * Checkbox item can be checked or unchecked. * @param checked checked - * @return new QStandardItem + * @param errorItem errorItem pointer + * @param type (file/message) + * @param errorPathIndex error path index + * @return new ResultItem */ - static QStandardItem *createCheckboxItem(bool checked); + static ResultItem *createCheckboxItem(bool checked, QSharedPointer errorItem, ResultItem::Type type, int errorPathIndex); /** * @brief Create new line number item. * - * Line number item has right align and text set as tooltip. - * @param linenumber name for the item - * @return new QStandardItem + * Line number item has right align. + * @param linenumber line number + * @param errorItem errorItem pointer + * @param type (file/message) + * @param errorPathIndex error path index + * @return new ResultItem */ - static QStandardItem *createLineNumberItem(const QString &linenumber); + static ResultItem *createLineNumberItem(int linenumber, QSharedPointer errorItem, ResultItem::Type type, int errorPathIndex); /** * @brief Finds a file item @@ -453,18 +475,16 @@ protected slots: * @param name name of the file item to find * @return pointer to file item or null if none found */ - QStandardItem *findFileItem(const QString &name) const; - + ResultItem *findFileItem(const QString &name) const; /** * @brief Ensures there's a item in the model for the specified file * - * @param fullpath Full path to the file item. - * @param file0 Source file + * @param errorItem Error item * @param hide is the error (we want this file item for) hidden? - * @return QStandardItem to be used as a parent for all errors for specified file + * @return ResultItem to be used as a parent for all errors for specified file */ - QStandardItem *ensureFileItem(const QString &fullpath, const QString &file0, bool hide); + ResultItem *ensureFileItem(const QSharedPointer& errorItem, bool hide); /** * @brief Item model for tree @@ -494,7 +514,7 @@ protected slots: * @brief Right clicked item (used by context menu slots) * */ - QStandardItem* mContextItem{}; + ResultItem* mContextItem{}; /** * @brief Should full path of files be shown (true) or relative (false) @@ -542,9 +562,6 @@ protected slots: /** tag selected items */ void tagSelectedItems(const QString &tag); - /** @brief Convert GUI error item into data error item */ - void readErrorItem(const QStandardItem *error, ErrorItem *item) const; - bool isCertReport() const; bool isAutosarMisraReport() const; diff --git a/gui/resultsview.cpp b/gui/resultsview.cpp index d200017fa50..8336a697878 100644 --- a/gui/resultsview.cpp +++ b/gui/resultsview.cpp @@ -69,8 +69,6 @@ #include #include #include -#include -#include #include enum class ReportType : std::uint8_t; @@ -440,54 +438,42 @@ void ResultsView::readErrorsXml(const QString &filename) mUI->mTree->setCheckDirectory(dir); } -void ResultsView::updateDetails(const QModelIndex &index) +void ResultsView::updateDetails(const ResultItem* item) { - const auto *model = qobject_cast(mUI->mTree->model()); - QStandardItem *item = model->itemFromIndex(index); - - if (!item) { + if (!item || !item->errorItem) { mUI->mCode->clear(); mUI->mDetails->setText(QString()); return; } - // Make sure we are working with the first column - if (item->parent() && item->column() != 0) - item = item->parent()->child(item->row(), 0); - - QVariantMap itemdata = item->data().toMap(); - - // If there is no severity data then it is a parent item without summary and message - if (!itemdata.contains("severity")) { + // File item => No details can be shown + if (item->getType() == ResultItem::Type::file) { mUI->mCode->clear(); mUI->mDetails->setText(QString()); return; } - const QString message = itemdata["message"].toString(); - QString formattedMsg = message; + QString formattedMsg = item->errorItem->message; - const QString file0 = itemdata["file0"].toString(); - if (!file0.isEmpty() && Path::isHeader(itemdata["file"].toString().toStdString())) + const QString file0 = item->errorItem->file0; + if (!file0.isEmpty() && Path::isHeader(item->getErrorPathItem().file.toStdString())) formattedMsg += QString("\n\n%1: %2").arg(tr("First included by")).arg(QDir::toNativeSeparators(file0)); - if (itemdata["cwe"].toInt() > 0) - formattedMsg.prepend("CWE: " + QString::number(itemdata["cwe"].toInt()) + "\n"); + if (item->errorItem->cwe > 0) + formattedMsg.prepend("CWE: " + QString::number(item->errorItem->cwe) + "\n"); if (mUI->mTree->showIdColumn()) - formattedMsg.prepend(tr("Id") + ": " + itemdata["id"].toString() + "\n"); - if (itemdata["incomplete"].toBool()) - formattedMsg += "\n" + tr("Bug hunting analysis is incomplete"); + formattedMsg.prepend(tr("Id") + ": " + item->errorItem->errorId + "\n"); mUI->mDetails->setText(formattedMsg); - const int lineNumber = itemdata["line"].toInt(); + const int lineNumber = item->getErrorPathItem().line; - QString filepath = itemdata["file"].toString(); + QString filepath = item->getErrorPathItem().file; if (!QFileInfo::exists(filepath) && QFileInfo::exists(mUI->mTree->getCheckDirectory() + '/' + filepath)) filepath = mUI->mTree->getCheckDirectory() + '/' + filepath; QStringList symbols; - if (itemdata.contains("symbolNames")) - symbols = itemdata["symbolNames"].toString().split("\n"); + if (!item->errorItem->symbolNames.isEmpty()) + symbols = item->errorItem->symbolNames.split("\n"); if (filepath == mUI->mCode->getFileName()) { mUI->mCode->setError(lineNumber, symbols); diff --git a/gui/resultsview.h b/gui/resultsview.h index 20ef0813615..7aef3c6d5c2 100644 --- a/gui/resultsview.h +++ b/gui/resultsview.h @@ -324,9 +324,9 @@ public slots: /** * @brief Update detailed message when selected item is changed. * - * @param index Position of new selected item. + * @param item selected item */ - void updateDetails(const QModelIndex &index); + void updateDetails(const ResultItem* item); /** * @brief Slot opening a print dialog to print the current report diff --git a/gui/showtypes.cpp b/gui/showtypes.cpp index 86b51bca48c..f1c0d325257 100644 --- a/gui/showtypes.cpp +++ b/gui/showtypes.cpp @@ -56,42 +56,6 @@ ShowTypes::ShowType ShowTypes::SeverityToShowType(Severity severity) } } -Severity ShowTypes::ShowTypeToSeverity(ShowTypes::ShowType type) -{ - switch (type) { - case ShowTypes::ShowStyle: - return Severity::style; - - case ShowTypes::ShowErrors: - return Severity::error; - - case ShowTypes::ShowWarnings: - return Severity::warning; - - case ShowTypes::ShowPerformance: - return Severity::performance; - - case ShowTypes::ShowPortability: - return Severity::portability; - - case ShowTypes::ShowInformation: - return Severity::information; - - case ShowTypes::ShowNone: - default: - return Severity::none; - } -} - -ShowTypes::ShowType ShowTypes::VariantToShowType(const QVariant &data) -{ - const int value = data.toInt(); - if (value < ShowTypes::ShowStyle || value > ShowTypes::ShowErrors) { - return ShowTypes::ShowNone; - } - return static_cast(value); -} - void ShowTypes::load() { QSettings settings; diff --git a/gui/showtypes.h b/gui/showtypes.h index 0627436075b..3280566ed35 100644 --- a/gui/showtypes.h +++ b/gui/showtypes.h @@ -21,8 +21,6 @@ #include -#include - enum class Severity : std::uint8_t; /// @addtogroup GUI @@ -103,21 +101,6 @@ class ShowTypes { */ static ShowTypes::ShowType SeverityToShowType(Severity severity); - /** - * @brief Convert ShowType to severity string - * @param type ShowType to convert - * @return ShowType converted to severity - */ - static Severity ShowTypeToSeverity(ShowTypes::ShowType type); - - /** - * @brief Convert QVariant (that contains an int) to Showtypes value - * - * @param data QVariant (that contains an int) to be converted - * @return data converted to ShowTypes - */ - static ShowTypes::ShowType VariantToShowType(const QVariant &data); - bool mVisible[ShowNone]; }; diff --git a/gui/test/resultstree/CMakeLists.txt b/gui/test/resultstree/CMakeLists.txt index c0b08195ea7..1bf8a02ffb1 100644 --- a/gui/test/resultstree/CMakeLists.txt +++ b/gui/test/resultstree/CMakeLists.txt @@ -1,6 +1,7 @@ qt_wrap_cpp(test-resultstree_SRC testresultstree.h ${CMAKE_SOURCE_DIR}/gui/resultstree.h + ${CMAKE_SOURCE_DIR}/gui/resultitem.h ${CMAKE_SOURCE_DIR}/gui/applicationlist.h ${CMAKE_SOURCE_DIR}/gui/projectfile.h ${CMAKE_SOURCE_DIR}/gui/threadhandler.h @@ -12,6 +13,7 @@ add_executable(test-resultstree ${test-resultstree_SRC} testresultstree.cpp ${CMAKE_SOURCE_DIR}/gui/resultstree.cpp + ${CMAKE_SOURCE_DIR}/gui/resultitem.cpp ${CMAKE_SOURCE_DIR}/gui/erroritem.cpp ${CMAKE_SOURCE_DIR}/gui/showtypes.cpp ${CMAKE_SOURCE_DIR}/gui/report.cpp diff --git a/gui/test/resultstree/testresultstree.cpp b/gui/test/resultstree/testresultstree.cpp index f0d85208a2b..be09034609f 100644 --- a/gui/test/resultstree/testresultstree.cpp +++ b/gui/test/resultstree/testresultstree.cpp @@ -134,6 +134,115 @@ void TestResultsTree::test1() const QCOMPARE(tree.isRowHidden(0,QModelIndex()), false); // Show item } +static QErrorPathItem createErrorPathItem(QString file, int line, int column, QString info) { + QErrorPathItem ret; + ret.file = std::move(file); + ret.line = line; + ret.column = column; + ret.info = std::move(info); + return ret; +} + +static ErrorItem createErrorItem(const QString& file, int line, Severity sev, const QString& message, QString id) { + ErrorItem ret; + ret.errorId = std::move(id); + ret.severity = sev; + ret.cwe = ret.hash = 0; + ret.file0 = file; + ret.inconclusive = false; + ret.message = ret.summary = message; + ret.errorPath << createErrorPathItem(file, line, 1, message); + return ret; +} + +void TestResultsTree::multiLineResult() const +{ + // Create tree with 1 multiline message + ResultsTree tree(nullptr); + ErrorItem errorItem = createErrorItem("file1.c", 10, Severity::style, "test", "bugId"); + errorItem.errorPath << createErrorPathItem("file2.c", 23, 2, "abc"); + tree.addErrorItem(errorItem); + + // Verify model + const auto* model = dynamic_cast(tree.model()); + QVERIFY(model != nullptr); + QVERIFY(model->rowCount() == 1); + + // Verify file item + const ResultItem* fileItem = dynamic_cast(model->item(0,0)); + QVERIFY(fileItem != nullptr); + QCOMPARE(fileItem->getType(), ResultItem::Type::file); + QCOMPARE(fileItem->text(), "file2.c"); + QCOMPARE(fileItem->getErrorPathItem().file, "file2.c"); + QVERIFY(fileItem->rowCount() == 1); + + // Verify message item + const ResultItem* res = dynamic_cast(fileItem->child(0,0)); + QVERIFY(res != nullptr); + QCOMPARE(res->text(), "file2.c"); + QVERIFY(res->errorItem != nullptr); + QCOMPARE(res->errorItem->toString(), + "file2.c:23:2:style: test [bugId]\n" + "file1.c:10:1:note: test\n" + "file2.c:23:2:note: abc"); + QCOMPARE(res->getErrorPathItem().file, "file2.c"); + QVERIFY(res->rowCount() == 2); + QVERIFY(res->columnCount() > 5); + // Verify both notes + for (int row = 0; row < 2; ++row) { + for (int col = 0; col < res->columnCount(); ++col) { + const ResultItem* item = dynamic_cast(res->child(row,col)); + QVERIFY(item); + QCOMPARE(item->errorItem.get(), res->errorItem.get()); + QCOMPARE(item->getType(), ResultItem::Type::note); + QCOMPARE(item->getErrorPathItem().file, row == 0 ? "file1.c" : "file2.c"); + } + } +} + +void TestResultsTree::resultsInSameFile() const +{ + ResultsTree tree(nullptr); + tree.addErrorItem(createErrorItem("file1.c", 10, Severity::style, "test", "bugId")); + tree.addErrorItem(createErrorItem("file1.c", 20, Severity::style, "test", "bugId")); + const auto* model = dynamic_cast(tree.model()); + QVERIFY(model != nullptr); + QVERIFY(model->rowCount() == 1); + + const ResultItem* fileItem = dynamic_cast(model->item(0,0)); + QVERIFY(fileItem != nullptr); + QCOMPARE(fileItem->getType(), ResultItem::Type::file); + QCOMPARE(fileItem->text(), "file1.c"); + QCOMPARE(fileItem->getErrorPathItem().file, "file1.c"); + QVERIFY(fileItem->rowCount() == 2); + + const ResultItem* res1 = dynamic_cast(fileItem->child(0,0)); + QVERIFY(res1 != nullptr); + QCOMPARE(res1->text(), "file1.c"); + QVERIFY(res1->errorItem != nullptr); + QCOMPARE(res1->errorItem->toString(), "file1.c:10:1:style: test [bugId]"); + QVERIFY(res1->rowCount() == 0); + for (int col = 0; col < fileItem->columnCount(); ++col) { + const ResultItem* item = dynamic_cast(fileItem->child(0,col)); + QVERIFY(item); + QCOMPARE(item->errorItem.get(), res1->errorItem.get()); + QCOMPARE(item->getType(), ResultItem::Type::message); + } + + const ResultItem* res2 = dynamic_cast(fileItem->child(1,0)); + QVERIFY(res2 != nullptr); + QCOMPARE(res2->text(), "file1.c"); + QVERIFY(res2->errorItem != nullptr); + QCOMPARE(res2->errorItem->toString(), "file1.c:20:1:style: test [bugId]"); + QVERIFY(res2->rowCount() == 0); + for (int col = 0; col < fileItem->columnCount(); ++col) { + const ResultItem* item = dynamic_cast(fileItem->child(1,col)); + QVERIFY(item); + QCOMPARE(item->errorItem.get(), res2->errorItem.get()); + QCOMPARE(item->getType(), ResultItem::Type::message); + } +} + void TestResultsTree::testReportType() const { TestReport report("{id},{classification},{guideline}"); @@ -194,5 +303,50 @@ void TestResultsTree::testGetGuidelineError() const QCOMPARE(report.output, "id1,Required,1.3"); } +void TestResultsTree::misraCReportShowClassifications() const +{ + ResultsTree tree(nullptr); + tree.showResults(ShowTypes::ShowType::ShowErrors, true); + tree.showResults(ShowTypes::ShowType::ShowWarnings, true); + tree.showResults(ShowTypes::ShowType::ShowStyle, true); + tree.setReportType(ReportType::misraC2012); + tree.addErrorItem(createErrorItem("file1.c", 10, Severity::style, "some rule text", "premium-misra-c-2012-1.1")); // Required + tree.addErrorItem(createErrorItem("file1.c", 20, Severity::style, "some rule text", "premium-misra-c-2012-1.2")); // Advisory + tree.addErrorItem(createErrorItem("file1.c", 30, Severity::style, "some rule text", "premium-misra-c-2012-9.1")); // Mandatory + QCOMPARE(tree.isRowHidden(0, QModelIndex()), false); + + const auto* model = dynamic_cast(tree.model()); + QVERIFY(model != nullptr); + QVERIFY(model->rowCount() == 1); + const ResultItem* fileItem = dynamic_cast(model->item(0,0)); + QVERIFY(fileItem != nullptr); + QVERIFY(fileItem->rowCount() == 3); + + QCOMPARE(tree.isRowHidden(0, fileItem->index()), false); + QCOMPARE(tree.isRowHidden(1, fileItem->index()), false); + QCOMPARE(tree.isRowHidden(2, fileItem->index()), false); + + tree.showResults(ShowTypes::ShowType::ShowErrors, false); + tree.showResults(ShowTypes::ShowType::ShowWarnings, true); + tree.showResults(ShowTypes::ShowType::ShowStyle, true); + QCOMPARE(tree.isRowHidden(0, fileItem->index()), false); + QCOMPARE(tree.isRowHidden(1, fileItem->index()), false); + QCOMPARE(tree.isRowHidden(2, fileItem->index()), true); + + tree.showResults(ShowTypes::ShowType::ShowErrors, true); + tree.showResults(ShowTypes::ShowType::ShowWarnings, false); + tree.showResults(ShowTypes::ShowType::ShowStyle, true); + QCOMPARE(tree.isRowHidden(0, fileItem->index()), true); + QCOMPARE(tree.isRowHidden(1, fileItem->index()), false); + QCOMPARE(tree.isRowHidden(2, fileItem->index()), false); + + tree.showResults(ShowTypes::ShowType::ShowErrors, true); + tree.showResults(ShowTypes::ShowType::ShowWarnings, true); + tree.showResults(ShowTypes::ShowType::ShowStyle, false); + QCOMPARE(tree.isRowHidden(0, fileItem->index()), false); + QCOMPARE(tree.isRowHidden(1, fileItem->index()), true); + QCOMPARE(tree.isRowHidden(2, fileItem->index()), false); +} + QTEST_MAIN(TestResultsTree) diff --git a/gui/test/resultstree/testresultstree.h b/gui/test/resultstree/testresultstree.h index a718690be32..1e743581bac 100644 --- a/gui/test/resultstree/testresultstree.h +++ b/gui/test/resultstree/testresultstree.h @@ -23,6 +23,9 @@ class TestResultsTree : public QObject { private slots: void test1() const; + void multiLineResult() const; + void resultsInSameFile() const; void testReportType() const; void testGetGuidelineError() const; + void misraCReportShowClassifications() const; };