/*
* Cppcheck - A tool for static C/C++ code analysis
* Copyright (C) 2007-2026 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 "resultstree.h"
#include "application.h"
#include "applicationlist.h"
#include "checkers.h"
#include "common.h"
#include "erroritem.h"
#include "errorlogger.h"
#include "errortypes.h"
#include "path.h"
#include "projectfile.h"
#include "report.h"
#include "resultitem.h"
#include "showtypes.h"
#include "suppressions.h"
#include "threadhandler.h"
#include "utils.h"
#include "xmlreportv2.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// These must match column headers given in ResultsTree::translate()
static constexpr int COLUMN_FILE = 0;
static constexpr int COLUMN_LINE = 1;
static constexpr int COLUMN_SEVERITY = 2;
static constexpr int COLUMN_MISRA_CLASSIFICATION = 3;
static constexpr int COLUMN_CERT_LEVEL = 4;
static constexpr int COLUMN_INCONCLUSIVE = 5;
static constexpr int COLUMN_SUMMARY = 6;
static constexpr int COLUMN_ID = 7;
static constexpr int COLUMN_MISRA_GUIDELINE = 8;
static constexpr int COLUMN_CERT_RULE = 9;
static constexpr int COLUMN_SINCE_DATE = 10;
static constexpr int COLUMN_TAGS = 11;
static constexpr int COLUMN_CWE = 12;
static QString getGuideline(ReportType reportType, const std::map &guidelineMapping,
const QString& errorId, Severity severity) {
return QString::fromStdString(getGuideline(errorId.toStdString(),
reportType, guidelineMapping,
severity));
}
static QString getClassification(ReportType reportType, const QString& guideline) {
return QString::fromStdString(getClassification(guideline.toStdString(), reportType));
}
static Severity getSeverityFromClassification(const QString &c) {
if (c == checkers::Man)
return Severity::error;
if (c == checkers::Req)
return Severity::warning;
if (c == checkers::Adv)
return Severity::style;
if (c == checkers::Doc)
return Severity::information;
if (c == "L1")
return Severity::error;
if (c == "L2")
return Severity::warning;
if (c == "L3")
return Severity::style;
return Severity::none;
}
static QStringList getLabels() {
return QStringList{
QObject::tr("File"),
QObject::tr("Line"),
QObject::tr("Severity"),
QObject::tr("Classification"),
QObject::tr("Level"),
QObject::tr("Inconclusive"),
QObject::tr("Summary"),
QObject::tr("Id"),
QObject::tr("Guideline"),
QObject::tr("Rule"),
QObject::tr("Since date"),
QObject::tr("Tags"),
QObject::tr("CWE")};
}
static Severity getSeverity(ReportType reportType, const ErrorItem& errorItem) {
return reportType == ReportType::normal ? errorItem.severity : getSeverityFromClassification(errorItem.classification);
}
ResultsTree::ResultsTree(QWidget * parent) :
QTreeView(parent),
mModel(new QStandardItemModel)
{
setModel(mModel);
translate(); // Adds columns to grid
clear();
setExpandsOnDoubleClick(false);
setSortingEnabled(true);
connect(this, &ResultsTree::doubleClicked, this, &ResultsTree::quickStartApplication);
}
void ResultsTree::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
quickStartApplication(this->currentIndex());
}
QTreeView::keyPressEvent(event);
}
void ResultsTree::setReportType(ReportType reportType) {
mReportType = reportType;
mGuideline = createGuidelineMapping(reportType);
for (int i = 0; i < mModel->rowCount(); ++i) {
auto *fileItem = dynamic_cast(mModel->item(i, COLUMN_FILE));
if (!fileItem)
continue;
for (int j = 0; j < fileItem->rowCount(); ++j) {
QSharedPointer& errorItem = dynamic_cast(fileItem->child(j,0))->errorItem;
errorItem->guideline = getGuideline(mReportType, mGuideline, errorItem->errorId, errorItem->severity);
errorItem->classification = getClassification(mReportType, errorItem->guideline);
dynamic_cast(fileItem->child(j, COLUMN_FILE))->setIconFileName(severityToIcon(getSeverity(reportType, *errorItem)));
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);
}
}
if (isAutosarMisraReport()) {
showColumn(COLUMN_MISRA_CLASSIFICATION);
showColumn(COLUMN_MISRA_GUIDELINE);
} else {
hideColumn(COLUMN_MISRA_CLASSIFICATION);
hideColumn(COLUMN_MISRA_GUIDELINE);
}
if (isCertReport()) {
showColumn(COLUMN_CERT_LEVEL);
showColumn(COLUMN_CERT_RULE);
} else {
hideColumn(COLUMN_CERT_LEVEL);
hideColumn(COLUMN_CERT_RULE);
}
if (mReportType == ReportType::normal) {
showColumn(COLUMN_SEVERITY);
} else {
hideColumn(COLUMN_SEVERITY);
}
refreshTree();
}
void ResultsTree::initialize(QSettings *settings, ApplicationList *list, ThreadHandler *checkThreadHandler)
{
mSettings = settings;
mApplications = list;
mThread = checkThreadHandler;
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;
}
ResultItem *ResultsTree::createFilenameItem(const QSharedPointer& errorItem, ResultItem::Type type, int errorPathIndex)
{
auto *item = new ResultItem(errorItem, type, errorPathIndex);
item->setText(QDir::toNativeSeparators(stripPath(errorItem->errorPath[errorPathIndex].file, false)));
item->setEditable(false);
return item;
}
ResultItem *ResultsTree::createCheckboxItem(bool checked, QSharedPointer errorItem, ResultItem::Type type, int errorPathIndex)
{
auto *item = new ResultItem(std::move(errorItem), type, errorPathIndex);
item->setCheckable(true);
item->setCheckState(checked ? Qt::Checked : Qt::Unchecked);
item->setEnabled(false);
return item;
}
ResultItem *ResultsTree::createLineNumberItem(int linenumber, QSharedPointer errorItem, ResultItem::Type type, int errorPathIndex)
{
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& errorItem)
{
if (errorItem.errorPath.isEmpty())
return false;
const QString s = errorItem.toString();
if (mErrorList.contains(s))
return false;
mErrorList.append(s);
QSharedPointer errorItemPtr{new ErrorItem(errorItem)};
if (mReportType != ReportType::normal) {
errorItemPtr->guideline = getGuideline(mReportType, mGuideline, errorItemPtr->errorId, errorItemPtr->severity);
errorItemPtr->classification = getClassification(mReportType, errorItemPtr->guideline);
}
const bool showItem = !isErrorItemHidden(errorItemPtr);
// if there is at least one error that is not hidden, we have a visible error
mVisibleErrors |= showItem;
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
ResultItem* fileItem = ensureFileItem(errorItemPtr, !showItem);
ResultItem* stditem = addBacktraceFiles(fileItem,
errorItemPtr,
!showItem,
severityToIcon(getSeverity(mReportType, *errorItemPtr)),
ResultItem::Type::message,
errorItemPtr->getMainLocIndex());
if (!stditem)
return false;
//Add backtrace files as children
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;
}
ResultItem *ResultsTree::addBacktraceFiles(ResultItem *parent,
const QSharedPointer& errorItem,
const bool hide,
const QString &icon,
ResultItem::Type type,
int errorPathIndex)
{
if (!parent)
return nullptr;
//TODO message has parameter names so we'll need changes to the core
//cppcheck so we can get proper translations
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 < errorPathIndex; i++) {
// The first column is the file name and is always the same
const auto& e = errorItem->errorPath[i];
if (loc.line == e.line && loc.info == e.info)
return nullptr;
}
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_FILE]->setIconFileName(icon);
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] ? columns[i] : createNormalItem(QString(), errorItem, type, errorPathIndex));
parent->appendRow(list);
setRowHidden(parent->rowCount() - 1, parent->index(), hide);
return columns[COLUMN_FILE];
}
QString ResultsTree::severityToTranslatedString(Severity severity)
{
switch (severity) {
case Severity::style:
return tr("style");
case Severity::error:
return tr("error");
case Severity::warning:
return tr("warning");
case Severity::performance:
return tr("performance");
case Severity::portability:
return tr("portability");
case Severity::information:
return tr("information");
case Severity::debug:
return tr("debug");
case Severity::internal:
return tr("internal");
case Severity::none:
return QString();
}
cppcheck::unreachable();
}
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.
for (int i = 0; i < mModel->rowCount(); i++) {
#ifdef _WIN32
if (QString::compare(mModel->item(i, COLUMN_FILE)->text(), name, Qt::CaseInsensitive) == 0)
#else
if (mModel->item(i, COLUMN_FILE)->text() == name)
#endif
return dynamic_cast(mModel->item(i, COLUMN_FILE));
}
return nullptr;
}
void ResultsTree::clear()
{
mErrorList.clear();
mModel->removeRows(0, mModel->rowCount());
if (const ProjectFile *activeProject = ProjectFile::getActiveProject()) {
hideColumn(COLUMN_SINCE_DATE);
if (activeProject->getTags().isEmpty())
hideColumn(COLUMN_TAGS);
else
showColumn(COLUMN_TAGS);
} else {
hideColumn(COLUMN_SINCE_DATE);
hideColumn(COLUMN_TAGS);
}
}
void ResultsTree::clear(const QString &filename)
{
const QString stripped = QDir::toNativeSeparators(stripPath(filename, false));
for (int i = 0; i < mModel->rowCount(); ++i) {
const auto *fileItem = dynamic_cast(mModel->item(i, COLUMN_FILE));
if (!fileItem)
continue;
if (stripped == fileItem->text() ||
filename == fileItem->errorItem->file0) {
mModel->removeRow(i);
mErrorList.removeAll(fileItem->errorItem->toString());
break;
}
}
}
void ResultsTree::clearRecheckFile(const QString &filename)
{
for (int i = 0; i < mModel->rowCount(); ++i) {
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);
QString storedfile = fileItem->getErrorPathItem().file;
storedfile = ((!mCheckPath.isEmpty() && storedfile.startsWith(mCheckPath)) ? storedfile.mid(mCheckPath.length() + 1) : storedfile);
if (actualfile == storedfile) {
mModel->removeRow(i);
mErrorList.removeAll(fileItem->errorItem->toString());
break;
}
}
}
void ResultsTree::loadSettings()
{
for (int i = 0; i < mModel->columnCount(); i++) {
QString temp = QString(SETTINGS_RESULT_COLUMN_WIDTH).arg(i);
setColumnWidth(i, qMax(20, mSettings->value(temp, 800 / mModel->columnCount()).toInt()));
}
mSaveFullPath = mSettings->value(SETTINGS_SAVE_FULL_PATH, false).toBool();
mSaveAllErrors = mSettings->value(SETTINGS_SAVE_ALL_ERRORS, false).toBool();
mShowFullPath = mSettings->value(SETTINGS_SHOW_FULL_PATH, false).toBool();
showIdColumn(mSettings->value(SETTINGS_SHOW_ERROR_ID, true).toBool());
showInconclusiveColumn(mSettings->value(SETTINGS_INCONCLUSIVE_ERRORS, false).toBool());
}
void ResultsTree::saveSettings() const
{
for (int i = 0; i < mModel->columnCount(); i++) {
QString temp = QString(SETTINGS_RESULT_COLUMN_WIDTH).arg(i);
mSettings->setValue(temp, columnWidth(i));
}
}
void ResultsTree::showResults(ShowTypes::ShowType type, bool show)
{
if (type != ShowTypes::ShowNone && mShowSeverities.isShown(type) != show) {
mShowSeverities.show(type, show);
refreshTree();
}
}
void ResultsTree::showCppcheckResults(bool show)
{
mShowCppcheck = show;
refreshTree();
}
void ResultsTree::showClangResults(bool show)
{
mShowClang = show;
refreshTree();
}
void ResultsTree::filterResults(const QString& filter)
{
mFilter = filter;
refreshTree();
}
void ResultsTree::showHiddenResults()
{
//Clear the "hide" flag for each item
mHiddenMessageId.clear();
refreshTree();
emit resultsHidden(false);
}
void ResultsTree::refreshTree()
{
mVisibleErrors = false;
//Get the amount of files in the tree
const int filecount = mModel->rowCount();
for (int i = 0; i < filecount; i++) {
//Get file i
auto *fileItem = dynamic_cast(mModel->item(i, 0));
if (!fileItem) {
continue;
}
//Get the amount of errors this file contains
const int errorcount = fileItem->rowCount();
//By default it shouldn't be visible
bool showFile = false;
for (int j = 0; j < errorcount; j++) {
//Get the error itself
const auto *child = dynamic_cast(fileItem->child(j, 0));
if (!child) {
continue;
}
//Check if this error should be hidden
const bool hide = child->hidden || isErrorItemHidden(child->errorItem);
if (!hide) {
showFile = true;
mVisibleErrors = true;
}
//Hide/show accordingly
setRowHidden(j, fileItem->index(), hide);
}
// Show the file if any of it's errors are visible
setRowHidden(i, QModelIndex(), !showFile);
}
sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder());
}
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 = QDir::toNativeSeparators(stripPath(errorItem->getFile(), false));
// Since item has path with native separators we must use path with
// native separators to find it.
ResultItem *fileItem = findFileItem(name);
if (fileItem) {
if (!hide)
setRowHidden(fileItem->row(), QModelIndex(), hide);
return fileItem;
}
// Ensure shown path is with native separators
fileItem = createFilenameItem(errorItem, ResultItem::Type::file, errorItem->getMainLocIndex());
fileItem->setIcon(QIcon(":images/text-x-generic.png"));
mModel->appendRow(fileItem);
setRowHidden(fileItem->row(), QModelIndex(), hide);
return fileItem;
}
void ResultsTree::contextMenuEvent(QContextMenuEvent * e)
{
QModelIndex index = indexAt(e->pos());
if (index.isValid()) {
bool multipleSelection = false;
mSelectionModel = selectionModel();
if (mSelectionModel->selectedRows().count() > 1)
multipleSelection = true;
mContextItem = dynamic_cast(mModel->itemFromIndex(index));
//Create a new context menu
QMenu menu(this);
//Create a signal mapper so we don't have to store data to class
//member variables
QSignalMapper signalMapper;
if (mContextItem && mApplications->getApplicationCount() > 0 && mContextItem->parent()) {
//Create an action for the application
int defaultApplicationIndex = mApplications->getDefaultApplication();
defaultApplicationIndex = std::max(defaultApplicationIndex, 0);
const Application& app = mApplications->getApplication(defaultApplicationIndex);
auto *start = new QAction(app.getName(), &menu);
if (multipleSelection)
start->setDisabled(true);
//Add it to context menu
menu.addAction(start);
//Connect the signal to signal mapper
connect(start, &QAction::triggered, &signalMapper, QOverload<>::of(&QSignalMapper::map));
//Add a new mapping
signalMapper.setMapping(start, defaultApplicationIndex);
connect(&signalMapper, SIGNAL(mappedInt(int)),
this, SLOT(context(int)));
}
// Add popup menuitems
if (mContextItem) {
if (mApplications->getApplicationCount() > 0) {
menu.addSeparator();
}
int selectedFiles = 0;
int selectedResults = 0;
for (auto row : mSelectionModel->selectedRows()) {
auto *item = mModel->itemFromIndex(row);
if (!item->parent())
selectedFiles++;
else if (!item->parent()->parent())
selectedResults++;
}
//Create an action for the application
auto *recheckAction = new QAction(tr("Recheck %1 file(s)").arg(selectedFiles), &menu);
auto *copyAction = new QAction(tr("Copy"), &menu);
auto *hide = new QAction(tr("Hide %1 result(s)").arg(selectedResults), &menu);
auto *hideallid = new QAction(tr("Hide all with id"), &menu);
auto *opencontainingfolder = new QAction(tr("Open containing folder"), &menu);
if (selectedFiles == 0 || mThread->isChecking() || mResultsSource == ResultsSource::Log)
recheckAction->setDisabled(true);
if (selectedResults == 0)
hide->setDisabled(true);
if (selectedResults == 0 || multipleSelection)
hideallid->setDisabled(true);
if (multipleSelection || mResultsSource == ResultsSource::Log)
opencontainingfolder->setDisabled(true);
menu.addAction(recheckAction);
menu.addSeparator();
menu.addAction(copyAction);
menu.addSeparator();
menu.addAction(hide);
menu.addAction(hideallid);
auto *suppress = new QAction(tr("Suppress selected id(s)"), &menu);
{
if (selectedResults == 0 || ErrorLogger::isCriticalErrorId(mContextItem->errorItem->errorId.toStdString()))
suppress->setDisabled(true);
}
menu.addAction(suppress);
connect(suppress, &QAction::triggered, this, &ResultsTree::suppressSelectedIds);
menu.addSeparator();
menu.addAction(opencontainingfolder);
connect(recheckAction, &QAction::triggered, this, &ResultsTree::recheckSelectedFiles);
connect(copyAction, &QAction::triggered, this, &ResultsTree::copy);
connect(hide, &QAction::triggered, this, &ResultsTree::hideResult);
connect(hideallid, &QAction::triggered, this, &ResultsTree::hideAllIdResult);
connect(opencontainingfolder, &QAction::triggered, this, &ResultsTree::openContainingFolder);
const ProjectFile *currentProject = ProjectFile::getActiveProject();
if (currentProject && !currentProject->getTags().isEmpty()) {
menu.addSeparator();
QMenu *tagMenu = menu.addMenu(tr("Tag"));
{
auto *action = new QAction(tr("No tag"), tagMenu);
tagMenu->addAction(action);
connect(action, &QAction::triggered, [this]() {
tagSelectedItems(QString());
});
}
for (const QString& tagstr : currentProject->getTags()) {
auto *action = new QAction(tagstr, tagMenu);
tagMenu->addAction(action);
connect(action, &QAction::triggered, [tagstr, this]() {
tagSelectedItems(tagstr);
});
}
}
}
//Start the menu
menu.exec(e->globalPos());
index = indexAt(e->pos());
if (index.isValid())
mContextItem = dynamic_cast(mModel->itemFromIndex(index));
}
}
void ResultsTree::startApplication(const ResultItem *target, int application)
{
//If there are no applications specified, tell the user about it
if (mApplications->getApplicationCount() == 0) {
QMessageBox msg(QMessageBox::Critical,
tr("Cppcheck"),
tr("No editor application configured.\n\n"
"Configure the editor application for Cppcheck in preferences/Applications."),
QMessageBox::Ok,
this);
msg.exec();
return;
}
if (application == -1)
application = mApplications->getDefaultApplication();
if (application == -1) {
QMessageBox msg(QMessageBox::Critical,
tr("Cppcheck"),
tr("No default editor application selected.\n\n"
"Please select the default editor application in preferences/Applications."),
QMessageBox::Ok,
this);
msg.exec();
return;
}
if (target && application >= 0 && application < mApplications->getApplicationCount() && target->parent()) {
const auto& errorPathItem = target->getErrorPathItem();
//Replace (file) with filename
QString file = QDir::toNativeSeparators(errorPathItem.file);
qDebug() << "Opening file: " << file;
const QFileInfo info(file);
if (!info.exists()) {
if (info.isAbsolute()) {
QMessageBox msgbox(this);
msgbox.setWindowTitle("Cppcheck");
msgbox.setText(tr("Could not find the file!"));
msgbox.setIcon(QMessageBox::Critical);
msgbox.exec();
} else {
QDir checkdir(mCheckPath);
if (checkdir.isAbsolute() && checkdir.exists()) {
file = mCheckPath + "/" + file;
} else {
QString dir = askFileDir(file);
dir += '/';
file = dir + file;
}
}
}
if (file.indexOf(" ") > -1) {
file.insert(0, "\"");
file.append("\"");
}
const Application& app = mApplications->getApplication(application);
QString params = app.getParameters();
params.replace("(file)", file, Qt::CaseInsensitive);
params.replace("(line)", QString::number(errorPathItem.line), Qt::CaseInsensitive);
params.replace("(message)", target->errorItem->message, Qt::CaseInsensitive);
params.replace("(severity)", severityToTranslatedString(target->errorItem->severity), Qt::CaseInsensitive);
QString program = app.getPath();
// In Windows we must surround paths including spaces with quotation marks.
#ifdef Q_OS_WIN
if (program.indexOf(" ") > -1) {
if (!program.startsWith('"') && !program.endsWith('"')) {
program.insert(0, "\"");
program.append("\"");
}
}
#endif // Q_OS_WIN
const bool success = QProcess::startDetached(program, QProcess::splitCommand(params));
if (!success) {
QString text = tr("Could not start %1\n\nPlease check the application path and parameters are correct.").arg(program);
QMessageBox msgbox(this);
msgbox.setWindowTitle("Cppcheck");
msgbox.setText(text);
msgbox.setIcon(QMessageBox::Critical);
msgbox.exec();
}
}
}
QString ResultsTree::askFileDir(const QString &file)
{
QString text = tr("Could not find file:") + '\n' + file + '\n';
QString title;
if (file.indexOf('/')) {
QString folderName = file.mid(0, file.indexOf('/'));
text += tr("Please select the folder '%1'").arg(folderName);
title = tr("Select Directory '%1'").arg(folderName);
} else {
text += tr("Please select the directory where file is located.");
title = tr("Select Directory");
}
QMessageBox msgbox(this);
msgbox.setWindowTitle("Cppcheck");
msgbox.setText(text);
msgbox.setIcon(QMessageBox::Warning);
msgbox.exec();
QString dir = QFileDialog::getExistingDirectory(this, title,
getPath(SETTINGS_LAST_SOURCE_PATH),
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
if (dir.isEmpty())
return QString();
// User selected root path
if (QFileInfo::exists(dir + '/' + file))
mCheckPath = dir;
// user selected checked folder
else if (file.indexOf('/') > 0) {
dir += '/';
QString folderName = file.mid(0, file.indexOf('/'));
if (dir.indexOf('/' + folderName + '/'))
dir = dir.mid(0, dir.lastIndexOf('/' + folderName + '/'));
if (QFileInfo::exists(dir + '/' + file))
mCheckPath = dir;
}
// Otherwise; return
else
return QString();
setPath(SETTINGS_LAST_SOURCE_PATH, mCheckPath);
return mCheckPath;
}
void ResultsTree::copy()
{
if (!mSelectionModel)
return;
QString text;
for (const QModelIndex& index : mSelectionModel->selectedRows()) {
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';
}
}
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(text);
}
void ResultsTree::hideResult()
{
if (!mSelectionModel)
return;
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);
}
}
void ResultsTree::recheckSelectedFiles()
{
if (!mSelectionModel)
return;
QStringList selectedItems;
for (const QModelIndex& index : mSelectionModel->selectedRows()) {
const auto *item = dynamic_cast(mModel->itemFromIndex(index));
while (item->parent())
item = dynamic_cast(item->parent());
const auto e = item->getErrorPathItem();
const QString currentFile = e.file;
if (!currentFile.isEmpty()) {
QString fileNameWithCheckPath;
const QFileInfo curfileInfo(currentFile);
if (!curfileInfo.exists() && !mCheckPath.isEmpty() && currentFile.indexOf(mCheckPath) != 0)
fileNameWithCheckPath = mCheckPath + "/" + currentFile;
else
fileNameWithCheckPath = currentFile;
const QFileInfo fileInfo(fileNameWithCheckPath);
if (!fileInfo.exists()) {
askFileDir(currentFile);
return;
}
if (Path::isHeader(currentFile.toStdString())) {
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 << fileNameWithCheckPath;
}
} else if (!selectedItems.contains(fileNameWithCheckPath))
selectedItems << fileNameWithCheckPath;
}
}
emit checkSelected(std::move(selectedItems));
}
void ResultsTree::hideAllIdResult()
{
if (!mContextItem || mContextItem->getType() == ResultItem::Type::file)
return;
mHiddenMessageId.append(mContextItem->errorItem->errorId);
refreshTree();
emit resultsHidden(true);
}
void ResultsTree::suppressSelectedIds()
{
if (!mSelectionModel)
return;
QSet selectedIds;
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 << 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();) {
const auto *errorItem = dynamic_cast(file->child(j, 0));
if (errorItem && errorItem->errorItem && selectedIds.contains(errorItem->errorItem->errorId)) {
file->removeRow(j);
} else {
j++;
}
}
if (file->rowCount() == 0)
mModel->removeRow(file->row());
}
if (!selectedIds.isEmpty()) {
refreshTree(); // If all visible warnings was suppressed then the file item should be hidden
emit suppressIds(selectedIds.values());
}
}
void ResultsTree::suppressHash()
{
if (!mSelectionModel)
return;
bool changed = false;
ProjectFile *projectFile = ProjectFile::getActiveProject();
for (QModelIndex index : mSelectionModel->selectedRows()) {
auto *item = dynamic_cast(mModel->itemFromIndex(index));
if (!item || item->getType() == ResultItem::Type::file)
continue;
if (item->getType() == ResultItem::Type::note)
item = dynamic_cast(item->parent());
// Suppress
if (projectFile && item->errorItem->hash > 0) {
SuppressionList::Suppression suppression;
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());
}
if (changed)
projectFile->write();
}
void ResultsTree::openContainingFolder()
{
if (!mContextItem)
return;
QString filePath = mContextItem->getErrorPathItem().file;
if (!filePath.isEmpty()) {
filePath = QFileInfo(filePath).absolutePath();
QDesktopServices::openUrl(QUrl::fromLocalFile(filePath));
}
}
void ResultsTree::tagSelectedItems(const QString &tag)
{
if (!mSelectionModel)
return;
bool isTagged = false;
ProjectFile *currentProject = ProjectFile::getActiveProject();
for (QModelIndex index : mSelectionModel->selectedRows()) {
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 && item->errorItem->hash > 0) {
isTagged = true;
currentProject->setWarningTags(item->errorItem->hash, tag);
}
}
}
if (isTagged)
currentProject->write();
}
void ResultsTree::context(int application)
{
startApplication(mContextItem, application);
}
void ResultsTree::quickStartApplication(const QModelIndex &index)
{
startApplication(dynamic_cast(mModel->itemFromIndex(index)));
}
QString ResultsTree::severityToIcon(Severity severity)
{
switch (severity) {
case Severity::error:
return ":images/dialog-error.png";
case Severity::style:
return ":images/applications-development.png";
case Severity::warning:
return ":images/dialog-warning.png";
case Severity::portability:
return ":images/applications-system.png";
case Severity::performance:
return ":images/utilities-system-monitor.png";
case Severity::information:
return ":images/dialog-information.png";
default:
return QString();
}
}
void ResultsTree::saveResults(Report *report) const
{
report->writeHeader();
for (int i = 0; i < mModel->rowCount(); i++) {
if (mSaveAllErrors || !isRowHidden(i, QModelIndex()))
saveErrors(report, dynamic_cast(mModel->item(i, 0)));
}
report->writeFooter();
}
void ResultsTree::saveErrors(Report *report, const ResultItem *fileItem) const
{
if (!fileItem) {
return;
}
for (int i = 0; i < fileItem->rowCount(); i++) {
const auto *error = dynamic_cast(fileItem->child(i, 0));
if (!error) {
continue;
}
if (isRowHidden(i, fileItem->index()) && !mSaveAllErrors) {
continue;
}
report->writeError(*error->errorItem);
}
}
void ResultsTree::updateFromOldReport(const QString &filename)
{
showColumn(COLUMN_SINCE_DATE);
QList oldErrors;
XmlReportV2 oldReport(filename, QString());
if (oldReport.open()) {
oldErrors = oldReport.read();
oldReport.close();
}
// Read current results..
for (int i = 0; i < mModel->rowCount(); i++) {
auto *fileItem = dynamic_cast(mModel->item(i,COLUMN_FILE));
for (int j = 0; j < fileItem->rowCount(); j++) {
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 (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);
error->errorItem->sinceDate = sinceDate;
fileItem->child(j, COLUMN_SINCE_DATE)->setText(sinceDate);
}
if (oldError && error->errorItem->tags.isEmpty())
error->errorItem->tags = oldError->tags;
}
}
}
void ResultsTree::updateSettings(bool showFullPath,
bool saveFullPath,
bool saveAllErrors,
bool showErrorId,
bool showInconclusive)
{
if (mShowFullPath != showFullPath) {
mShowFullPath = showFullPath;
refreshFilePaths();
}
mSaveFullPath = saveFullPath;
mSaveAllErrors = saveAllErrors;
showIdColumn(showErrorId);
showInconclusiveColumn(showInconclusive);
}
void ResultsTree::setCheckDirectory(const QString &dir)
{
mCheckPath = dir;
}
const QString& ResultsTree::getCheckDirectory() const
{
return mCheckPath;
}
void ResultsTree::setResultsSource(ResultsSource source)
{
mResultsSource = source;
}
QString ResultsTree::stripPath(const QString &path, bool saving) const
{
if ((!saving && mShowFullPath) || (saving && mSaveFullPath)) {
return QString(path);
}
QDir dir(mCheckPath);
return dir.relativeFilePath(path);
}
void ResultsTree::refreshFilePaths(ResultItem *fileItem)
{
if (!fileItem)
return;
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 < fileItem->rowCount(); i++) {
//Get error i
auto *error = dynamic_cast(fileItem->child(i, COLUMN_FILE));
if (!error) {
continue;
}
//Update this error's text
refreshItem(error);
//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
refreshItem(child);
}
}
}
}
void ResultsTree::refreshFilePaths()
{
qDebug("Refreshing file paths");
//Go through all file items (these are parent items that contain the errors)
for (int row = 0; row < mModel->rowCount(); row++) {
refreshFilePaths(dynamic_cast(mModel->item(row, COLUMN_FILE)));
}
}
bool ResultsTree::hasVisibleResults() const
{
return mVisibleErrors;
}
bool ResultsTree::hasResults() const
{
return mModel->rowCount() > 0;
}
void ResultsTree::translate()
{
mModel->setHorizontalHeaderLabels(getLabels());
//TODO go through all the errors in the tree and translate severity and message
}
void ResultsTree::showIdColumn(bool show)
{
mShowErrorId = show;
if (show)
showColumn(COLUMN_ID);
else
hideColumn(COLUMN_ID);
}
void ResultsTree::showInconclusiveColumn(bool show)
{
if (show)
showColumn(COLUMN_INCONCLUSIVE);
else
hideColumn(COLUMN_INCONCLUSIVE);
}
void ResultsTree::currentChanged(const QModelIndex ¤t, const QModelIndex &previous)
{
QTreeView::currentChanged(current, previous);
const auto *item = dynamic_cast(mModel->itemFromIndex(current));
emit treeSelectionChanged(item);
}
bool ResultsTree::isCertReport() const {
return mReportType == ReportType::certC || mReportType == ReportType::certCpp;
}
bool ResultsTree::isAutosarMisraReport() const {
return mReportType == ReportType::autosar ||
mReportType == ReportType::misraC2012 ||
mReportType == ReportType::misraC2023 ||
mReportType == ReportType::misraC2025 ||
mReportType == ReportType::misraCpp2008 ||
mReportType == ReportType::misraCpp2023;
}