Valgrind: Create a SuppressionsAspect

This is a fairly complex use case, as the corresponding widget looks the
same in global settings and Project settings, but behaves differently
(Project only stores a diff, is auto-apply). It also use two(!)
settings keys in the project case.

So while it works, it takes manual help for the cancel/apply and
toMap/fromMap. Looks like there is still some basic infrastructure
missing.

Change-Id: I25ab7b41616ee09ff9133e93b84f34947fc32988
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
hjk
2021-03-09 17:40:29 +01:00
parent bce64778c5
commit da01f4544f
5 changed files with 300 additions and 252 deletions

View File

@@ -128,7 +128,7 @@ public:
void start() override;
void stop() override;
QStringList suppressionFiles() const;
const QStringList suppressionFiles() const;
signals:
void internalParserError(const QString &errorString);
@@ -212,7 +212,7 @@ QStringList MemcheckToolRunner::toolArguments() const
}
arguments << "--leak-check=" + leakCheckValue;
foreach (const QString &file, m_settings.suppressionFiles())
for (const QString &file : m_settings.suppressions.value())
arguments << QString("--suppressions=%1").arg(file);
arguments << QString("--num-callers=%1").arg(m_settings.numCallers.value());
@@ -225,9 +225,9 @@ QStringList MemcheckToolRunner::toolArguments() const
return arguments;
}
QStringList MemcheckToolRunner::suppressionFiles() const
const QStringList MemcheckToolRunner::suppressionFiles() const
{
return m_settings.suppressionFiles();
return m_settings.suppressions.value();
}
void MemcheckToolRunner::startDebugger(qint64 valgrindPid)

View File

@@ -44,13 +44,12 @@
#include <utils/fileutils.h>
#include <utils/qtcassert.h>
#include <QFile>
#include <QDialogButtonBox>
#include <QFile>
#include <QFormLayout>
#include <QPushButton>
#include <QLabel>
#include <QPlainTextEdit>
#include <QPushButton>
using namespace Valgrind::XmlProtocol;
@@ -217,7 +216,7 @@ void SuppressionDialog::accept()
}
}
m_settings->addSuppressionFiles(QStringList(path));
m_settings->suppressions.addSuppressionFile(path);
QModelIndexList indices = m_view->selectionModel()->selectedRows();
Utils::sort(indices, [](const QModelIndex &l, const QModelIndex &r) {

View File

@@ -30,26 +30,17 @@
#include <debugger/analyzer/analyzericons.h>
#include <coreplugin/icore.h>
#include <utils/algorithm.h>
#include <utils/hostosinfo.h>
#include <utils/layoutbuilder.h>
#include <utils/qtcassert.h>
#include <QDebug>
#include <QFileDialog>
#include <QListView>
#include <QPushButton>
#include <QStandardItemModel>
#include <functional>
using namespace Utils;
namespace Valgrind {
namespace Internal {
class ValgrindBaseSettings;
class ValgrindConfigWidget : public Core::IOptionsPageWidget
{
Q_DECLARE_TR_FUNCTIONS(Valgrind::Internal::ValgrindConfigWidget)
@@ -63,37 +54,14 @@ public:
ValgrindGlobalSettings::instance()->writeSettings();
}
void setSuppressions(const QStringList &files);
QStringList suppressions() const;
void slotAddSuppression();
void slotRemoveSuppression();
void slotSuppressionsRemoved(const QStringList &files);
void slotSuppressionsAdded(const QStringList &files);
void slotSuppressionSelectionChanged();
private:
void updateUi();
ValgrindBaseSettings *m_settings;
QPushButton *addSuppression;
QPushButton *removeSuppression;
QListView *suppressionList;
QStandardItemModel m_model;
void finish() final
{
ValgrindGlobalSettings::instance()->group.finish();
}
};
ValgrindConfigWidget::ValgrindConfigWidget(ValgrindBaseSettings *settings)
: m_settings(settings)
{
addSuppression = new QPushButton(tr("Add..."));
removeSuppression = new QPushButton(tr("Remove"));
suppressionList = new QListView;
suppressionList->setModel(&m_model);
suppressionList->setSelectionMode(QAbstractItemView::MultiSelection);
using namespace Layouting;
const Break nl;
ValgrindBaseSettings &s = *settings;
@@ -111,16 +79,7 @@ ValgrindConfigWidget::ValgrindConfigWidget(ValgrindBaseSettings *settings)
s.leakCheckOnFinish, nl,
s.numCallers, nl,
s.filterExternalIssues, nl,
Item {
Group {
Title(tr("Suppression files:")),
Row {
suppressionList,
Column { addSuppression, removeSuppression, Stretch() }
}
},
2 // Span.
}
s.suppressions
};
Grid callgrind {
@@ -146,111 +105,6 @@ ValgrindConfigWidget::ValgrindConfigWidget(ValgrindBaseSettings *settings)
Group { Title(tr("CallGrind Profiling Options")), callgrind },
Stretch(),
}.attachTo(this);
updateUi();
connect(m_settings, &ValgrindBaseSettings::changed, this, &ValgrindConfigWidget::updateUi);
connect(addSuppression, &QPushButton::clicked,
this, &ValgrindConfigWidget::slotAddSuppression);
connect(removeSuppression, &QPushButton::clicked,
this, &ValgrindConfigWidget::slotRemoveSuppression);
connect(&s, &ValgrindBaseSettings::suppressionFilesRemoved,
this, &ValgrindConfigWidget::slotSuppressionsRemoved);
connect(&s, &ValgrindBaseSettings::suppressionFilesAdded,
this, &ValgrindConfigWidget::slotSuppressionsAdded);
connect(suppressionList->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &ValgrindConfigWidget::slotSuppressionSelectionChanged);
slotSuppressionSelectionChanged();
}
void ValgrindConfigWidget::updateUi()
{
m_model.clear();
foreach (const QString &file, m_settings->suppressionFiles())
m_model.appendRow(new QStandardItem(file));
}
void ValgrindConfigWidget::slotAddSuppression()
{
ValgrindGlobalSettings *conf = ValgrindGlobalSettings::instance();
QTC_ASSERT(conf, return);
QStringList files = QFileDialog::getOpenFileNames(this,
tr("Valgrind Suppression Files"),
conf->lastSuppressionDirectory.value(),
tr("Valgrind Suppression File (*.supp);;All Files (*)"));
//dialog.setHistory(conf->lastSuppressionDialogHistory());
if (!files.isEmpty()) {
foreach (const QString &file, files)
m_model.appendRow(new QStandardItem(file));
m_settings->addSuppressionFiles(files);
conf->lastSuppressionDirectory.setValue(QFileInfo(files.at(0)).absolutePath());
//conf->setLastSuppressionDialogHistory(dialog.history());
}
}
void ValgrindConfigWidget::slotSuppressionsAdded(const QStringList &files)
{
QStringList filesToAdd = files;
for (int i = 0, c = m_model.rowCount(); i < c; ++i)
filesToAdd.removeAll(m_model.item(i)->text());
foreach (const QString &file, filesToAdd)
m_model.appendRow(new QStandardItem(file));
}
void ValgrindConfigWidget::slotRemoveSuppression()
{
// remove from end so no rows get invalidated
QList<int> rows;
QStringList removed;
foreach (const QModelIndex &index, suppressionList->selectionModel()->selectedIndexes()) {
rows << index.row();
removed << index.data().toString();
}
Utils::sort(rows, std::greater<int>());
foreach (int row, rows)
m_model.removeRow(row);
m_settings->removeSuppressionFiles(removed);
}
void ValgrindConfigWidget::slotSuppressionsRemoved(const QStringList &files)
{
for (int i = 0; i < m_model.rowCount(); ++i) {
if (files.contains(m_model.item(i)->text())) {
m_model.removeRow(i);
--i;
}
}
}
void ValgrindConfigWidget::setSuppressions(const QStringList &files)
{
m_model.clear();
foreach (const QString &file, files)
m_model.appendRow(new QStandardItem(file));
}
QStringList ValgrindConfigWidget::suppressions() const
{
QStringList ret;
for (int i = 0; i < m_model.rowCount(); ++i)
ret << m_model.item(i)->text();
return ret;
}
void ValgrindConfigWidget::slotSuppressionSelectionChanged()
{
removeSuppression->setEnabled(suppressionList->selectionModel()->hasSelection());
}
// ValgrindOptionsPage

View File

@@ -30,32 +30,277 @@
#include <coreplugin/icore.h>
#include <utils/layoutbuilder.h>
#include <utils/qtcassert.h>
#include <utils/treemodel.h>
#include <utils/utilsicons.h>
#include <valgrind/xmlprotocol/error.h>
#include <QSettings>
#include <QDebug>
#include <QFileDialog>
#include <QListView>
#include <QPushButton>
#include <QSettings>
#include <QStandardItemModel>
using namespace Utils;
namespace Valgrind {
namespace Internal {
//
// SuppressionAspect
//
// This is somewhat unusual, as it looks the same in Global Settings and Project
// settings, but behaves differently (Project only stores a diff) depending on
// context.
const char globalSuppressionKey[] = "Analyzer.Valgrind.SupressionFiles";
const char removedProjectSuppressionKey[] = "Analyzer.Valgrind.RemovedSuppressionFiles";
const char addedProjectSuppressionKey[] = "Analyzer.Valgrind.AddedSuppressionFiles";
class SuppressionAspectPrivate : public QObject
{
Q_DECLARE_TR_FUNCTIONS(Valgrind::Internal::ValgrindConfigWidget)
public:
SuppressionAspectPrivate(SuppressionAspect *q, bool global) : q(q), isGlobal(global) {}
void slotAddSuppression();
void slotRemoveSuppression();
void slotSuppressionSelectionChanged();
SuppressionAspect *q;
const bool isGlobal;
QPointer<QPushButton> addEntry;
QPointer<QPushButton> removeEntry;
QPointer<QListView> entryList;
QStandardItemModel m_model; // The volatile value of this aspect.
QStringList globalSuppressionFiles; // Real value, only used for global settings
QStringList removedProjectSuppressionFiles; // Part of real value for project settings
QStringList addedProjectSuppressionFiles; // Part of real value for project settings
};
void SuppressionAspect::addSuppressionFile(const QString &suppression)
{
if (d->isGlobal) {
d->globalSuppressionFiles.append(suppression);
} else {
const QStringList globalSuppressions = ValgrindGlobalSettings::instance()->suppressions.value();
if (!d->addedProjectSuppressionFiles.contains(suppression))
d->addedProjectSuppressionFiles.append(suppression);
d->removedProjectSuppressionFiles.removeAll(suppression);
}
setVolatileValue(value());
}
void SuppressionAspectPrivate::slotAddSuppression()
{
ValgrindGlobalSettings *conf = ValgrindGlobalSettings::instance();
QTC_ASSERT(conf, return);
const QStringList files =
QFileDialog::getOpenFileNames(Core::ICore::dialogParent(),
tr("Valgrind Suppression Files"),
conf->lastSuppressionDirectory.value(),
tr("Valgrind Suppression File (*.supp);;All Files (*)"));
//dialog.setHistory(conf->lastSuppressionDialogHistory());
if (!files.isEmpty()) {
for (const QString &file : files)
m_model.appendRow(new QStandardItem(file));
conf->lastSuppressionDirectory.setValue(QFileInfo(files.at(0)).absolutePath());
//conf->setLastSuppressionDialogHistory(dialog.history());
if (!isGlobal)
q->apply();
}
}
void SuppressionAspectPrivate::slotRemoveSuppression()
{
// remove from end so no rows get invalidated
QList<int> rows;
QStringList removed;
const QModelIndexList selected = entryList->selectionModel()->selectedIndexes();
for (const QModelIndex &index : selected) {
rows << index.row();
removed << index.data().toString();
}
Utils::sort(rows, std::greater<int>());
for (int row : qAsConst(rows))
m_model.removeRow(row);
if (!isGlobal)
q->apply();
}
void SuppressionAspectPrivate::slotSuppressionSelectionChanged()
{
removeEntry->setEnabled(entryList->selectionModel()->hasSelection());
}
//
// SuppressionAspect
//
SuppressionAspect::SuppressionAspect(bool global)
{
d = new SuppressionAspectPrivate(this, global);
}
SuppressionAspect::~SuppressionAspect()
{
delete d;
}
QStringList SuppressionAspect::value() const
{
// Note: BaseAspect::d->value is /not/ used.
if (d->isGlobal)
return d->globalSuppressionFiles;
QStringList ret = ValgrindGlobalSettings::instance()->suppressions.value();
for (const QString &s : d->removedProjectSuppressionFiles)
ret.removeAll(s);
ret.append(d->addedProjectSuppressionFiles);
return ret;
}
void SuppressionAspect::setValue(const QStringList &val)
{
if (d->isGlobal) {
d->globalSuppressionFiles = val;
} else {
const QStringList globals = ValgrindGlobalSettings::instance()->suppressions.value();
d->addedProjectSuppressionFiles.clear();
for (const QString &s : val) {
if (!globals.contains(s))
d->addedProjectSuppressionFiles.append(s);
}
d->removedProjectSuppressionFiles.clear();
for (const QString &s : globals) {
if (!val.contains(s))
d->removedProjectSuppressionFiles.append(s);
}
}
}
void SuppressionAspect::addToLayout(LayoutBuilder &builder)
{
QTC_CHECK(!d->addEntry);
QTC_CHECK(!d->removeEntry);
QTC_CHECK(!d->entryList);
using namespace Layouting;
d->addEntry = new QPushButton(tr("Add..."));
d->removeEntry = new QPushButton(tr("Remove"));
d->entryList = new QListView;
d->entryList->setModel(&d->m_model);
d->entryList->setSelectionMode(QAbstractItemView::MultiSelection);
connect(d->addEntry, &QPushButton::clicked,
d, &SuppressionAspectPrivate::slotAddSuppression);
connect(d->removeEntry, &QPushButton::clicked,
d, &SuppressionAspectPrivate::slotRemoveSuppression);
connect(d->entryList->selectionModel(), &QItemSelectionModel::selectionChanged,
d, &SuppressionAspectPrivate::slotSuppressionSelectionChanged);
Group group {
Title(tr("Suppression files:")),
Row {
d->entryList.data(),
Column { d->addEntry.data(), d->removeEntry.data(), Stretch() }
}
};
builder.addItem(Item { group, 2 });
}
void SuppressionAspect::fromMap(const QVariantMap &map)
{
if (d->isGlobal) {
d->globalSuppressionFiles = map.value(globalSuppressionKey).toStringList();
} else {
d->addedProjectSuppressionFiles = map.value(addedProjectSuppressionKey).toStringList();
d->removedProjectSuppressionFiles = map.value(removedProjectSuppressionKey).toStringList();
}
setVolatileValue(value());
}
void SuppressionAspect::toMap(QVariantMap &map) const
{
auto save = [&map](const QStringList &data, const QString &key) {
if (data.isEmpty())
map.remove(key);
else
map.insert(key, data);
};
if (d->isGlobal) {
save(d->globalSuppressionFiles, globalSuppressionKey);
} else {
save(d->addedProjectSuppressionFiles, addedProjectSuppressionKey);
save(d->removedProjectSuppressionFiles, removedProjectSuppressionKey);
}
}
QVariant SuppressionAspect::volatileValue() const
{
QStringList ret;
for (int i = 0; i < d->m_model.rowCount(); ++i)
ret << d->m_model.item(i)->text();
return ret;
}
void SuppressionAspect::setVolatileValue(const QVariant &val)
{
const QStringList files = val.toStringList();
d->m_model.clear();
for (const QString &file : files)
d->m_model.appendRow(new QStandardItem(file));
}
void SuppressionAspect::cancel()
{
setVolatileValue(value());
}
void SuppressionAspect::apply()
{
setValue(volatileValue().toStringList());
}
void SuppressionAspect::finish()
{
setVolatileValue(value()); // Clean up m_model content
}
//////////////////////////////////////////////////////////////////
//
// ValgrindBaseSettings
//
//////////////////////////////////////////////////////////////////
ValgrindBaseSettings::ValgrindBaseSettings()
ValgrindBaseSettings::ValgrindBaseSettings(bool global)
: suppressions(global)
{
// Note that this is used twice, once for project settings in the .user files
// and once for global settings in QtCreator.ini. This uses intentionally
// the same key to facilitate copying using fromMap/toMap.
QString base = "Analyzer.Valgrind.";
group.registerAspect(&suppressions);
group.registerAspect(&valgrindExecutable);
valgrindExecutable.setSettingsKey(base + "ValgrindExecutable");
valgrindExecutable.setDefaultValue("valgrind");
@@ -236,14 +481,12 @@ void ValgrindBaseSettings::toMap(QVariantMap &map) const
static ValgrindGlobalSettings *theGlobalSettings = nullptr;
ValgrindGlobalSettings::ValgrindGlobalSettings()
: ValgrindBaseSettings(true)
{
theGlobalSettings = this;
const QString base = "Analyzer.Valgrind";
group.registerAspect(&suppressionFiles_);
suppressionFiles_.setSettingsKey(base + "SupressionFiles");
group.registerAspect(&lastSuppressionDirectory);
lastSuppressionDirectory.setSettingsKey(base + "LastSuppressionDirectory");
@@ -282,20 +525,6 @@ ValgrindGlobalSettings *ValgrindGlobalSettings::instance()
//
// Memcheck
//
QStringList ValgrindGlobalSettings::suppressionFiles() const
{
return suppressionFiles_.value();
}
void ValgrindGlobalSettings::addSuppressionFiles(const QStringList &suppressions)
{
suppressionFiles_.appendValues(suppressions);
}
void ValgrindGlobalSettings::removeSuppressionFiles(const QStringList &suppressions)
{
suppressionFiles_.removeValues(suppressions);
}
QVariantMap ValgrindBaseSettings::defaultSettings() const
{
@@ -310,14 +539,13 @@ static const char groupC[] = "Analyzer";
void ValgrindGlobalSettings::readSettings()
{
QVariantMap defaults = defaultSettings();
// Read stored values
QSettings *settings = Core::ICore::settings();
settings->beginGroup(groupC);
QVariantMap map = defaults;
for (QVariantMap::ConstIterator it = defaults.constBegin(); it != defaults.constEnd(); ++it)
map.insert(it.key(), settings->value(it.key(), it.value()));
QVariantMap map;
const QStringList childKey = settings->childKeys();
for (const QString &key : childKey)
map.insert(key, settings->value(key));
settings->endGroup();
fromMap(map);
@@ -343,49 +571,9 @@ void ValgrindGlobalSettings::writeSettings() const
//////////////////////////////////////////////////////////////////
ValgrindProjectSettings::ValgrindProjectSettings()
: ValgrindBaseSettings(false)
{
setConfigWidgetCreator([this] { return ValgrindOptionsPage::createSettingsWidget(this); });
group.registerAspect(&disabledGlobalSuppressionFiles);
disabledGlobalSuppressionFiles.setSettingsKey("Analyzer.Valgrind.RemovedSuppressionFiles");
group.registerAspect(&addedSuppressionFiles);
addedSuppressionFiles.setSettingsKey("Analyzer.Valgrind.AddedSuppressionFiles");
}
//
// Memcheck
//
void ValgrindProjectSettings::addSuppressionFiles(const QStringList &suppressions)
{
const QStringList globalSuppressions = ValgrindGlobalSettings::instance()->suppressionFiles();
for (const QString &s : suppressions) {
if (addedSuppressionFiles.value().contains(s))
continue;
disabledGlobalSuppressionFiles.removeValue(s);
if (!globalSuppressions.contains(s))
addedSuppressionFiles.appendValue(s);
}
}
void ValgrindProjectSettings::removeSuppressionFiles(const QStringList &suppressions)
{
const QStringList globalSuppressions = ValgrindGlobalSettings::instance()->suppressionFiles();
for (const QString &s : suppressions) {
addedSuppressionFiles.removeValue(s);
if (globalSuppressions.contains(s))
disabledGlobalSuppressionFiles.appendValue(s);
}
}
QStringList ValgrindProjectSettings::suppressionFiles() const
{
QStringList ret = ValgrindGlobalSettings::instance()->suppressionFiles();
for (const QString &s : disabledGlobalSuppressionFiles.value())
ret.removeAll(s);
ret.append(addedSuppressionFiles.value());
return ret;
}
} // namespace Internal

View File

@@ -36,6 +36,36 @@ namespace Internal {
const char ANALYZER_VALGRIND_SETTINGS[] = "Analyzer.Valgrind.Settings";
class SuppressionAspectPrivate;
class SuppressionAspect final : public Utils::BaseAspect
{
public:
explicit SuppressionAspect(bool global);
~SuppressionAspect() final;
QStringList value() const;
void setValue(const QStringList &val);
void addToLayout(Utils::LayoutBuilder &builder) final;
void fromMap(const QVariantMap &map) final;
void toMap(QVariantMap &map) const final;
QVariant volatileValue() const final;
void setVolatileValue(const QVariant &val) final;
void cancel() final;
void apply() final;
void finish() final;
void addSuppressionFile(const QString &suppressionFile);
private:
friend class ValgrindBaseSettings;
SuppressionAspectPrivate *d = nullptr;
};
/**
* Valgrind settings shared for global and per-project.
*/
@@ -44,7 +74,7 @@ class ValgrindBaseSettings : public ProjectExplorer::ISettingsAspect
Q_OBJECT
public:
ValgrindBaseSettings();
explicit ValgrindBaseSettings(bool global);
enum SelfModifyingCodeDetection {
DetectSmcNo,
@@ -73,6 +103,8 @@ public:
Utils::StringAspect valgrindArguments;
Utils::SelectionAspect selfModifyingCodeDetection;
SuppressionAspect suppressions;
/**
* Base memcheck settings
*/
@@ -85,16 +117,8 @@ public:
Utils::BoolAspect filterExternalIssues;
Utils::IntegersAspect visibleErrorKinds;
virtual QStringList suppressionFiles() const = 0;
virtual void addSuppressionFiles(const QStringList &) = 0;
virtual void removeSuppressionFiles(const QStringList &) = 0;
void setVisibleErrorKinds(const QList<int> &);
signals:
void suppressionFilesRemoved(const QStringList &);
void suppressionFilesAdded(const QStringList &);
/**
* Base callgrind settings
*/
@@ -130,15 +154,10 @@ public:
/**
* Global memcheck settings
*/
QStringList suppressionFiles() const override;
// in the global settings we change the internal list directly
void addSuppressionFiles(const QStringList &) override;
void removeSuppressionFiles(const QStringList &) override;
void writeSettings() const;
void readSettings();
Utils::StringListAspect suppressionFiles_;
Utils::StringAspect lastSuppressionDirectory;
Utils::StringAspect lastSuppressionHistory;
@@ -161,18 +180,6 @@ class ValgrindProjectSettings : public ValgrindBaseSettings
public:
ValgrindProjectSettings();
/**
* Per-project memcheck settings, saves a diff to the global suppression files list
*/
QStringList suppressionFiles() const override;
// in the project-specific settings we store a diff to the global list
void addSuppressionFiles(const QStringList &suppressions) override;
void removeSuppressionFiles(const QStringList &suppressions) override;
private:
Utils::StringListAspect disabledGlobalSuppressionFiles;
Utils::StringListAspect addedSuppressionFiles;
};
} // namespace Internal